python-ironicclient-2.2.0/0000775000175100017510000000000013232474761015551 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/.coveragerc0000666000175100017510000000011113232474343017661 0ustar zuulzuul00000000000000[report] include = ironicclient/* omit = ironicclient/tests/functional/* python-ironicclient-2.2.0/.stestr.conf0000666000175100017510000000010713232474343020016 0ustar zuulzuul00000000000000[DEFAULT] test_path=${TESTS_DIR:-./ironicclient/tests/unit} top_dir=./ python-ironicclient-2.2.0/python_ironicclient.egg-info/0000775000175100017510000000000013232474761023326 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/python_ironicclient.egg-info/not-zip-safe0000664000175100017510000000000113232474742025553 0ustar zuulzuul00000000000000 python-ironicclient-2.2.0/python_ironicclient.egg-info/PKG-INFO0000664000175100017510000001345413232474760024431 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: python-ironicclient Version: 2.2.0 Summary: OpenStack Bare Metal Provisioning API Client Library Home-page: https://docs.openstack.org/python-ironicclient/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description-Content-Type: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-ironicclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Python bindings for the Ironic API ================================== This is a client for the OpenStack `Ironic `_ API. It provides: * a Python API: the ``ironicclient`` module, and * two command-line interfaces: ``openstack baremetal`` and ``ironic`` (deprecated, please use ``openstack baremetal``). Development takes place via the usual OpenStack processes as outlined in the `developer guide `_. The master repository is on `git.openstack.org `_. ``python-ironicclient`` is licensed under the Apache License, Version 2.0, like the rest of OpenStack. .. contents:: Contents: :local: Python API ---------- Quick-start Example:: >>> from ironicclient import client >>> >>> kwargs = {'os_auth_token': '3bcc3d3a03f44e3d8377f9247b0ad155', >>> 'ironic_url': 'http://ironic.example.org:6385/'} >>> ironic = client.get_client(1, **kwargs) ``openstack baremetal`` CLI --------------------------- The ``openstack baremetal`` command line interface is available when the bare metal plugin (included in this package) is used with the `OpenStackClient `_. There are two ways to install the OpenStackClient (python-openstackclient) package: * along with this python-ironicclient package:: # pip install python-ironicclient[cli] * directly:: # pip install python-openstackclient An example of creating a basic node with the ``ipmi`` driver:: $ openstack baremetal node create --driver ipmi An example of creating a port on a node:: $ openstack baremetal port create --node AA:BB:CC:DD:EE:FF An example of updating driver properties for a node:: $ openstack baremetal node set --driver-info ipmi_address= For more information about the ``openstack baremetal`` command and the subcommands available, run:: $ openstack help baremetal ``ironic`` CLI (deprecated) --------------------------- This is deprecated and will be removed in the S* release. Please use the ``openstack baremetal`` CLI instead. This package will install the ``ironic`` command line interface that you can use to interact with the ``ironic`` API. In order to use the ``ironic`` CLI you'll need to provide your OpenStack tenant, username, password and authentication endpoint. You can do this with the ``--os-tenant-name``, ``--os-username``, ``--os-password`` and ``--os-auth-url`` parameters, though it may be easier to set them as environment variables:: $ export OS_PROJECT_NAME=project $ export OS_USERNAME=user $ export OS_PASSWORD=pass $ export OS_AUTH_URL=http://auth.example.com:5000/v2.0 To use a specific Ironic API endpoint:: $ export IRONIC_URL=http://ironic.example.com:6385 An example of creating a basic node with the ``ipmi`` driver:: $ ironic node-create -d ipmi An example of creating a port on a node:: $ ironic port-create -a AA:BB:CC:DD:EE:FF -n nodeUUID An example of updating driver properties for a node:: $ ironic node-update nodeUUID add driver_info/ipmi_address= $ ironic node-update nodeUUID add driver_info/ipmi_username= $ ironic node-update nodeUUID add driver_info/ipmi_password= For more information about the ``ironic`` command and the subcommands available, run:: $ ironic help Useful Links ------------ * Documentation: https://docs.openstack.org/python-ironicclient/latest/ * Source: https://git.openstack.org/cgit/openstack/python-ironicclient * Bugs: https://bugs.launchpad.net/python-ironicclient * Release notes: https://docs.openstack.org/releasenotes/python-ironicclient/ Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 python-ironicclient-2.2.0/python_ironicclient.egg-info/requires.txt0000664000175100017510000000044513232474760025730 0ustar zuulzuul00000000000000pbr!=2.1.0,>=2.0.0 appdirs>=1.3.0 dogpile.cache>=0.6.2 jsonschema<3.0.0,>=2.6.0 keystoneauth1>=3.3.0 osc-lib>=1.8.0 oslo.i18n>=3.15.3 oslo.serialization!=2.19.1,>=2.18.0 oslo.utils>=3.33.0 PrettyTable<0.8,>=0.7.1 PyYAML>=3.10 requests>=2.14.2 six>=1.10.0 [cli] python-openstackclient>=3.12.0 python-ironicclient-2.2.0/python_ironicclient.egg-info/SOURCES.txt0000664000175100017510000003131613232474761025216 0ustar zuulzuul00000000000000.coveragerc .stestr.conf AUTHORS CONTRIBUTING.rst ChangeLog LICENSE README.rst requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/source/api_v1.rst doc/source/conf.py doc/source/index.rst doc/source/cli/index.rst doc/source/cli/ironic_client.rst doc/source/cli/osc_plugin_cli.rst doc/source/cli/osc/v1/index.rst doc/source/contributor/contributing.rst doc/source/contributor/index.rst doc/source/contributor/testing.rst doc/source/user/create_command.rst ironicclient/__init__.py ironicclient/client.py ironicclient/exc.py ironicclient/shell.py ironicclient/common/__init__.py ironicclient/common/base.py ironicclient/common/cliutils.py ironicclient/common/filecache.py ironicclient/common/http.py ironicclient/common/i18n.py ironicclient/common/utils.py ironicclient/common/apiclient/__init__.py ironicclient/common/apiclient/base.py ironicclient/common/apiclient/exceptions.py ironicclient/osc/__init__.py ironicclient/osc/plugin.py ironicclient/osc/v1/__init__.py ironicclient/osc/v1/baremetal_chassis.py ironicclient/osc/v1/baremetal_create.py ironicclient/osc/v1/baremetal_driver.py ironicclient/osc/v1/baremetal_node.py ironicclient/osc/v1/baremetal_port.py ironicclient/osc/v1/baremetal_portgroup.py ironicclient/osc/v1/baremetal_volume_connector.py ironicclient/osc/v1/baremetal_volume_target.py ironicclient/tests/__init__.py ironicclient/tests/functional/__init__.py ironicclient/tests/functional/base.py ironicclient/tests/functional/test_chassis.py ironicclient/tests/functional/test_chassis_create.py ironicclient/tests/functional/test_driver.py ironicclient/tests/functional/test_help_msg.py ironicclient/tests/functional/test_json_response.py ironicclient/tests/functional/test_node.py ironicclient/tests/functional/test_node_set_power_state.py ironicclient/tests/functional/test_port.py ironicclient/tests/functional/test_portgroup.py ironicclient/tests/functional/test_table_structure.py ironicclient/tests/functional/utils.py ironicclient/tests/functional/hooks/post_test_hook.sh ironicclient/tests/functional/osc/__init__.py ironicclient/tests/functional/osc/v1/__init__.py ironicclient/tests/functional/osc/v1/base.py ironicclient/tests/functional/osc/v1/test_baremetal_chassis_basic.py ironicclient/tests/functional/osc/v1/test_baremetal_driver_basic.py ironicclient/tests/functional/osc/v1/test_baremetal_node_basic.py ironicclient/tests/functional/osc/v1/test_baremetal_node_create_negative.py ironicclient/tests/functional/osc/v1/test_baremetal_node_fields.py ironicclient/tests/functional/osc/v1/test_baremetal_node_negative.py ironicclient/tests/functional/osc/v1/test_baremetal_node_power_states.py ironicclient/tests/functional/osc/v1/test_baremetal_node_provision_states.py ironicclient/tests/functional/osc/v1/test_baremetal_port_basic.py ironicclient/tests/functional/osc/v1/test_baremetal_portgroup_basic.py ironicclient/tests/unit/__init__.py ironicclient/tests/unit/test_client.py ironicclient/tests/unit/test_exc.py ironicclient/tests/unit/test_import.py ironicclient/tests/unit/test_shell.py ironicclient/tests/unit/utils.py ironicclient/tests/unit/common/__init__.py ironicclient/tests/unit/common/test_base.py ironicclient/tests/unit/common/test_cliutils.py ironicclient/tests/unit/common/test_filecache.py ironicclient/tests/unit/common/test_http.py ironicclient/tests/unit/common/test_utils.py ironicclient/tests/unit/common/apiclient/__init__.py ironicclient/tests/unit/common/apiclient/test_base.py ironicclient/tests/unit/common/apiclient/test_exceptions.py ironicclient/tests/unit/osc/__init__.py ironicclient/tests/unit/osc/fakes.py ironicclient/tests/unit/osc/test_plugin.py ironicclient/tests/unit/osc/v1/__init__.py ironicclient/tests/unit/osc/v1/fakes.py ironicclient/tests/unit/osc/v1/test_baremetal_chassis.py ironicclient/tests/unit/osc/v1/test_baremetal_create.py ironicclient/tests/unit/osc/v1/test_baremetal_driver.py ironicclient/tests/unit/osc/v1/test_baremetal_node.py ironicclient/tests/unit/osc/v1/test_baremetal_port.py ironicclient/tests/unit/osc/v1/test_baremetal_portgroup.py ironicclient/tests/unit/osc/v1/test_baremetal_volume_connector.py ironicclient/tests/unit/osc/v1/test_baremetal_volume_target.py ironicclient/tests/unit/v1/__init__.py ironicclient/tests/unit/v1/test_chassis.py ironicclient/tests/unit/v1/test_chassis_shell.py ironicclient/tests/unit/v1/test_client.py ironicclient/tests/unit/v1/test_create_resources.py ironicclient/tests/unit/v1/test_create_resources_shell.py ironicclient/tests/unit/v1/test_driver.py ironicclient/tests/unit/v1/test_driver_shell.py ironicclient/tests/unit/v1/test_node.py ironicclient/tests/unit/v1/test_node_shell.py ironicclient/tests/unit/v1/test_port.py ironicclient/tests/unit/v1/test_port_shell.py ironicclient/tests/unit/v1/test_portgroup.py ironicclient/tests/unit/v1/test_portgroup_shell.py ironicclient/tests/unit/v1/test_resource_fields.py ironicclient/tests/unit/v1/test_volume_connector.py ironicclient/tests/unit/v1/test_volume_connector_shell.py ironicclient/tests/unit/v1/test_volume_target.py ironicclient/tests/unit/v1/test_volume_target_shell.py ironicclient/v1/__init__.py ironicclient/v1/chassis.py ironicclient/v1/chassis_shell.py ironicclient/v1/client.py ironicclient/v1/create_resources.py ironicclient/v1/create_resources_shell.py ironicclient/v1/driver.py ironicclient/v1/driver_shell.py ironicclient/v1/node.py ironicclient/v1/node_shell.py ironicclient/v1/port.py ironicclient/v1/port_shell.py ironicclient/v1/portgroup.py ironicclient/v1/portgroup_shell.py ironicclient/v1/resource_fields.py ironicclient/v1/shell.py ironicclient/v1/utils.py ironicclient/v1/volume_connector.py ironicclient/v1/volume_connector_shell.py ironicclient/v1/volume_target.py ironicclient/v1/volume_target_shell.py playbooks/legacy/ironicclient-dsvm-functional/post.yaml playbooks/legacy/ironicclient-dsvm-functional/run.yaml playbooks/legacy/ironicclient-tempest-dsvm-src/post.yaml playbooks/legacy/ironicclient-tempest-dsvm-src/run.yaml python_ironicclient.egg-info/PKG-INFO python_ironicclient.egg-info/SOURCES.txt python_ironicclient.egg-info/dependency_links.txt python_ironicclient.egg-info/entry_points.txt python_ironicclient.egg-info/not-zip-safe python_ironicclient.egg-info/pbr.json python_ironicclient.egg-info/requires.txt python_ironicclient.egg-info/top_level.txt releasenotes/notes/.placeholder releasenotes/notes/SHA1-hash-auth-token-f8dce46f854c002c.yaml releasenotes/notes/add-chassis_uuid-removal-possibility-5bc0bc3a7953eaa5.yaml releasenotes/notes/add-create-command-3df5efbbecc33276.yaml releasenotes/notes/add-environment-variable-to-specify-version-cache-timeout-dfa5f6d4af0ea1d3.yaml releasenotes/notes/add-json-option-0cf29be2a97e0212.yaml releasenotes/notes/add-neutron-integration-fields-cee7596c49722de6.yaml releasenotes/notes/add-node-resource-class-6040d1d6c734522c.yaml releasenotes/notes/add-pecan-exc-construction-a776408f7ae110dc.yaml releasenotes/notes/add-port-internal-info-74a03ebd8b0a3dfc.yaml releasenotes/notes/add-portgroup-mode-properties-0a3023cf905adaef.yaml releasenotes/notes/add-portgroups-support-c3cf3826093ee815.yaml releasenotes/notes/add-portgroups-to-create-command-6d685277f7af79df.yaml releasenotes/notes/add-vif-attach-detach-support-e680d64e4add0fa4.yaml releasenotes/notes/add-volume-connector-api-873090474d5e41b8.yaml releasenotes/notes/add-volume-connector-cli-873090474d5e41b9.yaml releasenotes/notes/add-volume-target-api-e062303f4b3b40ef.yaml releasenotes/notes/add-volume-target-cli-e062303f4b3b40f0.yaml releasenotes/notes/add_api_versions-a59e5b6899833c33.yaml releasenotes/notes/allow-api-user-to-use-latest-6b80e9f584eaaa4e.yaml releasenotes/notes/allow-client-to-request-list-of-versions-88f019cad76e6464.yaml releasenotes/notes/bug-1524745-adds-node-create-args-a7ace744515e5943.yaml releasenotes/notes/bug-1524745-extend-driver-list-and-driver-show-800d96393aa17342.yaml releasenotes/notes/bug-1524745-update-baremetal-node-set-c1ac57de0d481efe.yaml releasenotes/notes/bug-1712935-allow-os_baremetal_api_version_env_var_to_be_latest-28c8eed24f389673.yaml releasenotes/notes/bug-1724974-add-wanboot-to-supported-boot-devices.yaml releasenotes/notes/bug-1745099-allow-integer-portgroup-mode-6be4d3b35e216486.yaml releasenotes/notes/continue-del-next-node-8827e67e1c41a0a5.yaml releasenotes/notes/deprecate-ironic-cli-686b7a238ddf3e25.yaml releasenotes/notes/display-empty-string-for-chassis-uuid-if-it-is-empty-a5471c3aa740a27d.yaml releasenotes/notes/driver-properties-for-osc-07a99d2d4e166436.yaml releasenotes/notes/extend-vif-attach-commands-ef3a931413ddcee7.yaml releasenotes/notes/feature-parity-osc-cli-7606eed15f1c124f.yaml releasenotes/notes/fix-python3-compatibility-993ace45fefcba34.yaml releasenotes/notes/fix-token-with-vhosts-5d0a6d53e807fa5e.yaml releasenotes/notes/implicit-version-warning-d34b99727b50d519.yaml releasenotes/notes/implicit-version-warning-old-cli-fe34d423ae63544a.yaml releasenotes/notes/index-error-no-endpoint-eb281187f80a9aa4.yaml releasenotes/notes/instance-crash-dump-d845a31e72b5a9f7.yaml releasenotes/notes/ironic-cli-version-a5cdec73d585444d.yaml releasenotes/notes/ironic-create-files-fix-c31e40e566ff86b8.yaml releasenotes/notes/keystone-token-auth-661a0c0d53c1b4de.yaml releasenotes/notes/latest-baremetal-api-version-a20e3099e3b97a1b.yaml releasenotes/notes/latest-default-41fdcc49701c4d70.yaml releasenotes/notes/latest-renegotiation-55daa01b3fc261be.yaml releasenotes/notes/list-nodes-by-driver-b1e1e1018077089b.yaml releasenotes/notes/manual-clean-09f6b49df7d2513f.yaml releasenotes/notes/negative-wrap-fix-4197e91b2ecfb722.yaml releasenotes/notes/no-osc-requirement-411f25fd10f18caa.yaml releasenotes/notes/no-resource-attributeerror-d0cb327abab7dcc0.yaml releasenotes/notes/node-driver-support-storage-interface-e93fc8d4de5d24d6.yaml releasenotes/notes/not-ignore-delete-failtures-0783d33a606ed6f1.yaml releasenotes/notes/osc-baremetal-driver-raid-properties-159bd57058c0fc0e.yaml releasenotes/notes/osc-commands-1-7-d531960472a11ac2.yaml releasenotes/notes/osc-default-microver-172d6e69316e70c1.yaml releasenotes/notes/osc-instance-crash-dump-22634a57104561a5.yaml releasenotes/notes/osc-max-microver-22-dc0d91a62f03a2e6.yaml releasenotes/notes/osc-node-list-chassis-091d080684cdccf8.yaml releasenotes/notes/osc-node-list-no-maintenance-ff1cef7cfbe60fb9.yaml releasenotes/notes/osc-node-list-option-driver-a2901ba6b4e1d3b5.yaml releasenotes/notes/osc-node-list-provisionstate-cd98dbddaad93e96.yaml releasenotes/notes/osc-node-list-unassociated-60e46958a0abc3e5.yaml releasenotes/notes/osc-node-power-on-off-c269980e3b9c79ca.yaml releasenotes/notes/osc-node-rebuild-configdrive-8979d5b1373e8d5f.yaml releasenotes/notes/osc-node-set-chassis-aae3413489b66b9b.yaml releasenotes/notes/osc-plugin-9b5344aceb886cc1.yaml releasenotes/notes/osc-plugin-chassis-create-show-fix-ee276d707c5a5bdf.yaml releasenotes/notes/osc-plugin-f87e0fbb472261dd.yaml releasenotes/notes/osc-plugin-ff0d897d8441a9e1.yaml releasenotes/notes/osc-plugin-node-create-show-fix-283148c86fbccce2.yaml releasenotes/notes/osc-plugin-node-set-target-raid-config-5d538d6253902ecb.yaml releasenotes/notes/osc-plugin-set-unset-target-raid-config-9a1cecb5620eafda.yaml releasenotes/notes/osc-port-create-uuid-5da551b154540ef7.yaml releasenotes/notes/osc-port-set-llc-pxeenabled-21fd8ea1982af17e.yaml releasenotes/notes/osc-soft-reboot-poweroff-121b8043567f54a9.yaml releasenotes/notes/osc-versioned-endpoint-fix-08f6b7af2f47a5d6.yaml releasenotes/notes/osc-wait-option-for-provisioning-commands-b6f5b875d573c9c8.yaml releasenotes/notes/port-physical-network-6ea8860d773e473c.yaml releasenotes/notes/prelude-2-0-release-ee44150902d3d399.yaml releasenotes/notes/provision-state-adopt-d07b838813cecfb1.yaml releasenotes/notes/provision-state-wait-e7ff919ce8e13703.yaml releasenotes/notes/raid_CLI_support-7e816ccd0fb31d2b.yaml releasenotes/notes/remove-deprecated-osc-cmd-6dc980299d2fbde4.yaml releasenotes/notes/remove-llc-short-arg-89c7443acc6c54a4.yaml releasenotes/notes/remove-states-field-0242960d121a09a7.yaml releasenotes/notes/retry-on-keystone-auth-retriable-failures-91c08b9f8bdab7f3.yaml releasenotes/notes/session-client-endpoint-override-20f1d822b4430afa.yaml releasenotes/notes/show-required-arguments-in-help-commands-of-node-create-port-create-b213bb28bcc94743.yaml releasenotes/notes/soft-reboot-poweroff-e33d078a05db3894.yaml releasenotes/notes/start-using-reno-ccd220efa2c7022a.yaml releasenotes/notes/switch-requests-8304d4465a8976b1.yaml releasenotes/notes/traits-support-8864f6816abecdb2.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/mitaka.rst releasenotes/source/newton.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder tools/__init__.py tools/install_venv_common.py tools/ironic.bash_completion tools/run_functional.sh tools/with_venv.sh zuul.d/legacy-ironicclient-jobs.yaml zuul.d/project.yamlpython-ironicclient-2.2.0/python_ironicclient.egg-info/dependency_links.txt0000664000175100017510000000000113232474760027373 0ustar zuulzuul00000000000000 python-ironicclient-2.2.0/python_ironicclient.egg-info/top_level.txt0000664000175100017510000000001513232474760026053 0ustar zuulzuul00000000000000ironicclient python-ironicclient-2.2.0/python_ironicclient.egg-info/entry_points.txt0000664000175100017510000001461013232474760026625 0ustar zuulzuul00000000000000[console_scripts] ironic = ironicclient.shell:main [openstack.baremetal.v1] baremetal_chassis_create = ironicclient.osc.v1.baremetal_chassis:CreateBaremetalChassis baremetal_chassis_delete = ironicclient.osc.v1.baremetal_chassis:DeleteBaremetalChassis baremetal_chassis_list = ironicclient.osc.v1.baremetal_chassis:ListBaremetalChassis baremetal_chassis_set = ironicclient.osc.v1.baremetal_chassis:SetBaremetalChassis baremetal_chassis_show = ironicclient.osc.v1.baremetal_chassis:ShowBaremetalChassis baremetal_chassis_unset = ironicclient.osc.v1.baremetal_chassis:UnsetBaremetalChassis baremetal_create = ironicclient.osc.v1.baremetal_create:CreateBaremetal baremetal_driver_list = ironicclient.osc.v1.baremetal_driver:ListBaremetalDriver baremetal_driver_passthru_call = ironicclient.osc.v1.baremetal_driver:PassthruCallBaremetalDriver baremetal_driver_passthru_list = ironicclient.osc.v1.baremetal_driver:PassthruListBaremetalDriver baremetal_driver_property_list = ironicclient.osc.v1.baremetal_driver:ListBaremetalDriverProperty baremetal_driver_raid_property_list = ironicclient.osc.v1.baremetal_driver:ListBaremetalDriverRaidProperty baremetal_driver_show = ironicclient.osc.v1.baremetal_driver:ShowBaremetalDriver baremetal_node_abort = ironicclient.osc.v1.baremetal_node:AbortBaremetalNode baremetal_node_add_trait = ironicclient.osc.v1.baremetal_node:AddTraitBaremetalNode baremetal_node_adopt = ironicclient.osc.v1.baremetal_node:AdoptBaremetalNode baremetal_node_boot_device_set = ironicclient.osc.v1.baremetal_node:BootdeviceSetBaremetalNode baremetal_node_boot_device_show = ironicclient.osc.v1.baremetal_node:BootdeviceShowBaremetalNode baremetal_node_clean = ironicclient.osc.v1.baremetal_node:CleanBaremetalNode baremetal_node_console_disable = ironicclient.osc.v1.baremetal_node:ConsoleDisableBaremetalNode baremetal_node_console_enable = ironicclient.osc.v1.baremetal_node:ConsoleEnableBaremetalNode baremetal_node_console_show = ironicclient.osc.v1.baremetal_node:ConsoleShowBaremetalNode baremetal_node_create = ironicclient.osc.v1.baremetal_node:CreateBaremetalNode baremetal_node_delete = ironicclient.osc.v1.baremetal_node:DeleteBaremetalNode baremetal_node_deploy = ironicclient.osc.v1.baremetal_node:DeployBaremetalNode baremetal_node_inject_nmi = ironicclient.osc.v1.baremetal_node:InjectNmiBaremetalNode baremetal_node_inspect = ironicclient.osc.v1.baremetal_node:InspectBaremetalNode baremetal_node_list = ironicclient.osc.v1.baremetal_node:ListBaremetalNode baremetal_node_maintenance_set = ironicclient.osc.v1.baremetal_node:MaintenanceSetBaremetalNode baremetal_node_maintenance_unset = ironicclient.osc.v1.baremetal_node:MaintenanceUnsetBaremetalNode baremetal_node_manage = ironicclient.osc.v1.baremetal_node:ManageBaremetalNode baremetal_node_passthru_call = ironicclient.osc.v1.baremetal_node:PassthruCallBaremetalNode baremetal_node_passthru_list = ironicclient.osc.v1.baremetal_node:PassthruListBaremetalNode baremetal_node_power_off = ironicclient.osc.v1.baremetal_node:PowerOffBaremetalNode baremetal_node_power_on = ironicclient.osc.v1.baremetal_node:PowerOnBaremetalNode baremetal_node_provide = ironicclient.osc.v1.baremetal_node:ProvideBaremetalNode baremetal_node_reboot = ironicclient.osc.v1.baremetal_node:RebootBaremetalNode baremetal_node_rebuild = ironicclient.osc.v1.baremetal_node:RebuildBaremetalNode baremetal_node_remove_trait = ironicclient.osc.v1.baremetal_node:RemoveTraitBaremetalNode baremetal_node_set = ironicclient.osc.v1.baremetal_node:SetBaremetalNode baremetal_node_show = ironicclient.osc.v1.baremetal_node:ShowBaremetalNode baremetal_node_trait_list = ironicclient.osc.v1.baremetal_node:ListTraitsBaremetalNode baremetal_node_undeploy = ironicclient.osc.v1.baremetal_node:UndeployBaremetalNode baremetal_node_unset = ironicclient.osc.v1.baremetal_node:UnsetBaremetalNode baremetal_node_validate = ironicclient.osc.v1.baremetal_node:ValidateBaremetalNode baremetal_node_vif_attach = ironicclient.osc.v1.baremetal_node:VifAttachBaremetalNode baremetal_node_vif_detach = ironicclient.osc.v1.baremetal_node:VifDetachBaremetalNode baremetal_node_vif_list = ironicclient.osc.v1.baremetal_node:VifListBaremetalNode baremetal_port_create = ironicclient.osc.v1.baremetal_port:CreateBaremetalPort baremetal_port_delete = ironicclient.osc.v1.baremetal_port:DeleteBaremetalPort baremetal_port_group_create = ironicclient.osc.v1.baremetal_portgroup:CreateBaremetalPortGroup baremetal_port_group_delete = ironicclient.osc.v1.baremetal_portgroup:DeleteBaremetalPortGroup baremetal_port_group_list = ironicclient.osc.v1.baremetal_portgroup:ListBaremetalPortGroup baremetal_port_group_set = ironicclient.osc.v1.baremetal_portgroup:SetBaremetalPortGroup baremetal_port_group_show = ironicclient.osc.v1.baremetal_portgroup:ShowBaremetalPortGroup baremetal_port_group_unset = ironicclient.osc.v1.baremetal_portgroup:UnsetBaremetalPortGroup baremetal_port_list = ironicclient.osc.v1.baremetal_port:ListBaremetalPort baremetal_port_set = ironicclient.osc.v1.baremetal_port:SetBaremetalPort baremetal_port_show = ironicclient.osc.v1.baremetal_port:ShowBaremetalPort baremetal_port_unset = ironicclient.osc.v1.baremetal_port:UnsetBaremetalPort baremetal_volume_connector_create = ironicclient.osc.v1.baremetal_volume_connector:CreateBaremetalVolumeConnector baremetal_volume_connector_delete = ironicclient.osc.v1.baremetal_volume_connector:DeleteBaremetalVolumeConnector baremetal_volume_connector_list = ironicclient.osc.v1.baremetal_volume_connector:ListBaremetalVolumeConnector baremetal_volume_connector_set = ironicclient.osc.v1.baremetal_volume_connector:SetBaremetalVolumeConnector baremetal_volume_connector_show = ironicclient.osc.v1.baremetal_volume_connector:ShowBaremetalVolumeConnector baremetal_volume_connector_unset = ironicclient.osc.v1.baremetal_volume_connector:UnsetBaremetalVolumeConnector baremetal_volume_target_create = ironicclient.osc.v1.baremetal_volume_target:CreateBaremetalVolumeTarget baremetal_volume_target_delete = ironicclient.osc.v1.baremetal_volume_target:DeleteBaremetalVolumeTarget baremetal_volume_target_list = ironicclient.osc.v1.baremetal_volume_target:ListBaremetalVolumeTarget baremetal_volume_target_set = ironicclient.osc.v1.baremetal_volume_target:SetBaremetalVolumeTarget baremetal_volume_target_show = ironicclient.osc.v1.baremetal_volume_target:ShowBaremetalVolumeTarget baremetal_volume_target_unset = ironicclient.osc.v1.baremetal_volume_target:UnsetBaremetalVolumeTarget [openstack.cli.extension] baremetal = ironicclient.osc.plugin python-ironicclient-2.2.0/python_ironicclient.egg-info/pbr.json0000664000175100017510000000005613232474760025004 0ustar zuulzuul00000000000000{"git_version": "683b7c6", "is_release": true}python-ironicclient-2.2.0/PKG-INFO0000664000175100017510000001345413232474761016655 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: python-ironicclient Version: 2.2.0 Summary: OpenStack Bare Metal Provisioning API Client Library Home-page: https://docs.openstack.org/python-ironicclient/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description-Content-Type: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-ironicclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Python bindings for the Ironic API ================================== This is a client for the OpenStack `Ironic `_ API. It provides: * a Python API: the ``ironicclient`` module, and * two command-line interfaces: ``openstack baremetal`` and ``ironic`` (deprecated, please use ``openstack baremetal``). Development takes place via the usual OpenStack processes as outlined in the `developer guide `_. The master repository is on `git.openstack.org `_. ``python-ironicclient`` is licensed under the Apache License, Version 2.0, like the rest of OpenStack. .. contents:: Contents: :local: Python API ---------- Quick-start Example:: >>> from ironicclient import client >>> >>> kwargs = {'os_auth_token': '3bcc3d3a03f44e3d8377f9247b0ad155', >>> 'ironic_url': 'http://ironic.example.org:6385/'} >>> ironic = client.get_client(1, **kwargs) ``openstack baremetal`` CLI --------------------------- The ``openstack baremetal`` command line interface is available when the bare metal plugin (included in this package) is used with the `OpenStackClient `_. There are two ways to install the OpenStackClient (python-openstackclient) package: * along with this python-ironicclient package:: # pip install python-ironicclient[cli] * directly:: # pip install python-openstackclient An example of creating a basic node with the ``ipmi`` driver:: $ openstack baremetal node create --driver ipmi An example of creating a port on a node:: $ openstack baremetal port create --node AA:BB:CC:DD:EE:FF An example of updating driver properties for a node:: $ openstack baremetal node set --driver-info ipmi_address= For more information about the ``openstack baremetal`` command and the subcommands available, run:: $ openstack help baremetal ``ironic`` CLI (deprecated) --------------------------- This is deprecated and will be removed in the S* release. Please use the ``openstack baremetal`` CLI instead. This package will install the ``ironic`` command line interface that you can use to interact with the ``ironic`` API. In order to use the ``ironic`` CLI you'll need to provide your OpenStack tenant, username, password and authentication endpoint. You can do this with the ``--os-tenant-name``, ``--os-username``, ``--os-password`` and ``--os-auth-url`` parameters, though it may be easier to set them as environment variables:: $ export OS_PROJECT_NAME=project $ export OS_USERNAME=user $ export OS_PASSWORD=pass $ export OS_AUTH_URL=http://auth.example.com:5000/v2.0 To use a specific Ironic API endpoint:: $ export IRONIC_URL=http://ironic.example.com:6385 An example of creating a basic node with the ``ipmi`` driver:: $ ironic node-create -d ipmi An example of creating a port on a node:: $ ironic port-create -a AA:BB:CC:DD:EE:FF -n nodeUUID An example of updating driver properties for a node:: $ ironic node-update nodeUUID add driver_info/ipmi_address= $ ironic node-update nodeUUID add driver_info/ipmi_username= $ ironic node-update nodeUUID add driver_info/ipmi_password= For more information about the ``ironic`` command and the subcommands available, run:: $ ironic help Useful Links ------------ * Documentation: https://docs.openstack.org/python-ironicclient/latest/ * Source: https://git.openstack.org/cgit/openstack/python-ironicclient * Bugs: https://bugs.launchpad.net/python-ironicclient * Release notes: https://docs.openstack.org/releasenotes/python-ironicclient/ Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 python-ironicclient-2.2.0/CONTRIBUTING.rst0000666000175100017510000000051713232474343020213 0ustar zuulzuul00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps documented at: https://docs.openstack.org/infra/manual/developers.html More information on contributing can be found within the project documentation: https://docs.openstack.org/python-ironicclient/latest/contributor/contributing.html python-ironicclient-2.2.0/ChangeLog0000664000175100017510000007375513232474760017343 0ustar zuulzuul00000000000000CHANGES ======= 2.2.0 ----- * Add release note for fix to bug 1745099 * Traits support * Can not set portgroup mode as a number * Updated from global requirements * Allow API user to define list of versions * Facilitate latest Rest API use * Updated from global requirements * Updated from global requirements * Updated from global requirements * Use StrictVersion to compare versions * Accept port and portgroup as volume connector types * Ignore .eggs from git 2.1.0 ----- * Updated from global requirements * Accept None as a result of node validation in functional test * Use the tempest plugin from openstack/ironic-tempest-plugin * Remove RBD examples * Use assertRegex instead of assertRegexpMatches * Updated from global requirements * Avoid tox\_install.sh for constraints support * Remove setting of version/release from releasenotes * Updated from global requirements * Updated from global requirements * osc node power on & off commands * zuul: centralize 'irrelevant-files' list * Updated from global requirements 2.0.0 ----- * Update release notes * [reno] Prelude for release 2.0 * Move legacy ironicclient jobs in-tree * Mock filecache.CACHE in unit tests * Add ability to provide configdrive when rebuilding with OSC * Pass missing arguments to session in SessionClient.\_make\_session\_request * Switch the deprecated "ironic" CLI to "latest" API version by default * Make functional tests on JSON output debugable * Set the default API version of OSC CLI to "latest" * Synchronize ironic and ironicclients list of boot devices * Allow re-negotiation of the latest version supplied by CLI * Clean up the release notes * Use generic user for both zuul v2 and v3 * Deprecate the ironic CLI * Do not use urljoin in base http client * Replace testr with stestr * Update documentation * Update README * Updated from global requirements * Cleanup test-requirements * Do not depend on python-openstackclient * Updated from global requirements * Updated from global requirements * Remove deprecated OSC baremetal commands * flake8: Enable some off-by-default checks * Updated from global requirements * Fix to use "." to source script files * tox.ini: Add 'py36' to the default envlist * Allow OS\_BAREMETAL\_API\_VERSION=latest to work * Add auto-generated CLI reference * Updated from global requirements * Add test for set/unset node target\_raid\_config * Skip warning when changing target\_raid\_config * Update the documentation link for doc migration * Updated from global requirements * Add basic tests for OSC plugin baremetal driver commands * Pass os\_identity\_api\_version into functional tests * Update reno for stable/pike 1.16.0 ------ * Turn on warning-is-error * Follow up to the API version warning patches * Updated from global requirements * Log warning when API version is not specified for the ironic tool 1.15.0 ------ * Update volume release notes to fix reno * Add physical network to port commands * Update and optimize documentation links * Updated from global requirements * Follow up for OSC volume target commands * Add Ironic CLI commands for volume target * Rearrange existing documentation to fit the new standard layout * Updated from global requirements * Follow-up release note revision * Add Ironic CLI commands for volume connector * Add support for storage\_interface to node and driver CLI * Add OSC commands for volume target * Follow up for OSC volume connector commands * Remove useless variables assignment in unit test * Fixed wrap from taking negative values * Replace http with https * Fix unit tests for volume connector and target * Add OSC commands for volume connector 1.14.0 ------ * Fix over-indent in \_validate\_obj() functions * Add volume target support to Python API * Add volume connector support to Python API * switch from oslosphinx to openstackdocstheme * reno: feature parity between ironic & OSC * Updated from global requirements * Log warning when API version is not specified for the OSC plugin * Improve help text for --local-link-connection * Update releasenote for osc-port-set * Add options for osc 'port set' command * Updated from global requirements * Add OSC 'baremetal driver raid property list' cmd * Add OSC 'baremetal driver property list' command * Create port with specific port group UUID in OSC * Adds --driver option to OSC "node list" command * Add --uuid option to OSC "port create" cmd * Remove support for py34 * Updated from global requirements * Add basic tests for OSC plugin baremetal chassis commands * Updated from global requirements * Replace assertRaisesRegexp with assertRaisesRegex * Updated from global requirements * Updated from global requirements 1.13.0 ------ * Updated from global requirements * Add usage documentation for Baremetal OSC Plugin * Updated from global requirements * Extends driver-list, driver-show supporting new hardware types * Updated from global requirements * Deduplicate method in OSC functional tests * Remove log translations 1.12.0 ------ * Update OSC baremetal node set/unset supporting dynamic drivers * Add missing 'autospec' statements to unit test mocks * Add space between items in exception message * OSC 'node list' recognizes all provision states * Add DRIVER\_RESOURCE to remove duplicated strings * Add tests for node list and show with specific fields * Add negative tests for baremetal node commands * Updated from global requirements * Updated from global requirements * Update test requirement * Updated from global requirements * Add testcases for OSC baremetal port group commands * Remove 'states' field from OSC CLI output * Updated from global requirements * Print pecan exceptions properly * Add basic tests for OSC plugin baremetal port commands * Add VIFs commands to help test * Handle log message interpolation by the logger * Support i18n for baremetal driver, portgroup cmds * Do not show chassis\_uuid field when it is not specified * Add test to create a port with specific port group UUID * Fix cleanup of resources in OSC plugin functional tests * Fix cleanup of resources in functional tests * Change tenant to project in docs and unit tests * Support i18n for baremetal node cmds * Use same variable for --[no-]maintenance * Add args to CLI 'node-create' for selecting hardware interfaces * Update reno for stable/ocata 1.11.0 ------ * Support --os-baremetal-api-version latest * Allow creating portgroups via create commands * Fix help message for the node-vif-attach command * Functional tests for port groups in ironicclient * Add --wait to OSC provisioning commands * Add --no-maintenance to OSC 'baremetal node list' * Follow up nits in the patch "ironic node-inject-nmi" * Updated from global requirements * Fix node-inject-nmi to pass an empty body * Add negative test-cases for openstack node create command * Typo fix: prefered => preferred * Fix ImportError when providing a meaningless API version * Add a new OSC command for Inject NMI 1.10.0 ------ * Support soft reboot and soft power off with timeout for OSC * Add soft reboot/poweroff power states * Change os\_tenant\_name to os\_project\_name in tests configuration script * Support i18n for baremetal chassis cmds * Support i18n for baremetal port cmds * Updated from global requirements * Replace yaml.load() with yaml.safe\_load() * Add a new command "ironic node-inject-nmi" * Extend VIF attach commands 1.9.0 ----- * Add interface attach/detach support * Update tox envs list * Fix multiple ports deletion * Raise on NodeManager get when invalid identifier provided * [trivial] Fix underline length under title in doc * [trivial] Fix of apostrophe in tox.ini * Simplify heading capitalization * Add mode and properties to portgroup OSC plugin * Add mode and properties to portgroup * Log warning if no property for (Un)Set commands * Pass argument as params in test\_port\_update * Add portgroup support to osc plugin * Simplify heading capitalization in shell's HelpFormatter * Describe possible exception in docstring * Fix API object representation in unittests * Verify JSON response of chassis commands * Add python API and CLI for port groups * Use oslo\_serialization.base64 to follow OpenStack Python3 * Updated from global requirements * Fix exception message creation in get\_client() * Describe possible exception in docstring * Add more tests to node\_shell * Updated from global requirements * Use identity api version 3 for OSC plugin tests * Strip endpoint version in OSC plugin * Update release note for fix to required args * Show team and repo badges on README * Add warning message for baremetal node set/unset commands * Allow import more than one func from i18n * Add sanity tests for baremetal power state commands * List required arguments in '--help' message in Ironic Client * Replace six.iteritems() with .items() * Add unit tests for OSC plugin * Use uuidutils instead of uuid.uuid4() * Add tests for maintenance mode commands * Add tests for provision state commands * Add test for ironic port-list command * OSC add capability to remove node/chassis\_uuid * Update to hacking 0.12.0 and use new checks * Updated from global requirements 1.8.0 ----- * Fix python3 compatibility when HTTP Error are returned * Add basic tests for OSC plugin baremetal node commands * Updated from global requirements * Avoid string interpolation in logging calls * Updated from global requirements * Use function import\_versioned\_module from oslo.utils * Updated from global requirements * Fixed json response func tests * Fix display of chassis UUID field if empty * Extend OSC "node list" cmd to fetch nodes without instance UUID * [trivial] Remove unused variables assignment * Updated from global requirements * Add plug-in summary for osc doc * Updated from global requirements * Set OSC default baremetal api version as in ironicclient * Fix import of ironicclient and reformat docstring * Update .gitignore to ignore .idea of PyCharm * Enable release notes translation * Add docs for create command * Refactor provision state so all actions can use inherited take\_action * If no resource, don't call Resource.to\_dict() * Updated from global requirements * Add prefix "$" for command examples * TrivialFix Remove white space between print and () * Use ConfigParser instead of SafeConfigParser in Python 3 * Hide 'nodes' field from chassis OSC subcommands output * Hide 'ports' field from node OSC subcommands output * 'ironic create' handles file args * Adds --chassis-uuid to osc 'baremetal node set' * standardize release note page names and ordering * Document updating nested node attributes with CLI * OSC plugin support microversions 1.21 & 1.22 * Update reno for stable/newton * Include jsonschema only once in requirements * use utils.key\_value\_pairs\_to\_dict() * Move duplicated info to new v1/utils.py 1.7.0 ----- * Correct a couple small grammar things in release notes * Clean up release notes for 1.7 * Change 'P' to 'Queens' and add deprecation date * Add openstack baremetal driver commands * Don't write python bytecode while testing * Add node validate OSC command * Add --chassis to 'openstack baremetal node list' * Sync tools/tox\_install.sh * Updated from global requirements * Using assertIsNone() is preferred over assertEqual() * Add --node to 'openstack baremetal port list' * Adds node boot device & passthru OSC commands * Set/unset node's target RAID config via OSC command * Add baremetal port list command to OSC plugin * Updated from global requirements * Add baremetal port delete command to OSC plugin * Add openstack baremetal chassis commands * Adds 'openstack baremetal node console' commands * Use osc-lib instead of openstackclient * Use osc\_lib instead of cliff * Update hacking test-requirement * Add docs target to tox.ini * Add --wait flag for provision actions and wait\_for\_provision\_state function * Use constraints for all the things * Trivial: Fix doc string for class DeleteBaremetalNode * Add key\_value\_pairs\_to\_dict() method * Updated from global requirements * Add 'openstack baremetal node adopt' command * Update help shown for node-delete * Add baremetal port set command to OSC plugin * Fail with more meaningful error while creating client * Deprecate -l option for port creation in OSC plugin * Trivial: remove redundant parentheses * Remove unused variables assignments in OSC plugin unit tests * Fix i18n problems in shell.py * Add create command to ironic client * Updated from global requirements * Fix uuid to UUID in expected error messages * Trivial: Remove useless spaces * Verify JSON response of driver commands * Make shell main() specify return value in exit code * Add baremetal port unset command to OSC plugin 1.6.0 ----- * Add support for node.resource\_class * Fix coverage target * Add CLI tests to check JSON response body * Add baremetal port show command to OSC plugin * Add internal\_info field to port * Remove unused LONG\_FIELDS * Remove discover from test-requirements * Trivial: Fix wrong comment in power state test 1.5.0 ----- * Updates supporting ironic-neutron integration * Add release note link for client release notes * Add Python 3.5 classifier and venv * Fix Quick-start example syntax * Negative tests for testing chassis-create command * Updated from global requirements 1.4.0 ----- * Fix py35 unit tests * Grammatical fixes for cache expiry feature * Change position in assert arguments * Add test for ironic node-list command * Updated from global requirements * Add test for ironic driver-list command * Add env var for version cache timeout * Add invalid attribute names to exception * Simplify use of config\_drive arg in osc * Add maintenance mode commands * Add provision state commands * Fix error returned by ironic --json node-validate * Negative tests for testing actions with Chassis * Updated from global requirements * Add test for 'node-show --field' command * Updated from global requirements * Tests for CLI help message * Move methods to utils.py * Updated from global requirements * Implementation of baremetal power state commands * Implementation of baremetal port create * Updated from global requirements * Add test for chassis-node-list * Fix quotation mark in docstring * Add test for 'port-show --field' command * Add sanity tests for testing actions with Port * Updated from global requirements * Catch RetriableConnectionFailures from KAuth and retry * Updated from global requirements * Bring OSC plugin inline with approved spec * Updated from global requirements * Use name randomizer from tempest * Updated from global requirements * Replace deprecated tempest-lib with tempest * Update the home-page with developer documentation * Trivial: ignore openstack/common in flake8 exclude list * Updated from global requirements * Fix typo in docstring of assertTableHeaders method * Fix for tox 'testenv:cover' command * Updated from global requirements * Updated from global requirements * Fix typos in docstrings and comments * Add test for 'chassis-show --field' command * Client addition for Active Node Creation verb adopt * Fix pep8 stderr warning regarding \_\_all\_\_ defined as list * Code style fix according to flake8 * Set endpoint\_override while doing session.request * Trivial: Fix incorrect comments in test\_baremetal.py * Remove httplib2 * Update reno for stable/mitaka * Updated from global requirements 1.2.0 ----- * Remove leftover use of args.os\_endpoint * Improve output of --json option * Updated from global requirements * Add CLI support for RAID configuration * Updated from global requirements * Use keystoneauth instead of keystoneclient * Don't ignore failures when delete nodes * Use requests lib in HTTPClient * Add a JSON option to the client * Do not pass endpoint to constructor in OSC * Fix Resource.\_\_eq\_\_ mismatch semantics of object equal * Log SHA1 hash of X-Auth-Token value * Stop ignoring \_ as builtin in pep8 * Remove unused \`anyjson\` * Updated from global requirements * Add sanity tests for testing actions with Chassis * Add 'node-set-provision-state clean' * continue to delete next node if failed with previous one * Updated from global requirements * Fix unit test 'Argument parse failed' error * Updated from global requirements * Replace HTTP 'magic numbers' with constants * Add CLI to list nodes using the same driver * Allow functional tests to work with Keystone v3 1.1.0 ----- * Allow to initialize keystone v3 client * Updated from global requirements * Support all API versions up to 1.latest * Put py34 first in the env order of tox * Updated from global requirements * Updated from global requirements * Fix params order in assertEqual * Updated from global requirements * Remove openstack-common.conf * Move ironicclient/common tests to their respective directory * Do not log secrets * Fix test cases of listing with provisioning state * Replace assertTrue with explicit assertIsInstance * Add first reno-based release note * Updated from global requirements * Updated from global requirements * Updated from global requirements * Add reno for release notes management * Add Sanity tests for testing actions with Driver * Replace assertEqual(None, \*) with assertIsNone in tests * Removes MANIFEST.in as it is not needed explicitely by PBR * Switch tox unit test command to use ostestr * Drop py33 support * Add --uuid to port-create * Add --uuid to chassis-create * Deprecated tox -downloadcache option removed * Scale back on how many warnings we issue * Refactoring and removing duplicate code of "base.Manager" heirs without changing the API * Correct node-port-list help info * Fix exceptions.from\_response() parameter * Updated from global requirements * Tests for testing node-set-power-state command * Add --wrap option for "ironic driver-properties" * Fix misprints in docstring * Updated from global requirements * Revert "Refactoring and removing duplicate code of "base.Manager" heirs" * Refactoring and removing duplicate code of "base.Manager" heirs * Updated from global requirements * Add sanity tests for testing actions with Node * Remove httpretty workaround * Use a dict to translate node power states to json data * Use requests-mock instead of httpretty * Updated from global requirements * Add missing translation markers * update node shell help info * replace LOG.warn with LOG.warning * Updated from global requirements 1.0.0 ----- * A minor change for driver\_shell test * Add documentation on how to run tests * Add driver-get-vendor-passthru-methods cmd * Add a new cmd method node-get-vendor-passthru-methods * Add more unit tests for clituils * Drop explicit Python 2.6 support * Make print\_list accept a list of dict * Sync with oslo-incubator * Last sync from oslo-incubator * Use keystoneclient.exception directly * Add simple table structure Ironic CLI tests * Introduce tempest-lib to functional tests 0.10.0 ------ * Updated from global requirements * Make sort keys the same for list commands * Updated from global requirements * Remove lxml requirement * Mock keystone call to avoid test failure * Add more filters for chassis node-list 0.9.0 ----- * Use doc8 style checker * Fix the bug of incorrect spelling * Replace six.iteritems() with .items() * Allow 'abort' verb for node-set-provision-state * Set a default endpoint value of None * Updated from global requirements * Added unit test cases for command-line shell * Introduce openstackclient plugin * Updated from global requirements * Remove unneeded param['detail'] setting 0.8.1 ----- * Fixes file cache TypeError * Fix functional tests job * Replace ConfigParser with six.moves.configparser 0.8.0 ----- * Updated from global requirements * Updated from global requirements * Updated from global requirements * Added unit tests for command-line shell * Also retry on connection refused * Updated from global requirements * Updated from global requirements * Add provision\_updated\_at field to nodes * Filtering nodes by provision state * Replacing dict.iteritems() with dict.items() * Updated from global requirements * Fix version negotiation * Updated from global requirements * Allow specifying a set of fields of the Port and Chasis resources * Revert: requirement files in alphabetical order * Fix unittests due mock 1.1.0 release * Allow specifying a set of fields of the Node resource * Expose node's clean\_step and bump default version * Fix slow tests in RetriesTestCase * Updated from global requirements * Updated from global requirements * Also retry on HTTP 503 (service unavailable) * Cache negotiated api microversion for server * Updated from global requirements * Cleanup session creation * Updated from global requirements * Register global Keystone args first * Install the ironicclient into the 'venv' virtualenv * Updated from global requirements * Refactor resource\_fields.py 0.7.0 ----- * Disable meaningless sort keys in list command * httpretty can fail in Python 3.4 with wrong LC\_ALL * Add node-show-states command * Updated from global requirements * Revert fix that issues Unauthorized exception * Sync oslo.incubator * Consistent and more valid strings for Booleans * Drop use of 'oslo' namespace package * Disable invalid sort key in list command * Updated from global requirements * Add in support for a tox pypy target * Ensure \*-show input uuid is not empty * Remove unneeded 'utf-8' coding lines 0.6.0 ----- * Update README to work with release tools * Encode exception on cli for UnicodeDecodeError * Implement and enable retries on Conflict * Uncap library requirements for liberty 0.5.1 ----- * Client should fall back to the lower versions if necessary * Upgrade hacking to latest release * Fix node\_uuid option is required for port-create 0.5.0 ----- * Force LANGUAGE=en\_US in test runs * Add unittests for resource\_fields * Updated from global requirements * Add support for generating a config drive * Clean openstack-common module list * Send version header by default * Use oslo.i18n lib * Add support for logical names * Metavar name should be hyphenated * Avoid httpretty 0.8.8 as it can break unittests * Add support for inspection to node-set-provision-state * Fix the final PEP8 errors * Adds basic ironicclient functional testing * Fix two error strings in the CLI * Fix typo on patch 155624 * Enable ironicclient with --ironic-api-version 1.x * Fix PEP8 E121,E122,E123,E124,E125,E129 errors * Updated from global requirements * For flake8 check, make the 'E12' ignore be more granular * Updated from global requirements * Consistent names of args and metavars, and help strings * Remove unused OS\_TEST\_TIMEOUT variable * Capture stdout or stderr in tests * Updated from global requirements * Fix Python Ironic Client Documentation 0.4.1 ----- * Rename --configdrive to --config-drive * Fix argument for configdrive usage * Fix help string for port-list 0.4.0 ----- * port-list support get-port-from-mac * Update requirements.txt * cli support --os-endpoint * ironicclient node-set-maintenance to accept true/false * Updated from global requirements * Check if --config-drive is only used with provision state "active" * Add --config-drive to node-set-provision-state * Add driver\_internal\_info to node-show output 0.3.3 ----- * ironicclient handle faultstring when using SessionClient * Support setting non-string fields * Workflow documentation is now in infra-manual * Removed http proxy environment variable so that httpretty can work * Fix to properly issue an Unauthorized exception 0.3.2 ----- * Add option to specify node uuid in node-create subcommand * Add IRONIC\_URL to README * Fix log\_curl\_request API version duplication * Update README * Fix node-set-provision-state cmd's help strings * Updated from global requirements * Add maintenance\_reason to node-show output * Add 'API' to description of ironic command * Fix the usage comment of node-set-power-state cmd * Updated from global requirements * Fix sphinx warnings * VendorPassthru commands to support different HTTP methods * Sane parameters for node and driver vendor\_passthru() * Updated from global requirements * Sync apiclient from Oslo * Add node-set-maintenance command * Fix python-ironicclient crash * Adds tty password entry for ironicclient 0.3.1 ----- * Correct node CREATION\_ATTRIBUTE "uuid" * Updated from global requirements * Add keystone v3 CLI support * Stop using intersphinx * Bump hacking version * Add "ironic node-set-power-state" cmd unit test * Small fixes for utils/{common\_filters,common\_params\_for\_list} * Add unit tests for "ironic node-show" shell cmd * Remove unused command in tox.ini * Add unit tests for "ironic node-create" shell cmd * Add unit test for "ironic node-update" shell cmd * Updated from global requirements 0.3.0 ----- * Replace calls to Mock.assert\_not\_called * Add defaults to the CLI help strings * Updates to CLI doc * Add 'rebuild' option to node-set-provision-state * Update README with a bit more info * ironic client to use os\_region\_name if provided * Add unit test for "ironic port-update" shell cmd * Add unit tests for "ironic node-delete" shell cmd * handles keyboard interrupt * fixes help string for driver-list * Add 'bash-completion' to 'ironic help' response * List resources with detail * Add sort\_key and sort\_dir parameters to \*-list * Fix column headings regression due to switch to cliutils * Work toward Python 3.4 support and testing * Updated from global requirements * Fix misspelled class name AmbigiousAuthSystem * Optimize get\_by\_instance\_uuid * Show port by MAC address 0.2.1 ----- * Add /nodes/detail support * Trim trailing slash and version from endpoint * Updated from global requirements 0.2.0 ----- * Export 'client' and 'exc' modules * Add {set,get}\_boot\_device and get\_supported\_boot\_devices * Add driver-properties command * Add ironic cli support for vendor-passthru * Add pagination support to {node, port, chassis}-list * Expose auth\_ref in ironicclient client object * Add bash completion support for ironic cli * Remove aliases \`arg\` and \`env\` from utils * Use suitable assert * Add CONTRIBUTING.rst * Make a few minor updates to node shell help strings * Updated from global requirements * Add set\_provision\_state command * Add UTF-8 coding lines to all Python files * replace dict.iteritems() with six.iteritems(dict) * node-show to show the instance\_info field * removed py3kcompat module * Updated from global requirements * Reuse module \`cliutils\` from common code 0.1.4 ----- * Updated from global requirements * Sync latest code and reuse exceptions from oslo * node-get-console incorporate the changes in API * Adds documentation for ironicclient API * Remove py3kcompat module * Add documentation for ironic CLI * Updated from global requirements * Documentation for contributors * Fix docstring for client.get\_client() * Updated from global requirements * node-list to show the maintenance field * Add main developer page 0.1.3 ----- * Sync cliutils from oslo * Avoid traceback with insufficient auth credentials * Add support for 'driver-show' command * Updated from global requirements * Add set-console-mode, get-console commands * Sort requirement files in alphabetical order * Filtering nodes by maintenance mode * Remove shebang lines from code 0.1.2 ----- * Fix the test parameter order * Return 'maintenance' from node-show command * Fix params order in assertEqual * Remove vim header * Node power state is not printed after set * Return node\_uuid from a port-show cmd * Fix Iterface misspelling from node-validate cmd * Remove tox locale overrides * Add node.states() to the client library * Fix node-create help requiring chassis uuid * Updated from global requirements * Remove unused method 'string\_to\_bool' from utils 0.1.1 ----- * Reuse Resource from oslo * Sync apiclient and strutils from Oslo * Run unittest with python 3.3 * Drop python2.5 suport in ironicclient.common.http * Improve node-validate command output * Add set\_provision\_state to the client libs * ironic client should display chassis\_uuid value * Update openstack-common.conf list, sync with oslo (0d8f18b) 0.1.0 ----- * Rename nodes//state to nodes//states * Remove unused oslo-incubator modules * Updated from global requirements * driver-list command to show the list of conductors 0.0.1 ----- * Enable rebooting in the client lib and cli * Move from inheritance HTTPClient in Ironic client * Move six dependency to requirements.txt * Let CLI print exception traceback from 'debuginfo' * Add missing i18n support * Add node-validate to cli * Replace chassis\_id with chassis\_uuid on Nodes * Enable created\_at/updated\_at for port-show/chassis-show * Include 'chassis\_id' on node-show * Enable created\_at/updated\_at for port-show/chassis-show * Remove in-place try/except blocks from shell commands * Replace node\_id to node\_uuid on Ports * Support building wheels (PEP-427) * Shows 'last\_error' property for a node * Comply with new hacking release * Change id->uuid in ironic cli help messages * Updated from global requirements * Modifies CLI to show nodes by instance uuid * Remove Python 2.4 all() implementation * Deal with unicode strings * Custom output file on the print\_\*() functions * Add driver-list * Fix cmd usage msg for ironic port-create * Reorder imports * Rename variables called 'fixtures' * Comply with new hacking requirements * Transform print statement to print function * Add node-set-power-state to cli * Fixes Auth Token being sent as lambda function * Updated from global requirements * Sort the dict iterms * Replace basestring with six.string\_types * Use six.iteritems() for dict * Import urlutils module from oslo * Import httplib from six.moves * Transform print statement to print function * Import StringIO from six * Removes mox from ironicclient * Add chassis-node-list * Add node-port-list * Updated from global requirements * Allows multiple deletions * Add short command line option * Improve help strings * Remove duplicated do\_node\_show * Add missing chassis\_id arg to node-create * Improve error feedback * Updated from global requirements * Implement \*-update commands to resrouces * Change service\_type to baremetal * Split v1/shell.py into different files * Implement Node commands * Add port-create and port-delete * Stop HTTPClient from calling self.auth\_token() * Add chassis-create and chassis-delete * Add port-show and port-list * Implement chassis-list + more tests * Add tests for chassis * Add basic tests * Manager.\_list should always return a list * Implement chassis-show * Basic project structure * remove client suffix from command line client * Add initial files * Added .gitreview python-ironicclient-2.2.0/README.rst0000666000175100017510000000773613232474343017253 0ustar zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/python-ironicclient.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on Python bindings for the Ironic API ================================== This is a client for the OpenStack `Ironic `_ API. It provides: * a Python API: the ``ironicclient`` module, and * two command-line interfaces: ``openstack baremetal`` and ``ironic`` (deprecated, please use ``openstack baremetal``). Development takes place via the usual OpenStack processes as outlined in the `developer guide `_. The master repository is on `git.openstack.org `_. ``python-ironicclient`` is licensed under the Apache License, Version 2.0, like the rest of OpenStack. .. contents:: Contents: :local: Python API ---------- Quick-start Example:: >>> from ironicclient import client >>> >>> kwargs = {'os_auth_token': '3bcc3d3a03f44e3d8377f9247b0ad155', >>> 'ironic_url': 'http://ironic.example.org:6385/'} >>> ironic = client.get_client(1, **kwargs) ``openstack baremetal`` CLI --------------------------- The ``openstack baremetal`` command line interface is available when the bare metal plugin (included in this package) is used with the `OpenStackClient `_. There are two ways to install the OpenStackClient (python-openstackclient) package: * along with this python-ironicclient package:: # pip install python-ironicclient[cli] * directly:: # pip install python-openstackclient An example of creating a basic node with the ``ipmi`` driver:: $ openstack baremetal node create --driver ipmi An example of creating a port on a node:: $ openstack baremetal port create --node AA:BB:CC:DD:EE:FF An example of updating driver properties for a node:: $ openstack baremetal node set --driver-info ipmi_address= For more information about the ``openstack baremetal`` command and the subcommands available, run:: $ openstack help baremetal ``ironic`` CLI (deprecated) --------------------------- This is deprecated and will be removed in the S* release. Please use the ``openstack baremetal`` CLI instead. This package will install the ``ironic`` command line interface that you can use to interact with the ``ironic`` API. In order to use the ``ironic`` CLI you'll need to provide your OpenStack tenant, username, password and authentication endpoint. You can do this with the ``--os-tenant-name``, ``--os-username``, ``--os-password`` and ``--os-auth-url`` parameters, though it may be easier to set them as environment variables:: $ export OS_PROJECT_NAME=project $ export OS_USERNAME=user $ export OS_PASSWORD=pass $ export OS_AUTH_URL=http://auth.example.com:5000/v2.0 To use a specific Ironic API endpoint:: $ export IRONIC_URL=http://ironic.example.com:6385 An example of creating a basic node with the ``ipmi`` driver:: $ ironic node-create -d ipmi An example of creating a port on a node:: $ ironic port-create -a AA:BB:CC:DD:EE:FF -n nodeUUID An example of updating driver properties for a node:: $ ironic node-update nodeUUID add driver_info/ipmi_address= $ ironic node-update nodeUUID add driver_info/ipmi_username= $ ironic node-update nodeUUID add driver_info/ipmi_password= For more information about the ``ironic`` command and the subcommands available, run:: $ ironic help Useful Links ------------ * Documentation: https://docs.openstack.org/python-ironicclient/latest/ * Source: https://git.openstack.org/cgit/openstack/python-ironicclient * Bugs: https://bugs.launchpad.net/python-ironicclient * Release notes: https://docs.openstack.org/releasenotes/python-ironicclient/ python-ironicclient-2.2.0/LICENSE0000666000175100017510000002363713232474343016567 0ustar zuulzuul00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. python-ironicclient-2.2.0/tox.ini0000666000175100017510000000357113232474343017070 0ustar zuulzuul00000000000000[tox] minversion = 1.6 envlist = py36,py35,py27,pep8,pypy skipsdist = True [testenv] setenv = VIRTUAL_ENV={envdir} PYTHONDONTWRITEBYTECODE = 1 LANGUAGE=en_US # .stestr.conf uses TESTS_DIR TESTS_DIR=./ironicclient/tests/unit usedevelop = True install_command = pip install {opts} {packages} 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 = ostestr {posargs} [testenv:releasenotes] commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:pep8] commands = flake8 {posargs} doc8 doc/source CONTRIBUTING.rst README.rst [testenv:cover] setenv = {[testenv]setenv} PYTHON=coverage run --source ironicclient --omit='*tests*' --parallel-mode commands = coverage erase ostestr {posargs} coverage combine coverage report --omit='*tests*' coverage html -d ./cover --omit='*tests*' [testenv:venv] commands = {posargs} [testenv:functional] setenv = TESTS_DIR=./ironicclient/tests/functional LANGUAGE=en_US [testenv:docs] setenv = PYTHONHASHSEED=0 sitepackages = False envdir = {toxworkdir}/venv commands = python setup.py build_sphinx [flake8] ignore = exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,tools # [H106] Don't put vim configuration in source files. # [H203] Use assertIs(Not)None to check for None. # [H204] Use assert(Not)Equal to check for equality. # [H205] Use assert(Greater|Less)(Equal) for comparison. # [H210] Require ‘autospec’, ‘spec’, or ‘spec_set’ in mock.patch/mock.patch.object calls # [H904] Delay string interpolations at logging calls. enable-extensions=H106,H203,H204,H205,H210,H904 [hacking] import_exceptions = testtools.matchers, ironicclient.common.i18n python-ironicclient-2.2.0/setup.cfg0000666000175100017510000001675513232474761017412 0ustar zuulzuul00000000000000[metadata] name = python-ironicclient summary = OpenStack Bare Metal Provisioning API Client Library description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = https://docs.openstack.org/python-ironicclient/latest/ classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 [files] packages = ironicclient [entry_points] console_scripts = ironic = ironicclient.shell:main openstack.cli.extension = baremetal = ironicclient.osc.plugin openstack.baremetal.v1 = baremetal_chassis_create = ironicclient.osc.v1.baremetal_chassis:CreateBaremetalChassis baremetal_chassis_delete = ironicclient.osc.v1.baremetal_chassis:DeleteBaremetalChassis baremetal_chassis_list = ironicclient.osc.v1.baremetal_chassis:ListBaremetalChassis baremetal_chassis_set = ironicclient.osc.v1.baremetal_chassis:SetBaremetalChassis baremetal_chassis_show = ironicclient.osc.v1.baremetal_chassis:ShowBaremetalChassis baremetal_chassis_unset = ironicclient.osc.v1.baremetal_chassis:UnsetBaremetalChassis baremetal_create = ironicclient.osc.v1.baremetal_create:CreateBaremetal baremetal_driver_list = ironicclient.osc.v1.baremetal_driver:ListBaremetalDriver baremetal_driver_passthru_call = ironicclient.osc.v1.baremetal_driver:PassthruCallBaremetalDriver baremetal_driver_passthru_list = ironicclient.osc.v1.baremetal_driver:PassthruListBaremetalDriver baremetal_driver_property_list = ironicclient.osc.v1.baremetal_driver:ListBaremetalDriverProperty baremetal_driver_raid_property_list = ironicclient.osc.v1.baremetal_driver:ListBaremetalDriverRaidProperty baremetal_driver_show = ironicclient.osc.v1.baremetal_driver:ShowBaremetalDriver baremetal_node_abort = ironicclient.osc.v1.baremetal_node:AbortBaremetalNode baremetal_node_add_trait = ironicclient.osc.v1.baremetal_node:AddTraitBaremetalNode baremetal_node_adopt = ironicclient.osc.v1.baremetal_node:AdoptBaremetalNode baremetal_node_boot_device_set = ironicclient.osc.v1.baremetal_node:BootdeviceSetBaremetalNode baremetal_node_boot_device_show = ironicclient.osc.v1.baremetal_node:BootdeviceShowBaremetalNode baremetal_node_clean = ironicclient.osc.v1.baremetal_node:CleanBaremetalNode baremetal_node_console_disable = ironicclient.osc.v1.baremetal_node:ConsoleDisableBaremetalNode baremetal_node_console_enable = ironicclient.osc.v1.baremetal_node:ConsoleEnableBaremetalNode baremetal_node_console_show = ironicclient.osc.v1.baremetal_node:ConsoleShowBaremetalNode baremetal_node_create = ironicclient.osc.v1.baremetal_node:CreateBaremetalNode baremetal_node_delete = ironicclient.osc.v1.baremetal_node:DeleteBaremetalNode baremetal_node_deploy = ironicclient.osc.v1.baremetal_node:DeployBaremetalNode baremetal_node_inspect = ironicclient.osc.v1.baremetal_node:InspectBaremetalNode baremetal_node_list = ironicclient.osc.v1.baremetal_node:ListBaremetalNode baremetal_node_maintenance_set = ironicclient.osc.v1.baremetal_node:MaintenanceSetBaremetalNode baremetal_node_maintenance_unset = ironicclient.osc.v1.baremetal_node:MaintenanceUnsetBaremetalNode baremetal_node_manage = ironicclient.osc.v1.baremetal_node:ManageBaremetalNode baremetal_node_passthru_call = ironicclient.osc.v1.baremetal_node:PassthruCallBaremetalNode baremetal_node_passthru_list = ironicclient.osc.v1.baremetal_node:PassthruListBaremetalNode baremetal_node_power_off = ironicclient.osc.v1.baremetal_node:PowerOffBaremetalNode baremetal_node_power_on = ironicclient.osc.v1.baremetal_node:PowerOnBaremetalNode baremetal_node_provide = ironicclient.osc.v1.baremetal_node:ProvideBaremetalNode baremetal_node_reboot = ironicclient.osc.v1.baremetal_node:RebootBaremetalNode baremetal_node_rebuild = ironicclient.osc.v1.baremetal_node:RebuildBaremetalNode baremetal_node_remove_trait = ironicclient.osc.v1.baremetal_node:RemoveTraitBaremetalNode baremetal_node_set = ironicclient.osc.v1.baremetal_node:SetBaremetalNode baremetal_node_show = ironicclient.osc.v1.baremetal_node:ShowBaremetalNode baremetal_node_trait_list = ironicclient.osc.v1.baremetal_node:ListTraitsBaremetalNode baremetal_node_undeploy = ironicclient.osc.v1.baremetal_node:UndeployBaremetalNode baremetal_node_unset = ironicclient.osc.v1.baremetal_node:UnsetBaremetalNode baremetal_node_validate = ironicclient.osc.v1.baremetal_node:ValidateBaremetalNode baremetal_node_vif_attach = ironicclient.osc.v1.baremetal_node:VifAttachBaremetalNode baremetal_node_vif_detach = ironicclient.osc.v1.baremetal_node:VifDetachBaremetalNode baremetal_node_vif_list = ironicclient.osc.v1.baremetal_node:VifListBaremetalNode baremetal_node_inject_nmi = ironicclient.osc.v1.baremetal_node:InjectNmiBaremetalNode baremetal_port_create = ironicclient.osc.v1.baremetal_port:CreateBaremetalPort baremetal_port_delete = ironicclient.osc.v1.baremetal_port:DeleteBaremetalPort baremetal_port_list = ironicclient.osc.v1.baremetal_port:ListBaremetalPort baremetal_port_set = ironicclient.osc.v1.baremetal_port:SetBaremetalPort baremetal_port_show = ironicclient.osc.v1.baremetal_port:ShowBaremetalPort baremetal_port_unset = ironicclient.osc.v1.baremetal_port:UnsetBaremetalPort baremetal_port_group_create = ironicclient.osc.v1.baremetal_portgroup:CreateBaremetalPortGroup baremetal_port_group_delete = ironicclient.osc.v1.baremetal_portgroup:DeleteBaremetalPortGroup baremetal_port_group_list = ironicclient.osc.v1.baremetal_portgroup:ListBaremetalPortGroup baremetal_port_group_set = ironicclient.osc.v1.baremetal_portgroup:SetBaremetalPortGroup baremetal_port_group_show = ironicclient.osc.v1.baremetal_portgroup:ShowBaremetalPortGroup baremetal_port_group_unset = ironicclient.osc.v1.baremetal_portgroup:UnsetBaremetalPortGroup baremetal_volume_connector_create = ironicclient.osc.v1.baremetal_volume_connector:CreateBaremetalVolumeConnector baremetal_volume_connector_delete = ironicclient.osc.v1.baremetal_volume_connector:DeleteBaremetalVolumeConnector baremetal_volume_connector_list = ironicclient.osc.v1.baremetal_volume_connector:ListBaremetalVolumeConnector baremetal_volume_connector_set = ironicclient.osc.v1.baremetal_volume_connector:SetBaremetalVolumeConnector baremetal_volume_connector_show = ironicclient.osc.v1.baremetal_volume_connector:ShowBaremetalVolumeConnector baremetal_volume_connector_unset = ironicclient.osc.v1.baremetal_volume_connector:UnsetBaremetalVolumeConnector baremetal_volume_target_create = ironicclient.osc.v1.baremetal_volume_target:CreateBaremetalVolumeTarget baremetal_volume_target_delete = ironicclient.osc.v1.baremetal_volume_target:DeleteBaremetalVolumeTarget baremetal_volume_target_list = ironicclient.osc.v1.baremetal_volume_target:ListBaremetalVolumeTarget baremetal_volume_target_set = ironicclient.osc.v1.baremetal_volume_target:SetBaremetalVolumeTarget baremetal_volume_target_show = ironicclient.osc.v1.baremetal_volume_target:ShowBaremetalVolumeTarget baremetal_volume_target_unset = ironicclient.osc.v1.baremetal_volume_target:UnsetBaremetalVolumeTarget [pbr] autodoc_index_modules = True autodoc_exclude_modules = ironicclient.tests.functional.* warnerrors = True [build_sphinx] all_files = 1 build-dir = doc/build source-dir = doc/source warning-is-error = 1 [wheel] universal = 1 [extras] cli = python-openstackclient>=3.12.0 # Apache-2.0 [egg_info] tag_build = tag_date = 0 python-ironicclient-2.2.0/setup.py0000666000175100017510000000200613232474343017257 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-ironicclient-2.2.0/requirements.txt0000666000175100017510000000113013232474343021026 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 appdirs>=1.3.0 # MIT License dogpile.cache>=0.6.2 # BSD jsonschema<3.0.0,>=2.6.0 # MIT keystoneauth1>=3.3.0 # Apache-2.0 osc-lib>=1.8.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 PrettyTable<0.8,>=0.7.1 # BSD PyYAML>=3.10 # MIT requests>=2.14.2 # Apache-2.0 six>=1.10.0 # MIT python-ironicclient-2.2.0/playbooks/0000775000175100017510000000000013232474761017554 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/playbooks/legacy/0000775000175100017510000000000013232474761021020 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/playbooks/legacy/ironicclient-tempest-dsvm-src/0000775000175100017510000000000013232474761026715 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/playbooks/legacy/ironicclient-tempest-dsvm-src/run.yaml0000666000175100017510000001417513232474343030413 0ustar zuulzuul00000000000000- hosts: all name: Autoconverted job legacy-tempest-dsvm-python-ironicclient-src from old job gate-tempest-dsvm-python-ironicclient-src-ubuntu-xenial tasks: - name: Ensure legacy workspace directory file: path: '{{ ansible_user_dir }}/workspace' state: directory - shell: cmd: | set -e set -x cat > clonemap.yaml << EOF clonemap: - name: openstack-infra/devstack-gate dest: devstack-gate EOF /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \ git://git.openstack.org \ openstack-infra/devstack-gate executable: /bin/bash chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' - shell: cmd: | cat << 'EOF' >> ironic-extra-vars export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_DEPLOY_DRIVER_ISCSI_WITH_IPA=True" # Standardize VM size for each supported ramdisk case "tinyipa" in 'tinyipa') export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_VM_SPECS_RAM=384" export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_RAMDISK_TYPE=tinyipa" ;; 'tinyipa256') export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_VM_SPECS_RAM=256" export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_RAMDISK_TYPE=tinyipa" ;; 'coreos') export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_VM_SPECS_RAM=1280" export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_RAMDISK_TYPE=coreos" ;; # if using a ramdisk without a known good value, use the devstack # default by not exporting any value for IRONIC_VM_SPECS_RAM esac EOF chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' - shell: cmd: | cat << 'EOF' >> ironic-extra-vars export DEVSTACK_GATE_TEMPEST_REGEX="ironic" EOF chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' - shell: cmd: | cat << 'EOF' >> ironic-extra-vars export DEVSTACK_PROJECT_FROM_GIT="python-ironicclient,$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=pxe_ipmitool export BRANCH_OVERRIDE=default if [ "$BRANCH_OVERRIDE" != "default" ] ; then export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE fi if [[ ! "stable/newton stable/ocata stable/pike" =~ $ZUUL_BRANCH ]] ; then export DEVSTACK_GATE_TLSPROXY=1 fi if [ "pxe_ipmitool" == "pxe_snmp" ] ; then # explicitly enable pxe_snmp driver export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_ENABLED_DRIVERS=fake,pxe_snmp" fi if [ "pxe_ipmitool" == "redfish" ] ; then # When deploying with redfish we need to enable the "redfish" # hardware type export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_ENABLED_HARDWARE_TYPES=redfish" fi if [ "partition" == "wholedisk" ] ; then export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_TEMPEST_WHOLE_DISK_IMAGE=True" export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_VM_EPHEMERAL_DISK=0" else export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_TEMPEST_WHOLE_DISK_IMAGE=False" export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_VM_EPHEMERAL_DISK=1" fi if [ -n "" ] ; then export DEVSTACK_GATE_IRONIC_BUILD_RAMDISK=1 export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_INSPECTOR_BUILD_RAMDISK=True" export DEVSTACK_LOCAL_CONFIG+=$'\n'"USE_SUBNETPOOL=False" else export DEVSTACK_GATE_IRONIC_BUILD_RAMDISK=0 export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_INSPECTOR_BUILD_RAMDISK=False" fi if [ "bios" == "uefi" ] ; then export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_BOOT_MODE=uefi" fi export DEVSTACK_PROJECT_FROM_GIT="" export DEVSTACK_LOCAL_CONFIG+=$'\n'"IRONIC_VM_COUNT=1" # Ensure the ironic-vars-EARLY file exists touch ironic-vars-early # Pull in the EARLY variables injected by the optional builders source ironic-vars-early export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin ironic git://git.openstack.org/openstack/ironic" # Ensure the ironic-EXTRA-vars file exists touch ironic-extra-vars # Pull in the EXTRA variables injected by the optional builders source ironic-extra-vars cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh executable: /bin/bash chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' python-ironicclient-2.2.0/playbooks/legacy/ironicclient-tempest-dsvm-src/post.yaml0000666000175100017510000000063313232474343030566 0ustar zuulzuul00000000000000- hosts: primary tasks: - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=/logs/** - --include=*/ - --exclude=* - --prune-empty-dirs python-ironicclient-2.2.0/playbooks/legacy/ironicclient-dsvm-functional/0000775000175100017510000000000013232474761026611 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/playbooks/legacy/ironicclient-dsvm-functional/run.yaml0000666000175100017510000000440013232474343030275 0ustar zuulzuul00000000000000- hosts: all name: Autoconverted job legacy-ironicclient-dsvm-functional from old job gate-ironicclient-dsvm-functional-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: | set -e set -x cat << 'EOF' >>"/tmp/dg-local.conf" [[local|localrc]] enable_plugin ironic git://git.openstack.org/openstack/ironic IRONIC_DEPLOY_DRIVER=fake # neutron is not enabled here IRONIC_ENABLED_NETWORK_INTERFACES=noop IRONIC_DHCP_PROVIDER=none EOF executable: /bin/bash chdir: '{{ ansible_user_dir }}/workspace' environment: '{{ zuul | zuul_legacy_vars }}' - shell: cmd: | set -e set -x export PYTHONUNBUFFERED=true export DEVSTACK_GATE_TEMPEST=0 export DEVSTACK_PROJECT_FROM_GIT=python-ironicclient export OVERRIDE_ENABLED_SERVICES=key,mysql,rabbit,ir-api,ir-cond export BRANCH_OVERRIDE=default if [ "$BRANCH_OVERRIDE" != "default" ] ; then export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE fi export PROJECTS="openstack/ironic $PROJECTS" function post_test_hook { # Configure and run functional tests $BASE/new/python-ironicclient/ironicclient/tests/functional/hooks/post_test_hook.sh } export -f post_test_hook 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-ironicclient-2.2.0/playbooks/legacy/ironicclient-dsvm-functional/post.yaml0000666000175100017510000000063313232474343030462 0ustar zuulzuul00000000000000- hosts: primary tasks: - name: Copy files from {{ ansible_user_dir }}/workspace/ on node synchronize: src: '{{ ansible_user_dir }}/workspace/' dest: '{{ zuul.executor.log_root }}' mode: pull copy_links: true verify_host: true rsync_opts: - --include=/logs/** - --include=*/ - --exclude=* - --prune-empty-dirs python-ironicclient-2.2.0/releasenotes/0000775000175100017510000000000013232474761020242 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/releasenotes/notes/0000775000175100017510000000000013232474761021372 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/releasenotes/notes/bug-1724974-add-wanboot-to-supported-boot-devices.yaml0000666000175100017510000000013013232474343033103 0ustar zuulzuul00000000000000--- features: - Adds missing ``wanboot`` value to the list of supported boot devices. python-ironicclient-2.2.0/releasenotes/notes/traits-support-8864f6816abecdb2.yaml0000666000175100017510000000123413232474373027604 0ustar zuulzuul00000000000000--- features: - | Adds support for reading and modifying traits for a node, including adding traits to the detailed output of a node. This is available starting with Bare Metal API version 1.37. The new commands are: * ``openstack baremetal node trait list `` * ``openstack baremetal node add trait [...]`` * ``openstack baremetal node remove trait [ [...]] [--all]`` It also adds the following methods to the Python SDK: * ``NodeManager.get_traits`` * ``NodeManager.add_trait`` * ``NodeManager.set_traits`` * ``NodeManager.remove_trait`` * ``NodeManager.remove_all_traits`` python-ironicclient-2.2.0/releasenotes/notes/add-json-option-0cf29be2a97e0212.yaml0000666000175100017510000000020513232474343027463 0ustar zuulzuul00000000000000--- features: - Add a --json option to the client to display the JSON response body from the Ironic API without formatting it. python-ironicclient-2.2.0/releasenotes/notes/remove-deprecated-osc-cmd-6dc980299d2fbde4.yaml0000666000175100017510000000164413232474343031511 0ustar zuulzuul00000000000000--- upgrade: - | These previously deprecated commands were removed and are no longer available: * ``openstack baremetal delete`` * ``openstack baremetal list`` * ``openstack baremetal show`` * ``openstack baremetal set`` * ``openstack baremetal unset`` Instead, use these corresponding equivalent commands: * ``openstack baremetal node delete`` * ``openstack baremetal node list`` * ``openstack baremetal node show`` * ``openstack baremetal node set`` * ``openstack baremetal node unset`` - | Support for creating a single node via ``openstack baremetal create`` had been previously deprecated; it is now no longer available. Instead, use the equivalent command ``openstack baremetal node create``. The only valid usage of ``openstack baremetal create`` is to create various resources (chassis, nodes, port groups, and ports) from resource files. python-ironicclient-2.2.0/releasenotes/notes/manual-clean-09f6b49df7d2513f.yaml0000666000175100017510000000112013232474343027032 0ustar zuulzuul00000000000000--- features: - Adds support for manual cleaning API; available with ironic-api-version 1.15 or higher. The ironic CLI is "ironic node-set-provision-state --clean-steps " where is 'clean' and is the clean steps in JSON format. May be the path to a file containing the clean steps; OR '-', with the clean steps being read from standard input; OR a string. The value should be a list of clean-step dictionaries; each dictionary should have keys 'interface' and 'step', and optional key 'args'. python-ironicclient-2.2.0/releasenotes/notes/remove-llc-short-arg-89c7443acc6c54a4.yaml0000666000175100017510000000032513232474343030446 0ustar zuulzuul00000000000000--- deprecations: - Deprecates ``-l`` argument of ``openstack baremetal port create`` command in favor of ``--local-link-connection``, as by OpenStackClient guidelines, the options should be full words. ././@LongLink0000000000000000000000000000015300000000000011214 Lustar 00000000000000python-ironicclient-2.2.0/releasenotes/notes/osc-plugin-set-unset-target-raid-config-9a1cecb5620eafda.yamlpython-ironicclient-2.2.0/releasenotes/notes/osc-plugin-set-unset-target-raid-config-9a1cecb5620eafd0000666000175100017510000000033313232474343033325 0ustar zuulzuul00000000000000--- fixes: - No longer emits the incorrect warning "Please specify what to set" (or "unset") when only the ``--target-raid-config`` is specified in the ``openstack baremetal node set`` (or ``unset``) command. python-ironicclient-2.2.0/releasenotes/notes/extend-vif-attach-commands-ef3a931413ddcee7.yaml0000666000175100017510000000031113232474343031736 0ustar zuulzuul00000000000000--- features: - Adds ability to set custom VIF information fields via the `--vif-info ` option in the `ironic node-vif-attach` and `openstack baremetal node vif attach` commands. python-ironicclient-2.2.0/releasenotes/notes/fix-python3-compatibility-993ace45fefcba34.yaml0000666000175100017510000000026313232474343031756 0ustar zuulzuul00000000000000--- fixes: - Fixes an issue in a python 3 environment, where TypeError exceptions were being raised (due to the "requests" library returning bytes instead of a string). python-ironicclient-2.2.0/releasenotes/notes/driver-properties-for-osc-07a99d2d4e166436.yaml0000666000175100017510000000043213232474343031372 0ustar zuulzuul00000000000000--- features: - Adds the ``openstack baremetal driver property list `` command. For the specified driver, this returns a list of its properties, along with descriptions for each property. (The values of these properties are specified in a node's driver_info.) python-ironicclient-2.2.0/releasenotes/notes/port-physical-network-6ea8860d773e473c.yaml0000666000175100017510000000017413232474343030715 0ustar zuulzuul00000000000000--- features: - | Adds support for the ``port.physical_network`` field, which was introduced in API version 1.34. python-ironicclient-2.2.0/releasenotes/notes/start-using-reno-ccd220efa2c7022a.yaml0000666000175100017510000000007113232474343030023 0ustar zuulzuul00000000000000--- other: - Start using reno to manage release notes. python-ironicclient-2.2.0/releasenotes/notes/instance-crash-dump-d845a31e72b5a9f7.yaml0000666000175100017510000000017313232474343030345 0ustar zuulzuul00000000000000--- features: - Add a new command "ironic node-inject-nmi" to support the injection of Non-Masking Interrupts (NMI). python-ironicclient-2.2.0/releasenotes/notes/raid_CLI_support-7e816ccd0fb31d2b.yaml0000666000175100017510000000077513232474343030035 0ustar zuulzuul00000000000000--- features: - | Adds support for RAID configuration: * Ability to set target_raid_config for a node using node-set-target-raid-config. * Displays target_raid_config and raid_config in node-show. * Displays logical disk properties for a driver using driver-raid-logical-disk-properties. API version 1.15 should be used with these features, as RAID configuration was added in 1.12 and manual cleaning (used to trigger RAID configuration) was added in 1.15. python-ironicclient-2.2.0/releasenotes/notes/osc-port-create-uuid-5da551b154540ef7.yaml0000666000175100017510000000023113232474343030354 0ustar zuulzuul00000000000000--- features: - | Adds the ``--uuid`` option for the ``openstack baremetal port create`` command so that the new port's UUID can be specified. python-ironicclient-2.2.0/releasenotes/notes/osc-versioned-endpoint-fix-08f6b7af2f47a5d6.yaml0000666000175100017510000000016613232474343031745 0ustar zuulzuul00000000000000--- fixes: - Fixes an issue with OpenStackClient plugin not being able to work with versioned ironic endpoints. python-ironicclient-2.2.0/releasenotes/notes/feature-parity-osc-cli-7606eed15f1c124f.yaml0000666000175100017510000000023413232474343030756 0ustar zuulzuul00000000000000--- prelude: > With this release, we have achieved feature parity between the ``ironic`` and ``openstack baremetal`` (OpenStack Client plugin) CLI. ././@LongLink0000000000000000000000000000015000000000000011211 Lustar 00000000000000python-ironicclient-2.2.0/releasenotes/notes/add-chassis_uuid-removal-possibility-5bc0bc3a7953eaa5.yamlpython-ironicclient-2.2.0/releasenotes/notes/add-chassis_uuid-removal-possibility-5bc0bc3a7953eaa5.y0000666000175100017510000000023313232474343033350 0ustar zuulzuul00000000000000--- features: - Add possibility to remove chassis_uuid for node by adding '--chassis-uuid' argument to 'openstack baremetal node unset' command. python-ironicclient-2.2.0/releasenotes/notes/session-client-endpoint-override-20f1d822b4430afa.yaml0000666000175100017510000000026513232474343033044 0ustar zuulzuul00000000000000--- fixes: - Fixes an issue when SessionClient ignores endpoint_override while doing session requests, which leads to undeterministic results in multi-region deployments. python-ironicclient-2.2.0/releasenotes/notes/osc-commands-1-7-d531960472a11ac2.yaml0000666000175100017510000000222213232474343027176 0ustar zuulzuul00000000000000--- features: - | Adds new OpenStackClient commands: * ``openstack baremetal chassis create`` * ``openstack baremetal chassis delete`` * ``openstack baremetal chassis list`` * ``openstack baremetal chassis set`` * ``openstack baremetal chassis show`` * ``openstack baremetal chassis unset`` * ``openstack baremetal driver list`` * ``openstack baremetal driver passthru call`` * ``openstack baremetal driver passthru list`` * ``openstack baremetal driver show`` * ``openstack baremetal node adopt`` * ``openstack baremetal node boot device set`` * ``openstack baremetal node boot device show`` * ``openstack baremetal node console disable`` * ``openstack baremetal node console enable`` * ``openstack baremetal node console show`` * ``openstack baremetal node passthru call`` * ``openstack baremetal node passthru list`` * ``openstack baremetal node validate`` * ``openstack baremetal port delete`` * ``openstack baremetal port list`` * ``openstack baremetal port set`` * ``openstack baremetal port unset`` Please see the help for each command for more information. python-ironicclient-2.2.0/releasenotes/notes/add-volume-connector-api-873090474d5e41b8.yaml0000666000175100017510000000114213232474343031052 0ustar zuulzuul00000000000000--- features: - | Adds these python API client methods to support volume connector resources (available starting with API version 1.32): * ``client.volume_connector.create`` for creating a volume connector * ``client.volume_connector.list`` for listing volume connectors * ``client.volume_connector.get`` for getting a volume connector * ``client.volume_connector.update`` for updating a volume connector * ``client.volume_connector.delete`` for deleting a volume connector * ``client.node.list_volume_connectors`` for getting volume connectors associated with a node python-ironicclient-2.2.0/releasenotes/notes/implicit-version-warning-old-cli-fe34d423ae63544a.yaml0000666000175100017510000000045713232474343032754 0ustar zuulzuul00000000000000--- deprecations: - | Currently, the default API version for the ``ironic`` tool is fixed to be 1.9. In the Queens release, it will be changed to the latest version understood by both the client and the server. In this release a warning is logged, if no explicit version is provided. python-ironicclient-2.2.0/releasenotes/notes/add-volume-connector-cli-873090474d5e41b9.yaml0000666000175100017510000000130613232474343031053 0ustar zuulzuul00000000000000--- features: - | Adds these ``openstack baremetal`` and ``ironic`` CLI commands for volume connector resources. * ``openstack baremetal volume connector create`` * ``openstack baremetal volume connector list`` * ``openstack baremetal volume connector show`` * ``openstack baremetal volume connector set`` * ``openstack baremetal volume connector unset`` * ``openstack baremetal volume connector delete`` * ``ironic volume-connector-create`` * ``ironic volume-connector-list`` * ``ironic volume-connector-show`` * ``ironic volume-connector-update`` * ``ironic volume-connector-delete`` They are available starting with ironic API microversion 1.32. python-ironicclient-2.2.0/releasenotes/notes/osc-plugin-node-create-show-fix-283148c86fbccce2.yaml0000666000175100017510000000022013232474343032554 0ustar zuulzuul00000000000000--- upgrade: - Hide ports field in 'node create' and 'node show' OSC subcommand output because this field is not meant for CLI users. python-ironicclient-2.2.0/releasenotes/notes/osc-port-set-llc-pxeenabled-21fd8ea1982af17e.yaml0000666000175100017510000000110613232474343031766 0ustar zuulzuul00000000000000--- features: - | For ``openstack baremetal port set``, adds these options: * ``--local-link-connection ``: Key/value metadata describing local link connection information. Valid keys are ``switch_info``, ``switch_id``, and ``port_id``. The keys ``switch_id`` and ``port_id`` are required (repeat option to specify multiple keys). * ``--pxe-enabled``: Indicates that this port should be used when PXE booting this node (default) * ``--pxe-disabled``: Indicates that this port should not be used when PXE booting this node ././@LongLink0000000000000000000000000000015500000000000011216 Lustar 00000000000000python-ironicclient-2.2.0/releasenotes/notes/osc-wait-option-for-provisioning-commands-b6f5b875d573c9c8.yamlpython-ironicclient-2.2.0/releasenotes/notes/osc-wait-option-for-provisioning-commands-b6f5b875d573c0000666000175100017510000000074513232474343033400 0ustar zuulzuul00000000000000--- features: - | Adds an option ``--wait []`` to all of the OSC provisioning commands, except for abort which is not supported. When specified, provisioning commands will wait for the node to reach the desired state before returning. An optional argument, time-out, can be specified which will end the waiting after the specified amount of time (in seconds). The default value for this timeout is 0, which means it will wait indefinitely. python-ironicclient-2.2.0/releasenotes/notes/allow-api-user-to-use-latest-6b80e9f584eaaa4e.yaml0000666000175100017510000000231113232474343032201 0ustar zuulzuul00000000000000--- features: - | Allows a python API user to pass ``latest`` to the client creation request for the ``os_ironic_api_version`` parameter. The version utilized for REST API requests will, as a result, be the highest available version understood by both the ironicclient library and the server. - | Adds base client properties to provide insight to a python API user of what the current REST API version that will be utilized, and if API version negotiation has occured. These new properties are ``client.current_api_version`` and ``client.is_api_version_negotiated`` respectively. - | Adds additional base client method to allow a python API user to trigger version negotiation and return the negotiated version. This new method is ``client.negotiate_api_version()``. other: - | The maximum supported version supported for negotiation is now defined in the ``common/http.py`` file. Any new feature added to the API client library must increment this version. - | The maximum known version supported by the ``OpenStackClient`` plugin is now defined by the maximum supported version for API negotiation as defined in the ``common/http.py`` file. ././@LongLink0000000000000000000000000000020300000000000011210 Lustar 00000000000000python-ironicclient-2.2.0/releasenotes/notes/bug-1712935-allow-os_baremetal_api_version_env_var_to_be_latest-28c8eed24f389673.yamlpython-ironicclient-2.2.0/releasenotes/notes/bug-1712935-allow-os_baremetal_api_version_env_var_to_b0000666000175100017510000000045213232474343033612 0ustar zuulzuul00000000000000--- fixes: - | ``openstack baremetal`` commands no longer fail when specifying ``latest`` as the API version (via ``--os-baremetal-api-version`` or ``export OS_BAREMETAL_API_VERSION=latest``). For more details, see `bug 1712935 `_. python-ironicclient-2.2.0/releasenotes/notes/implicit-version-warning-d34b99727b50d519.yaml0000666000175100017510000000045113232474343031302 0ustar zuulzuul00000000000000--- deprecations: - | Currently, the default API version for the OSC plugin is fixed to be 1.9. In the Queens release, it will be changed to the latest version understood by both the client and the server. In this release a warning is logged, if no explicit version is provided. python-ironicclient-2.2.0/releasenotes/notes/osc-node-list-no-maintenance-ff1cef7cfbe60fb9.yaml0000666000175100017510000000031113232474343032427 0ustar zuulzuul00000000000000--- features: - | For OSC, adds ability to get list of nodes that are not in maintenance mode via the --no-maintenance optional argument to the ``openstack baremetal node list`` command. python-ironicclient-2.2.0/releasenotes/notes/add-pecan-exc-construction-a776408f7ae110dc.yaml0000666000175100017510000000014413232474343031606 0ustar zuulzuul00000000000000--- fixes: - Fixes an issue where certain error messages from the ironic API were suppressed. python-ironicclient-2.2.0/releasenotes/notes/osc-plugin-f87e0fbb472261dd.yaml0000666000175100017510000000101513232474343026630 0ustar zuulzuul00000000000000--- features: - | Extend the OpenStackClient plugin with new commands: * openstack baremetal port group create * openstack baremetal port group show * openstack baremetal port group list * openstack baremetal port group delete * openstack baremetal port group set * openstack baremetal port group unset Extends the 'openstack baremetal port' commands (where applicable) to allow specifying the port group (if any) that a port is a member of, via the new --port-group argument. python-ironicclient-2.2.0/releasenotes/notes/not-ignore-delete-failtures-0783d33a606ed6f1.yaml0000666000175100017510000000040313232474343031722 0ustar zuulzuul00000000000000--- fixes: - Fixes an issue where deleting nodes ignore failures and just prints the results which nodes are deleted and which are failed, always returning exit code of 0. This now will return a non-zero exit code if at least one node-delete fails. python-ironicclient-2.2.0/releasenotes/notes/osc-plugin-ff0d897d8441a9e1.yaml0000666000175100017510000000223513232474343026570 0ustar zuulzuul00000000000000--- features: - | Extend the OpenStackClient plugin with new commands: * openstack baremetal node abort * openstack baremetal node clean * openstack baremetal node create * openstack baremetal node delete - Supports deleting multiple nodes * openstack baremetal node deploy * openstack baremetal node inspect * openstack baremetal node list * openstack baremetal node maintenance set * opnestack baremetal node maintenance unset * openstack baremetal node manage * openstack baremetal node power * openstack baremetal node provide * openstack baremetal node reboot * openstack baremetal node rebuild * openstack baremetal node set * openstack baremetal node show * openstack baremetal node undeploy * openstack baremetal node unset * openstack baremetal port create deprecations: - | Deprecating the following commands in favor of the new commands: * openstack baremetal create * openstack baremetal delete * openstack baremetal list * openstack baremetal set * openstack baremetal show * openstack baremetal unset These will be removed in the 'Queens' release. python-ironicclient-2.2.0/releasenotes/notes/osc-node-power-on-off-c269980e3b9c79ca.yaml0000666000175100017510000000064213232474343030535 0ustar zuulzuul00000000000000--- fixes: - | Replaces ``openstack baremetal node power `` with the two commands: * ``openstack baremetal node power on`` and * ``openstack baremetal node power off``. There is no change to the command the user enters (the actual command line is the same). However, help (e.g. via ``openstack -h baremetal``) will list the two power commands (instead of the original one). ././@LongLink0000000000000000000000000000016200000000000011214 Lustar 00000000000000python-ironicclient-2.2.0/releasenotes/notes/bug-1524745-extend-driver-list-and-driver-show-800d96393aa17342.yamlpython-ironicclient-2.2.0/releasenotes/notes/bug-1524745-extend-driver-list-and-driver-show-800d96390000666000175100017510000000146113232474343032465 0ustar zuulzuul00000000000000--- features: - | To support dynamic drivers (available starting with ironic API microversion 1.30): * ironic driver-list has two new optional arguments, ``--type `` for the type of driver ('classic' or 'dynamic') to list, and ``--detail`` to show detailed information about the drivers. * ironic driver-show returns a lot more information, including the type of driver and the default and enabled interfaces. * openstack baremetal driver list has two new optional arguments, ``--type `` for the type of driver ('classic' or 'dynamic') to list, and ``--long`` to show detailed information about the drivers. * openstack baremetal driver show returns a lot more information, including the type of driver and the default and enabled interfaces.././@LongLink0000000000000000000000000000017500000000000011220 Lustar 00000000000000python-ironicclient-2.2.0/releasenotes/notes/add-environment-variable-to-specify-version-cache-timeout-dfa5f6d4af0ea1d3.yamlpython-ironicclient-2.2.0/releasenotes/notes/add-environment-variable-to-specify-version-cache-timeo0000666000175100017510000000026113232474343034106 0ustar zuulzuul00000000000000--- features: - Add support for specifying the expiry (in seconds) of the caching of the ironic API version, via the environment variable "IRONICCLIENT_CACHE_EXPIRY". ././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000python-ironicclient-2.2.0/releasenotes/notes/osc-plugin-node-set-target-raid-config-5d538d6253902ecb.yamlpython-ironicclient-2.2.0/releasenotes/notes/osc-plugin-node-set-target-raid-config-5d538d6253902ecb0000666000175100017510000000043513232474343032714 0ustar zuulzuul00000000000000--- features: - | Allows setting and unsetting (clearing) the node's target RAID configuration via the OpenStackClient plugin commands ``openstack baremetal node set --target-raid-config`` and ``openstack baremetal node unset --target-raid-config`` respectively. ././@LongLink0000000000000000000000000000015000000000000011211 Lustar 00000000000000python-ironicclient-2.2.0/releasenotes/notes/osc-baremetal-driver-raid-properties-159bd57058c0fc0e.yamlpython-ironicclient-2.2.0/releasenotes/notes/osc-baremetal-driver-raid-properties-159bd57058c0fc0e.y0000666000175100017510000000053313232474343033115 0ustar zuulzuul00000000000000--- features: - Adds the ``openstack baremetal driver raid property list `` command. For a specified driver, this returns a list of the RAID logical disk properties that can be specified, along with a description for each property. (The values of these properties are specified in a node's ``target_raid_config`` field.) python-ironicclient-2.2.0/releasenotes/notes/keystone-token-auth-661a0c0d53c1b4de.yaml0000666000175100017510000000025713232474343030453 0ustar zuulzuul00000000000000--- features: - Added a possibility to pass os_auth_token instead of os_username and os_password to authenticate in keystone both in CLI and in get_client function. python-ironicclient-2.2.0/releasenotes/notes/soft-reboot-poweroff-e33d078a05db3894.yaml0000666000175100017510000000016313232474343030503 0ustar zuulzuul00000000000000--- features: - Add optional arguments '--soft' and '--power-timeout' to the command "node-set-power-state". python-ironicclient-2.2.0/releasenotes/notes/add-portgroup-mode-properties-0a3023cf905adaef.yaml0000666000175100017510000000035313232474343032516 0ustar zuulzuul00000000000000--- features: - Mode and properties fields were added to the portgroup object, along with respective arguments for the ironic CLI and the OpenStackClient plugin, they are available starting with ironic API microversion 1.26. ././@LongLink0000000000000000000000000000015100000000000011212 Lustar 00000000000000python-ironicclient-2.2.0/releasenotes/notes/bug-1524745-update-baremetal-node-set-c1ac57de0d481efe.yamlpython-ironicclient-2.2.0/releasenotes/notes/bug-1524745-update-baremetal-node-set-c1ac57de0d481efe.0000666000175100017510000000036713232474343032366 0ustar zuulzuul00000000000000--- features: - Adds new arguments to the OSC baremetal-node-set to allow setting boot, console, deploy, inspect, management, power, raid, and vendor hardware interfaces. They are available starting with ironic API microversion 1.30.python-ironicclient-2.2.0/releasenotes/notes/add-vif-attach-detach-support-e680d64e4add0fa4.yaml0000666000175100017510000000073413232474343032347 0ustar zuulzuul00000000000000--- features: - | Adds support for attaching and detaching VIFs. This is available starting with ironic API microversion 1.28. The new commands are: * ``ironic node-vif-list `` * ``ironic node-vif-attach `` * ``ironic node-vif-detach `` * ``openstack baremetal node vif list `` * ``openstack baremetal node vif attach `` * ``openstack baremetal node vif detach `` python-ironicclient-2.2.0/releasenotes/notes/ironic-create-files-fix-c31e40e566ff86b8.yaml0000666000175100017510000000015313232474343031112 0ustar zuulzuul00000000000000--- fixes: - Fixes 'ironic create ' command so that it handles the file argument(s) correctly. python-ironicclient-2.2.0/releasenotes/notes/add-create-command-3df5efbbecc33276.yaml0000666000175100017510000000101013232474343030307 0ustar zuulzuul00000000000000--- features: - Adds a new ``ironic create`` command that creates resources (chassis, nodes, ports) specified in one or more JSON (``*.json``) or YAML (``*.yaml``) files. - Adds a new ``openstack baremetal create`` command. Similar to the ``ironic create`` command, this command creates resources specified in one or more files. (Note that ``openstack baremetal create`` can also be used to create a node via specified node attributes. However, this feature has been deprecated since 1.4.0.) ././@LongLink0000000000000000000000000000015100000000000011212 Lustar 00000000000000python-ironicclient-2.2.0/releasenotes/notes/node-driver-support-storage-interface-e93fc8d4de5d24d6.yamlpython-ironicclient-2.2.0/releasenotes/notes/node-driver-support-storage-interface-e93fc8d4de5d24d6.0000666000175100017510000000103413232474343033310 0ustar zuulzuul00000000000000--- features: - | Adds support for storage_interface for the commands below. They are available starting with ironic API microversion 1.33. * ``openstack baremetal node create`` * ``openstack baremetal node show`` * ``openstack baremetal node set`` * ``openstack baremetal node unset`` * ``openstack baremetal driver list`` * ``openstack baremetal driver show`` * ``ironic node-create`` * ``ironic node-show`` * ``ironic node-update`` * ``ironic driver-list`` * ``ironic driver-show`` ././@LongLink0000000000000000000000000000017000000000000011213 Lustar 00000000000000python-ironicclient-2.2.0/releasenotes/notes/display-empty-string-for-chassis-uuid-if-it-is-empty-a5471c3aa740a27d.yamlpython-ironicclient-2.2.0/releasenotes/notes/display-empty-string-for-chassis-uuid-if-it-is-empty-a50000666000175100017510000000024013232474343033655 0ustar zuulzuul00000000000000--- fixes: - | If a node does not have ``chassis_uuid`` field in the API response, it is added to the output of the node commands as an empty string. python-ironicclient-2.2.0/releasenotes/notes/provision-state-wait-e7ff919ce8e13703.yaml0000666000175100017510000000036713232474343030625 0ustar zuulzuul00000000000000--- features: - Adds ``--wait`` flag to the ``ironic node-set-provision-state`` command and the associated ``node.wait_for_provision_state`` method in the Python API. The flag works with all provision state changes except for ``abort``. python-ironicclient-2.2.0/releasenotes/notes/osc-instance-crash-dump-22634a57104561a5.yaml0000666000175100017510000000020613232474343030603 0ustar zuulzuul00000000000000--- features: - Add a new OSC command for the injection of Non-Masking Interrupts (NMI), "openstack baremetal node inject nmi". python-ironicclient-2.2.0/releasenotes/notes/SHA1-hash-auth-token-f8dce46f854c002c.yaml0000666000175100017510000000012013232474343030237 0ustar zuulzuul00000000000000--- other: - Log the SHA1 hash of X-Auth-Token value prefixed by '{SHA}'. python-ironicclient-2.2.0/releasenotes/notes/osc-node-set-chassis-aae3413489b66b9b.yaml0000666000175100017510000000021213232474343030421 0ustar zuulzuul00000000000000--- features: - The chassis of a node can be set via the new --chassis-uuid optional argument for ``openstack baremetal node set``. python-ironicclient-2.2.0/releasenotes/notes/remove-states-field-0242960d121a09a7.yaml0000666000175100017510000000024313232474343030110 0ustar zuulzuul00000000000000--- upgrade: - Hides 'states' field in 'node create' and 'node show' OSC subcommands output because this field is not meant to be present in the output. python-ironicclient-2.2.0/releasenotes/notes/add-port-internal-info-74a03ebd8b0a3dfc.yaml0000666000175100017510000000027213232474343031154 0ustar zuulzuul00000000000000--- features: - The ``ironic port-show``, ``ironic port-list --detail``, and ``ironic node-port-list --detail`` commands now include the ``internal_info`` field in the output. python-ironicclient-2.2.0/releasenotes/notes/osc-node-list-provisionstate-cd98dbddaad93e96.yaml0000666000175100017510000000015113232474343032551 0ustar zuulzuul00000000000000--- fixes: - Allows all provision states for OSC node list command with --provision-state argument python-ironicclient-2.2.0/releasenotes/notes/osc-node-list-option-driver-a2901ba6b4e1d3b5.yaml0000666000175100017510000000026313232474343032011 0ustar zuulzuul00000000000000--- features: - | For the OSC command ``openstack baremetal node list``, adds the ``--driver `` option to limit the list to nodes with the specified driver. python-ironicclient-2.2.0/releasenotes/notes/ironic-cli-version-a5cdec73d585444d.yaml0000666000175100017510000000221513232474343030271 0ustar zuulzuul00000000000000--- upgrade: - | The default API version for the ``ironic`` command changed from ``1.9`` to ``latest``. ``latest`` is the maximum version understood by both the client and the server. This change makes the CLI automatically pull in new features and changes (including potentially breaking), when talking to new servers. Scripts that used the previous default API version, or that rely on some specific API behavior, should set the ``IRONIC_API_VERSION`` environment variable or use the ``--ironic-api-version`` CLI argument. .. note:: This change does not affect the Python API. features: - | The ``ironic`` command now supports the specification of API version ``1``. The actual version used will be the maximum 1.x version understood by both the client and the server. Thus, it is currently identical to the ``latest`` value. fixes: - | Users of the ``ironic`` command no longer have to specify an explicit API version to use the latest features. The default API version changed from ``1.9`` to ``latest``, which is the maximum version understood by both the client and the server. python-ironicclient-2.2.0/releasenotes/notes/provision-state-adopt-d07b838813cecfb1.yaml0000666000175100017510000000042613232474343031026 0ustar zuulzuul00000000000000--- features: - Support has been added for the node-set-provision-state verb ``adopt`` which requires API version 1.17. This feature allows an operator move a node from ``MANAGABLE`` state to ``ACTIVE`` state without performing cleaning or a deployment operation. python-ironicclient-2.2.0/releasenotes/notes/osc-node-rebuild-configdrive-8979d5b1373e8d5f.yaml0000666000175100017510000000040213232474343032063 0ustar zuulzuul00000000000000--- features: - | Adds the ability to specify a configuration drive when rebuilding a node, via the ``--config-drive`` option to the ``openstack baremetal node rebuild`` command. This is available starting with Bare Metal API version 1.35. ././@LongLink0000000000000000000000000000020700000000000011214 Lustar 00000000000000python-ironicclient-2.2.0/releasenotes/notes/show-required-arguments-in-help-commands-of-node-create-port-create-b213bb28bcc94743.yamlpython-ironicclient-2.2.0/releasenotes/notes/show-required-arguments-in-help-commands-of-node-create0000666000175100017510000000036613232474343034040 0ustar zuulzuul00000000000000fixes: - | Where applicable, help for commands will output the required arguments in a separate section from the optional arguments. For example, the commands ``ironic help node-create`` and ``ironic help port-create``. python-ironicclient-2.2.0/releasenotes/notes/prelude-2-0-release-ee44150902d3d399.yaml0000666000175100017510000000147013232474343027710 0ustar zuulzuul00000000000000--- prelude: | The 2.0 release has three major changes: * The default API version for the ``openstack baremetal`` and ``ironic`` commands changed from ``1.9`` to ``latest``. ``latest`` is the maximum version understood by both the client and the server. This change makes the CLI automatically pull in new features and changes (including potentially breaking), from servers. * The ``python-ironicclient`` package no longer includes the ``python-openstackclient`` (OSC) package as a requirement. ``python-openstackclient`` is needed if using the ``openstack baremetal`` CLI. * The ``ironic`` command line interface (``ironic`` commands) is deprecated and will be removed in the OpenStack S* release. Please use the ``openstack baremetal`` CLI instead. python-ironicclient-2.2.0/releasenotes/notes/.placeholder0000666000175100017510000000000013232474343023641 0ustar zuulzuul00000000000000python-ironicclient-2.2.0/releasenotes/notes/add_api_versions-a59e5b6899833c33.yaml0000666000175100017510000000032313232474343027662 0ustar zuulzuul00000000000000--- features: - Add support to the openstackclient plugin for all Ironic API versions of the form 1.x, up to the latest known minor version. The regular Ironic CLI already supports all known versions. ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000python-ironicclient-2.2.0/releasenotes/notes/osc-plugin-chassis-create-show-fix-ee276d707c5a5bdf.yamlpython-ironicclient-2.2.0/releasenotes/notes/osc-plugin-chassis-create-show-fix-ee276d707c5a5bdf.yam0000666000175100017510000000022613232474343033200 0ustar zuulzuul00000000000000--- upgrade: - Hide nodes field in 'chasiss create' and 'chassis show' OSC subcommand output because this field is not meant for CLI users. python-ironicclient-2.2.0/releasenotes/notes/index-error-no-endpoint-eb281187f80a9aa4.yaml0000666000175100017510000000025213232474343031156 0ustar zuulzuul00000000000000--- fixes: - Fail with EndpointException instead of IndexError while creating v1 client without ``endpoint`` argument if os_ironic_api_version isn't specified. ././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000python-ironicclient-2.2.0/releasenotes/notes/allow-client-to-request-list-of-versions-88f019cad76e6464.yamlpython-ironicclient-2.2.0/releasenotes/notes/allow-client-to-request-list-of-versions-88f019cad76e640000666000175100017510000000043613232474343033240 0ustar zuulzuul00000000000000--- features: - | The ``os_ironic_api_version`` parameter now accepts a list of REST API micro-versions to attempt to negotiate with the remote server. The highest available microversion in the list will be negotiated for the remaining lifetime of the client session. python-ironicclient-2.2.0/releasenotes/notes/negative-wrap-fix-4197e91b2ecfb722.yaml0000666000175100017510000000036613232474343030036 0ustar zuulzuul00000000000000--- fixes: - | ``--wrap`` CLI argument for ``ironic driver-properties`` and ``ironic driver-raid-logical-disk-properties`` commands now takes only non-negative integers as input. An error is shown if a negative value is passed. python-ironicclient-2.2.0/releasenotes/notes/add-portgroups-support-c3cf3826093ee815.yaml0000666000175100017510000000055113232474343031104 0ustar zuulzuul00000000000000--- features: - | Extends the Ironic CLI with new commands: * ironic portgroup-create * ironic portgroup-show * ironic portgroup-list * ironic portgroup-delete * ironic portgroup-update * ironic portgroup-port-list Also extends ironic port-create with --portgroup. Portgroup support was added in Ironic API version 1.24. python-ironicclient-2.2.0/releasenotes/notes/osc-node-list-chassis-091d080684cdccf8.yaml0000666000175100017510000000031313232474343030604 0ustar zuulzuul00000000000000--- features: - | Adds an option ``--chassis `` to the ``openstack baremetal node list`` command. It provides the ability to get a list of the nodes of the specified chassis. python-ironicclient-2.2.0/releasenotes/notes/add-volume-target-api-e062303f4b3b40ef.yaml0000666000175100017510000000107313232474343030545 0ustar zuulzuul00000000000000--- features: - | Adds these python API client methods to support volume target resources (available starting with API version 1.32): * ``client.volume_target.create`` for creating a volume target * ``client.volume_target.list`` for listing volume targets * ``client.volume_target.get`` for getting a volume target * ``client.volume_target.update`` for updating a volume target * ``client.volume_target.delete`` for deleting a volume target * ``client.node.list_volume_targets`` for getting volume targets associated with a node python-ironicclient-2.2.0/releasenotes/notes/no-osc-requirement-411f25fd10f18caa.yaml0000666000175100017510000000107313232474343030270 0ustar zuulzuul00000000000000--- upgrade: - | ``python-ironicclient`` package no longer has the ``python-openstackclient`` package (OSC) as a requirement. Users installing only the ``python-ironicclient`` package will not automatically get access to ``openstack baremetal ...`` OSC commands. To have them available, the ``python-openstackclient`` package must be installed separately, or, when installing ``python-ironicclient`` via ``pip``, the new ``cli`` extra can be used to also install OSC: .. code-block:: shell pip install python-ironicclient[cli] python-ironicclient-2.2.0/releasenotes/notes/bug-1524745-adds-node-create-args-a7ace744515e5943.yaml0000666000175100017510000000042013232474343032047 0ustar zuulzuul00000000000000--- features: - Add new arguments to the CLI node-create to allow selecting boot, console, deploy, inspect, management, power, raid, and vendor hardware interfaces, when hardware types are used. They are available starting with ironic API microversion 1.31. python-ironicclient-2.2.0/releasenotes/notes/add-node-resource-class-6040d1d6c734522c.yaml0000666000175100017510000000017013232474343030720 0ustar zuulzuul00000000000000--- features: - Adds support for the new ``node.resource_class`` field, which was introduced in API version 1.21. python-ironicclient-2.2.0/releasenotes/notes/osc-soft-reboot-poweroff-121b8043567f54a9.yaml0000666000175100017510000000016013232474343031120 0ustar zuulzuul00000000000000--- features: - Enhances OSC to support soft reboot and soft power off with power control timeout option. python-ironicclient-2.2.0/releasenotes/notes/osc-node-list-unassociated-60e46958a0abc3e5.yaml0000666000175100017510000000032313232474343031626 0ustar zuulzuul00000000000000--- features: - | Adds an option ``--unassociated`` to the ``openstack baremetal node list`` command. It provides the ability to get a list of the nodes that are not associated with instances. python-ironicclient-2.2.0/releasenotes/notes/latest-default-41fdcc49701c4d70.yaml0000666000175100017510000000236613232474343027412 0ustar zuulzuul00000000000000--- upgrade: - | The default API version for the bare metal OSC client (``openstack baremetal`` commands) changed from ``1.9`` to ``latest``. ``latest`` is the maximum version understood by both the client and the server. This change makes the CLI automatically pull in new features and changes (including potentially breaking), when talking to new servers. Scripts that used the previous default API version, or that rely on some specific API behavior, should set the ``OS_BAREMETAL_API_VERSION`` environment variable or use the ``--os-baremetal-api-version`` CLI argument. .. note:: This change does not affect the Python API. features: - | The bare metal OSC client (``openstack baremetal`` commands) now supports the specification of API version ``1``. The actual version used will be the maximum 1.x version understood by both the client and the server. Thus, it is currently identical to the ``latest`` value. fixes: - | Users of the ``openstack baremetal`` commands no longer have to specify an explicit API version to use the latest features. The default API version changed from ``1.9`` to ``latest``, which is the maximum version understood by both the client and the server. python-ironicclient-2.2.0/releasenotes/notes/osc-default-microver-172d6e69316e70c1.yaml0000666000175100017510000000021113232474343030357 0ustar zuulzuul00000000000000--- fixes: - Fixes the OpenStackClient plugin so that it uses the same default API version as the Ironic CLI (1.9 instead of 1.6). python-ironicclient-2.2.0/releasenotes/notes/add-portgroups-to-create-command-6d685277f7af79df.yaml0000666000175100017510000000020113232474343032764 0ustar zuulzuul00000000000000--- features: - | Supports creation of port groups via ``ironic create`` and ``openstack baremetal create`` commands. python-ironicclient-2.2.0/releasenotes/notes/latest-baremetal-api-version-a20e3099e3b97a1b.yaml0000666000175100017510000000044013232474343032142 0ustar zuulzuul00000000000000--- features: - For OSC commands, the --os-baremetal-api-version optional argument (or OS_BAREMETAL_API_VERSION environment variable) can have the value 'latest'. If set to 'latest', the latest API version known by client will be used in requests to the Bare Metal service. python-ironicclient-2.2.0/releasenotes/notes/add-volume-target-cli-e062303f4b3b40f0.yaml0000666000175100017510000000123513232474343030456 0ustar zuulzuul00000000000000--- features: - | Adds these ``openstack baremetal`` and ``ironic`` CLI commands for volume target resources. * ``openstack baremetal volume target create`` * ``openstack baremetal volume target list`` * ``openstack baremetal volume target show`` * ``openstack baremetal volume target set`` * ``openstack baremetal volume target unset`` * ``openstack baremetal volume target delete`` * ``ironic volume-target-create`` * ``ironic volume-target-list`` * ``ironic volume-target-show`` * ``ironic volume-target-update`` * ``ironic volume-target-delete`` They are available starting with ironic API microversion 1.32. python-ironicclient-2.2.0/releasenotes/notes/no-resource-attributeerror-d0cb327abab7dcc0.yaml0000666000175100017510000000063713232474343032267 0ustar zuulzuul00000000000000--- fixes: - For node resources that had no boot devices, no supported boot devices, or no passthru methods (and driver resources with no properties or no passthru methods), issuing a request to get that information (for example, 'ironic driver-get-vendor-passthru-methods fake') would result in the error "'NoneType' has no attribute 'to_dict'". This is fixed; an empty list is now returned. python-ironicclient-2.2.0/releasenotes/notes/latest-renegotiation-55daa01b3fc261be.yaml0000666000175100017510000000063513232474343030755 0ustar zuulzuul00000000000000--- fixes: - | When using ``--os-baremetal-api-version=latest`` (for ``openstack baremetal`` CLI) or ``--ironic-api-version=latest`` (for ``ironic`` CLI), the resulting API version is now the maximum API version supported by both the client and the server. Previously, the maximum API version supported by the client was used, which prevented ``latest`` from working with older servers. python-ironicclient-2.2.0/releasenotes/notes/continue-del-next-node-8827e67e1c41a0a5.yaml0000666000175100017510000000012613232474343030675 0ustar zuulzuul00000000000000--- fixes: - when deleting multiple nodes, continue with deletes even if one fails. python-ironicclient-2.2.0/releasenotes/notes/switch-requests-8304d4465a8976b1.yaml0000666000175100017510000000030513232474343027431 0ustar zuulzuul00000000000000--- features: - Switch HTTP client to requests lib. It allows client to work with API behind the proxy, configure proxies by setting the environment variables HTTP_PROXY and HTTPS_PROXY. python-ironicclient-2.2.0/releasenotes/notes/add-neutron-integration-fields-cee7596c49722de6.yaml0000666000175100017510000000066013232474343032522 0ustar zuulzuul00000000000000--- features: - | Add support of new fields: * ``node.network_interface`` is introduced in API 1.20, specifies the network interface to use for a node. * ``port.local_link_connection`` contains the port binding profile. * ``port.pxe_enabled`` indicates whether PXE is enabled for the port. The ``port.local_link_connection`` and ``port.pxe_enabled`` fields were introduced in API 1.19. ././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000python-ironicclient-2.2.0/releasenotes/notes/bug-1745099-allow-integer-portgroup-mode-6be4d3b35e216486.yamlpython-ironicclient-2.2.0/releasenotes/notes/bug-1745099-allow-integer-portgroup-mode-6be4d3b35e21640000666000175100017510000000036413232474373032447 0ustar zuulzuul00000000000000--- fixes: - | Fixes `bug 1745099 `_, which prevented a port group's mode from being set to an integer value via the ``openstack baremetal port group set`` command. python-ironicclient-2.2.0/releasenotes/notes/osc-plugin-9b5344aceb886cc1.yaml0000666000175100017510000000016213232474343026633 0ustar zuulzuul00000000000000--- features: - | Extend the OpenStackClient plugin with new commands: * openstack baremetal port show python-ironicclient-2.2.0/releasenotes/notes/osc-max-microver-22-dc0d91a62f03a2e6.yaml0000666000175100017510000000014713232474343030154 0ustar zuulzuul00000000000000--- fixes: - Fixes the OpenStackClient plugin so that microversions 1.21 and 1.22 are supported. python-ironicclient-2.2.0/releasenotes/notes/fix-token-with-vhosts-5d0a6d53e807fa5e.yaml0000666000175100017510000000050713232474343030755 0ustar zuulzuul00000000000000--- fixes: - | Fixes a bug where the client could not access the ironic API service when the client was instantiated with a keystone token and an ironic API endpoint that included a virtual host (such as "http://hostname/baremetal"). For more details, see `bug 1721599 `_. python-ironicclient-2.2.0/releasenotes/notes/deprecate-ironic-cli-686b7a238ddf3e25.yaml0000666000175100017510000000034713232474343030467 0ustar zuulzuul00000000000000--- deprecations: - | The ``ironic`` command line interface (``ironic`` commands) is deprecated and will be removed in the OpenStack S* release. Please use the ``openstack baremetal`` command line interface instead. python-ironicclient-2.2.0/releasenotes/notes/list-nodes-by-driver-b1e1e1018077089b.yaml0000666000175100017510000000013113232474343030277 0ustar zuulzuul00000000000000--- features: - Add support for filtering nodes using the same driver in list command. ././@LongLink0000000000000000000000000000015500000000000011216 Lustar 00000000000000python-ironicclient-2.2.0/releasenotes/notes/retry-on-keystone-auth-retriable-failures-91c08b9f8bdab7f3.yamlpython-ironicclient-2.2.0/releasenotes/notes/retry-on-keystone-auth-retriable-failures-91c08b9f8bdab0000666000175100017510000000046513232474343033520 0ustar zuulzuul00000000000000--- fixes: - The client will now retry on keystoneauth retriable connection failures if retries are enabled for a particular request. This ensures that on a temporary network outage to the keystone auth services a request we be retried the requested number of times before raising an exception. python-ironicclient-2.2.0/releasenotes/source/0000775000175100017510000000000013232474761021542 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/releasenotes/source/newton.rst0000666000175100017510000000023213232474343023601 0ustar zuulzuul00000000000000=================================== Newton Series Release Notes =================================== .. release-notes:: :branch: origin/stable/newton python-ironicclient-2.2.0/releasenotes/source/pike.rst0000666000175100017510000000021713232474343023222 0ustar zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike python-ironicclient-2.2.0/releasenotes/source/_templates/0000775000175100017510000000000013232474761023677 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/releasenotes/source/_templates/.placeholder0000666000175100017510000000000013232474343026146 0ustar zuulzuul00000000000000python-ironicclient-2.2.0/releasenotes/source/conf.py0000666000175100017510000002163213232474343023043 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 Client Release Notes documentation build configuration file, created # by sphinx-quickstart on Wed Dec 23 22:54:56 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 = [ 'openstackdocstheme', 'reno.sphinxext', ] # openstackdocstheme options repository_name = 'openstack/python-ironicclient' bug_project = 'python-ironicclient' bug_tag = '' # 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 Client Release Notes' copyright = u'2015, Ironic Developers' # Release notes are version independent. # The short X.Y version. version = '' # The full version, including alpha/beta/rc tags. release = '' # 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. html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom 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 = '%Y-%m-%d %H:%M' # 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 = 'IronicClientReleaseNotesdoc' # -- 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', 'IronicClientReleaseNotes.tex', u'Ironic Client Release Notes Documentation', u'Ironic 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', 'ironicclientreleasenotes', u'Ironic Client Release Notes Documentation', [u'Ironic 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', 'IronicClientReleaseNotes', u'Ironic Client Release Notes Documentation', u'Ironic Developers', 'IronicClientReleaseNotes', 'Bare metal provisioning service client.', '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-ironicclient-2.2.0/releasenotes/source/ocata.rst0000666000175100017510000000023013232474343023354 0ustar zuulzuul00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata python-ironicclient-2.2.0/releasenotes/source/unreleased.rst0000666000175100017510000000016013232474343024416 0ustar zuulzuul00000000000000============================== Current Series Release Notes ============================== .. release-notes:: python-ironicclient-2.2.0/releasenotes/source/index.rst0000666000175100017510000000025313232474343023401 0ustar zuulzuul00000000000000============================= Ironic Client Release Notes ============================= .. toctree:: :maxdepth: 1 unreleased pike ocata newton mitaka python-ironicclient-2.2.0/releasenotes/source/mitaka.rst0000666000175100017510000000023213232474343023535 0ustar zuulzuul00000000000000=================================== Mitaka Series Release Notes =================================== .. release-notes:: :branch: origin/stable/mitaka python-ironicclient-2.2.0/releasenotes/source/_static/0000775000175100017510000000000013232474761023170 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/releasenotes/source/_static/.placeholder0000666000175100017510000000000013232474343025437 0ustar zuulzuul00000000000000python-ironicclient-2.2.0/tools/0000775000175100017510000000000013232474761016711 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/tools/ironic.bash_completion0000666000175100017510000000176413232474343023272 0ustar zuulzuul00000000000000_ironic_opts="" # lazy init _ironic_flags="" # lazy init _ironic_opts_exp="" # lazy init _ironic() { local cur prev nbc cflags COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" if [ "x$_ironic_opts" == "x" ] ; then nbc="`ironic bash-completion | sed -e "s/ *-h */ /" -e "s/ *-i */ /"`" _ironic_opts="`echo "$nbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/ */ /g"`" _ironic_flags="`echo " $nbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/ */ /g"`" _ironic_opts_exp="`echo "$_ironic_opts" | tr ' ' '|'`" fi if [[ " ${COMP_WORDS[@]} " =~ " "($_ironic_opts_exp)" " && "$prev" != "help" ]] ; then COMPLETION_CACHE=$HOME/.cache/python-ironicclient/*/*-cache cflags="$_ironic_flags "$(cat $COMPLETION_CACHE 2> /dev/null | tr '\n' ' ') COMPREPLY=($(compgen -W "${cflags}" -- ${cur})) else COMPREPLY=($(compgen -W "${_ironic_opts}" -- ${cur})) fi return 0 } complete -F _ironic ironic python-ironicclient-2.2.0/tools/install_venv_common.py0000666000175100017510000001615513232474343023345 0ustar zuulzuul00000000000000# Copyright 2013 OpenStack Foundation # Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Provides methods needed by installation script for OpenStack development virtual environments. Since this script is used to bootstrap a virtualenv from the system's Python environment, it should be kept strictly compatible with Python 2.7. Synced in from openstack-common """ from __future__ import print_function import optparse import os import subprocess import sys class InstallVenv(object): def __init__(self, root, venv, requirements, test_requirements, py_version, project): self.root = root self.venv = venv self.requirements = requirements self.test_requirements = test_requirements self.py_version = py_version self.project = project def die(self, message, *args): print(message % args, file=sys.stderr) sys.exit(1) def check_python_version(self): if sys.version_info < (2, 7): self.die("Need Python Version >= 2.7") def run_command_with_code(self, cmd, redirect_output=True, check_exit_code=True): """Runs a command in an out-of-process shell. Returns the output of that command. Working directory is self.root. """ if redirect_output: stdout = subprocess.PIPE else: stdout = None proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout) output = proc.communicate()[0] if check_exit_code and proc.returncode != 0: self.die('Command "%s" failed.\n%s', ' '.join(cmd), output) return (output, proc.returncode) def run_command(self, cmd, redirect_output=True, check_exit_code=True): return self.run_command_with_code(cmd, redirect_output, check_exit_code)[0] def get_distro(self): if (os.path.exists('/etc/fedora-release') or os.path.exists('/etc/redhat-release')): return Fedora( self.root, self.venv, self.requirements, self.test_requirements, self.py_version, self.project) else: return Distro( self.root, self.venv, self.requirements, self.test_requirements, self.py_version, self.project) def check_dependencies(self): self.get_distro().install_virtualenv() def create_virtualenv(self, no_site_packages=True): """Creates the virtual environment and installs PIP. Creates the virtual environment and installs PIP only into the virtual environment. """ if not os.path.isdir(self.venv): print('Creating venv...', end=' ') if no_site_packages: self.run_command(['virtualenv', '-q', '--no-site-packages', self.venv]) else: self.run_command(['virtualenv', '-q', self.venv]) print('done.') else: print("venv already exists...") pass def pip_install(self, *args): self.run_command(['tools/with_venv.sh', 'pip', 'install', '--upgrade'] + list(args), redirect_output=False) def install_dependencies(self): print('Installing dependencies with pip (this can take a while)...') # First things first, make sure our venv has the latest pip and # setuptools. self.pip_install('pip>=1.3') self.pip_install('setuptools') self.pip_install('-r', self.requirements) self.pip_install('-r', self.test_requirements) def post_process(self): self.get_distro().post_process() def parse_args(self, argv): """Parses command-line arguments.""" parser = optparse.OptionParser() parser.add_option('-n', '--no-site-packages', action='store_true', help="Do not inherit packages from global Python " "install") return parser.parse_args(argv[1:])[0] class Distro(InstallVenv): def check_cmd(self, cmd): return bool(self.run_command(['which', cmd], check_exit_code=False).strip()) def install_virtualenv(self): if self.check_cmd('virtualenv'): return if self.check_cmd('easy_install'): print('Installing virtualenv via easy_install...', end=' ') if self.run_command(['easy_install', 'virtualenv']): print('Succeeded') return else: print('Failed') self.die('ERROR: virtualenv not found.\n\n%s development' ' requires virtualenv, please install it using your' ' favorite package management tool' % self.project) def post_process(self): """Any distribution-specific post-processing gets done here. In particular, this is useful for applying patches to code inside the venv. """ pass class Fedora(Distro): """This covers all Fedora-based distributions. Includes: Fedora, RHEL, CentOS, Scientific Linux """ def check_pkg(self, pkg): return self.run_command_with_code(['rpm', '-q', pkg], check_exit_code=False)[1] == 0 def apply_patch(self, originalfile, patchfile): self.run_command(['patch', '-N', originalfile, patchfile], check_exit_code=False) def install_virtualenv(self): if self.check_cmd('virtualenv'): return if not self.check_pkg('python-virtualenv'): self.die("Please install 'python-virtualenv'.") super(Fedora, self).install_virtualenv() def post_process(self): """Workaround for a bug in eventlet. This currently affects RHEL6.1, but the fix can safely be applied to all RHEL and Fedora distributions. This can be removed when the fix is applied upstream. Nova: https://bugs.launchpad.net/nova/+bug/884915 Upstream: https://bitbucket.org/eventlet/eventlet/issue/89 RHEL: https://bugzilla.redhat.com/958868 """ # Install "patch" program if it's not there if not self.check_pkg('patch'): self.die("Please install 'patch'.") # Apply the eventlet patch self.apply_patch(os.path.join(self.venv, 'lib', self.py_version, 'site-packages', 'eventlet/green/subprocess.py'), 'contrib/redhat-eventlet.patch') python-ironicclient-2.2.0/tools/with_venv.sh0000777000175100017510000000033213232474343021255 0ustar zuulzuul00000000000000#!/bin/bash tools_path=${tools_path:-$(dirname $0)} venv_path=${venv_path:-${tools_path}} venv_dir=${venv_name:-/../.venv} TOOLS=${tools_path} VENV=${venv:-${venv_path}/${venv_dir}} source ${VENV}/bin/activate && "$@" python-ironicclient-2.2.0/tools/run_functional.sh0000777000175100017510000000124213232474343022273 0ustar zuulzuul00000000000000#!/bin/bash FUNC_TEST_DIR=$(dirname $0)/../ironicclient/tests/functional/ CONFIG_FILE=$FUNC_TEST_DIR/test.conf if [[ -n "$OS_AUTH_TOKEN" ]] && [[ -n "$IRONIC_URL" ]]; then cat <$CONFIG_FILE [functional] api_version = 1 auth_strategy=noauth os_auth_token=$OS_AUTH_TOKEN ironic_url=$IRONIC_URL END else cat <$CONFIG_FILE [functional] api_version = 1 os_auth_url=$OS_AUTH_URL os_identity_api_version=$OS_IDENTITY_API_VERSION os_username=$OS_USERNAME os_password=$OS_PASSWORD os_project_name=$OS_PROJECT_NAME os_user_domain_id=$OS_USER_DOMAIN_ID os_project_domain_id=$OS_PROJECT_DOMAIN_ID os_service_type=baremetal os_endpoint_type=public END fi tox -e functional python-ironicclient-2.2.0/tools/__init__.py0000666000175100017510000000000013232474343021006 0ustar zuulzuul00000000000000python-ironicclient-2.2.0/AUTHORS0000664000175100017510000001171213232474760016622 0ustar zuulzuul00000000000000Adam Gandelman Alexey Galkin Aline Bousquet Andreas Jaeger Andreas Jaeger Andrey Kurilin Anh Tran Anton Arefiev Arun S A G Ben Nemec Brad P. Crochet ChangBo Guo(gcb) Chaozhe.Chen ChenZheng Chris Krelle Christian Berendt Clark Boylan Clif Houck Dao Cong Tien Davanum Srinivas David Hu David Shrewsbury Devananda van der Veen Diego de Lima Pereira Dirk Mueller Dmitry Tantsur Dmitry Tantsur Doug Hellmann Flavio Percoco Florian Fuchs Galyna Zholtkevych Ghe Rivero Gábor Antal Hangdong Zhang HaoZhi, Cui Haomeng, Wang He Yongli Himanshu Kumar Hironori Shiina Jamie Lennox Jamie Lennox Jeremy Stanley Jim Rollenhagen John L. Villalovos John Trowbridge JuPing Julia Kreger KATO Tomoyuki KaiFeng Wang Kaifeng Wang Kan Kevin McDonald Kui Shi Kyrylo Romanenko Lin Tan Lin Tan LiuNanke Lokesh S Lucas Alvares Gomes Luong Anh Tuan M V P Nitesh Marc Aubry Mario Villaplana Mark Goddard Martin Geisler Mathieu Gagné Matt Keenan Maxime Belanger Michael Davies Michael Johnson Michael Turek Mikhail Durnosvistov Miles Gould Monty Taylor Motohiro OTSUKA Nam Nguyen Hoai Naohiro Tamura Nisha Agarwal Ondřej Nový OpenStack Release Bot Pavlo Shchelokovskyy Pavlo Shchelokovskyy Rakesh H S Ramakrishnan G Rodion Promyshlennikov Ruby Loo Ruby Loo Rui Chen Sam Betts Sascha Peilicke Sergey Lupersolsky Sergey Turivnyi Sergii Turivnyi Shuquan Huang Sinval Vieira SofiiaAndriichenko Steve Martinelli Tang Chen Tang Chen Tao Li Ukesh Kumar Vasudevan Vadim Hmyrov Vasyl Saienko Victor Sergeyev Vladyslav Drok William Stevenson Yang Hongyang Yuiko Takada YuikoTakada Yuriy Zveryanskyy Yushiro FURUKAWA Zhenguo Niu Zuul deepakmourya ericxiett fpxie jiang wei jiangfei linbing linggao lingyongxu liuqing llg8212 max_lobur melissaml qinchunhua ricolin sandriichenko shu-mutou sonu.kumar venkatamahesh vishal mahajan xiexs zhengchuan hu python-ironicclient-2.2.0/ironicclient/0000775000175100017510000000000013232474761020233 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/v1/0000775000175100017510000000000013232474761020561 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/v1/utils.py0000666000175100017510000000415613232474343022277 0ustar zuulzuul00000000000000# Copyright 2016 Intel 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. HTTP_METHODS = ['POST', 'PUT', 'GET', 'DELETE', 'PATCH'] BOOT_DEVICES = ['pxe', 'disk', 'cdrom', 'bios', 'safe', 'wanboot'] # Polling intervals in seconds. _LONG_ACTION_POLL_INTERVAL = 10 _SHORT_ACTION_POLL_INTERVAL = 2 # This dict acts as both list of possible provision actions and arguments for # wait_for_provision_state invocation. PROVISION_ACTIONS = { 'active': {'expected_state': 'active', 'poll_interval': _LONG_ACTION_POLL_INTERVAL}, 'deleted': {'expected_state': 'available', 'poll_interval': _LONG_ACTION_POLL_INTERVAL}, 'rebuild': {'expected_state': 'active', 'poll_interval': _LONG_ACTION_POLL_INTERVAL}, 'inspect': {'expected_state': 'manageable', # This is suboptimal for in-band inspection, but it's probably # not worth making people wait 10 seconds for OOB inspection 'poll_interval': _SHORT_ACTION_POLL_INTERVAL}, 'provide': {'expected_state': 'available', # This assumes cleaning is in place 'poll_interval': _LONG_ACTION_POLL_INTERVAL}, 'manage': {'expected_state': 'manageable', 'poll_interval': _SHORT_ACTION_POLL_INTERVAL}, 'clean': {'expected_state': 'manageable', 'poll_interval': _LONG_ACTION_POLL_INTERVAL}, 'adopt': {'expected_state': 'active', 'poll_interval': _SHORT_ACTION_POLL_INTERVAL}, 'abort': None, # no support for --wait in abort } PROVISION_STATES = list(PROVISION_ACTIONS) python-ironicclient-2.2.0/ironicclient/v1/portgroup_shell.py0000666000175100017510000002424513232474343024370 0ustar zuulzuul00000000000000# Copyright 2015 SAP Ltd. # 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. from ironicclient.common.apiclient import exceptions from ironicclient.common import cliutils from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient.v1 import resource_fields as res_fields def _print_portgroup_show(portgroup, fields=None, json=False): if fields is None: fields = res_fields.PORTGROUP_DETAILED_RESOURCE.fields data = dict([(f, getattr(portgroup, f, '')) for f in fields]) cliutils.print_dict(data, wrap=72, json_flag=json) @cliutils.arg( 'portgroup', metavar='', help="Name or UUID of the portgroup " "(or MAC address if --address is specified).") @cliutils.arg( '--address', dest='address', action='store_true', default=False, help=' is the MAC address (instead of the UUID) of the portgroup.') @cliutils.arg( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], help="One or more portgroup fields. Only these fields will be fetched " "from the server.") def do_portgroup_show(cc, args): """Show detailed information about a portgroup.""" fields = args.fields[0] if args.fields else None utils.check_for_invalid_fields( fields, res_fields.PORTGROUP_DETAILED_RESOURCE.fields) if args.address: portgroup = cc.portgroup.get_by_address(args.portgroup, fields=fields) else: utils.check_empty_arg(args.portgroup, '') portgroup = cc.portgroup.get(args.portgroup, fields=fields) _print_portgroup_show(portgroup, fields=fields, json=args.json) @cliutils.arg( '--detail', dest='detail', action='store_true', default=False, help="Show detailed information about portgroups.") @cliutils.arg( '-n', '--node', dest='node', metavar='', help='UUID of the node that this portgroup belongs to.') @cliutils.arg( '-a', '--address', metavar='', help='Only show information for the portgroup with this MAC address.') @cliutils.arg( '--limit', metavar='', type=int, help='Maximum number of portgroups to return per request, ' '0 for no limit. Default is the maximum number used ' 'by the Ironic API Service.') @cliutils.arg( '--marker', metavar='', help='Portgroup UUID (for example, of the last portgroup in the list ' 'from a previous request). ' 'Returns the list of portgroups after this UUID.') @cliutils.arg( '--sort-key', metavar='', help='Portgroup field that will be used for sorting.') @cliutils.arg( '--sort-dir', metavar='', choices=['asc', 'desc'], help='Sort direction: "asc" (the default) or "desc".') @cliutils.arg( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], help="One or more portgroup fields. Only these fields will be fetched " "from the server. Can not be used when '--detail' is specified.") def do_portgroup_list(cc, args): """List the portgroups.""" params = {} if args.address is not None: params['address'] = args.address if args.node is not None: params['node'] = args.node if args.detail: fields = res_fields.PORTGROUP_DETAILED_RESOURCE.fields field_labels = res_fields.PORTGROUP_DETAILED_RESOURCE.labels elif args.fields: utils.check_for_invalid_fields( args.fields[0], res_fields.PORTGROUP_DETAILED_RESOURCE.fields) resource = res_fields.Resource(args.fields[0]) fields = resource.fields field_labels = resource.labels else: fields = res_fields.PORTGROUP_RESOURCE.fields field_labels = res_fields.PORTGROUP_RESOURCE.labels sort_fields = res_fields.PORTGROUP_DETAILED_RESOURCE.sort_fields sort_field_labels = res_fields.PORTGROUP_DETAILED_RESOURCE.sort_labels params.update(utils.common_params_for_list(args, sort_fields, sort_field_labels)) portgroup = cc.portgroup.list(**params) cliutils.print_list(portgroup, fields, field_labels=field_labels, sortby_index=None, json_flag=args.json) @cliutils.arg( '-a', '--address', metavar='
', help='MAC address for this portgroup.') @cliutils.arg( '-n', '--node', dest='node_uuid', metavar='', required=True, help='UUID of the node that this portgroup belongs to.') @cliutils.arg( '--name', metavar="", help='Name for the portgroup.') @cliutils.arg( '-e', '--extra', metavar="", action='append', help="Record arbitrary key/value metadata. " "Can be specified multiple times.") @cliutils.arg( '--standalone-ports-supported', metavar="", help='Specifies whether ports from this portgroup can be used ' 'in stand alone mode.') @cliutils.arg( '-u', '--uuid', metavar='', help="UUID of the portgroup.") @cliutils.arg( '-m', '--mode', metavar='', help="Portgroup mode. For possible values, refer to " "https://www.kernel.org/doc/Documentation/networking/bonding.txt") @cliutils.arg( '-p', '--properties', metavar="", action='append', help="Record key/value properties related to this portgroup's " "configuration.") def do_portgroup_create(cc, args): """Create a new portgroup.""" field_list = ['address', 'extra', 'node_uuid', 'name', 'uuid', 'standalone_ports_supported', 'mode', 'properties'] fields = dict((k, v) for (k, v) in vars(args).items() if k in field_list and not (v is None)) fields = utils.args_array_to_dict(fields, 'extra') fields = utils.args_array_to_dict(fields, 'properties') portgroup = cc.portgroup.create(**fields) data = dict([(f, getattr(portgroup, f, '')) for f in field_list]) cliutils.print_dict(data, wrap=72, json_flag=args.json) @cliutils.arg( '--detail', dest='detail', action='store_true', default=False, help="Show detailed information about the ports.") @cliutils.arg( '--limit', metavar='', type=int, help='Maximum number of ports to return per request, ' '0 for no limit. Default is the maximum number used ' 'by the Ironic API Service.') @cliutils.arg( '--marker', metavar='', help='Port UUID (for example, of the last port in the list from a ' 'previous request). Returns the list of ports after this UUID.') @cliutils.arg( '--sort-key', metavar='', help='Port field that will be used for sorting.') @cliutils.arg( '--sort-dir', metavar='', choices=['asc', 'desc'], help='Sort direction: "asc" (the default) or "desc".') @cliutils.arg( 'portgroup', metavar='', help="Name or UUID of the portgroup.") @cliutils.arg( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], help="One or more port fields. Only these fields will be fetched from " "the server. Can not be used when '--detail' is specified.") def do_portgroup_port_list(cc, args): """List the ports associated with a portgroup.""" if args.detail: fields = res_fields.PORT_DETAILED_RESOURCE.fields field_labels = res_fields.PORT_DETAILED_RESOURCE.labels elif args.fields: utils.check_for_invalid_fields( args.fields[0], res_fields.PORT_DETAILED_RESOURCE.fields) resource = res_fields.Resource(args.fields[0]) fields = resource.fields field_labels = resource.labels else: fields = res_fields.PORT_RESOURCE.fields field_labels = res_fields.PORT_RESOURCE.labels sort_fields = res_fields.PORT_DETAILED_RESOURCE.sort_fields sort_field_labels = res_fields.PORT_DETAILED_RESOURCE.sort_labels params = utils.common_params_for_list(args, sort_fields, sort_field_labels) ports = cc.portgroup.list_ports(args.portgroup, **params) cliutils.print_list(ports, fields, field_labels=field_labels, sortby_index=None, json_flag=args.json) @cliutils.arg('portgroup', metavar='', nargs='+', help="UUID or Name of the portgroup.") def do_portgroup_delete(cc, args): """Delete a portgroup.""" failures = [] for p in args.portgroup: try: cc.portgroup.delete(p) print('Deleted portgroup %s' % p) except exceptions.ClientException as e: failures.append(_("Failed to delete portgroup %(pg)s: %(error)s") % {'pg': p, 'error': e}) if failures: raise exceptions.ClientException("\n".join(failures)) @cliutils.arg('portgroup', metavar='', help="UUID or Name of the portgroup.") @cliutils.arg( 'op', metavar='', choices=['add', 'replace', 'remove'], help="Operation: 'add', 'replace', or 'remove'.") @cliutils.arg( 'attributes', metavar='', nargs='+', action='append', default=[], help="Attribute to add, replace, or remove. Can be specified multiple " "times. For 'remove', only is necessary.") def do_portgroup_update(cc, args): """Update information about a portgroup.""" patch = utils.args_array_to_patch(args.op, args.attributes[0]) portgroup = cc.portgroup.update(args.portgroup, patch) _print_portgroup_show(portgroup, json=args.json) python-ironicclient-2.2.0/ironicclient/v1/driver_shell.py0000666000175100017510000001343213232474343023616 0ustar zuulzuul00000000000000# Copyright 2013 Red Hat, Inc. # 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 argparse from ironicclient.common import cliutils from ironicclient.common import utils from ironicclient.v1 import resource_fields as res_fields from ironicclient.v1 import utils as v1_utils def _print_driver_show(driver, json=False): fields = res_fields.DRIVER_DETAILED_RESOURCE.fields data = dict([(f, getattr(driver, f, '')) for f in fields]) cliutils.print_dict(data, wrap=72, json_flag=json) @cliutils.arg('-t', '--type', metavar='', choices=["classic", "dynamic"], help='Type of driver ("classic" or "dynamic"). ' 'The default is to list all of them.') @cliutils.arg('--detail', dest='detail', action='store_true', default=None, help="Show detailed information about the drivers.") def do_driver_list(cc, args): """List the enabled drivers.""" drivers = cc.driver.list(driver_type=args.type, detail=args.detail) # NOTE(lucasagomes): For list-type properties, show the values as # comma separated strings. It's easier to read. data = [utils.convert_list_props_to_comma_separated(d._info) for d in drivers] if args.detail: field_labels = res_fields.DRIVER_DETAILED_RESOURCE.labels fields = res_fields.DRIVER_DETAILED_RESOURCE.fields else: field_labels = res_fields.DRIVER_RESOURCE.labels fields = res_fields.DRIVER_RESOURCE.fields cliutils.print_list(data, fields, field_labels=field_labels, json_flag=args.json) @cliutils.arg('driver_name', metavar='', help='Name of the driver.') def do_driver_show(cc, args): """Show information about a driver.""" driver = cc.driver.get(args.driver_name) _print_driver_show(driver, json=args.json) @cliutils.arg('driver_name', metavar='', help="Name of the driver.") @cliutils.arg('--wrap', dest='wrap', metavar='', type=int, default=0, help=('Wrap the output to a specified length. ' 'Positive number can realize wrap functionality. ' '0 is default for disabled.')) def do_driver_properties(cc, args): """Get properties of a driver.""" properties = cc.driver.properties(args.driver_name) cliutils.print_dict( properties, wrap=args.wrap, dict_value='Description', json_flag=args.json) @cliutils.arg('driver_name', metavar='', help="Name of the driver.") @cliutils.arg('--wrap', dest='wrap', metavar='', type=int, default=0, help=('Wrap the output to a specified length. ' 'Positive number can realize wrap functionality. ' '0 is default for disabled.')) def do_driver_raid_logical_disk_properties(cc, args): """Get RAID logical disk properties for a driver.""" properties = cc.driver.raid_logical_disk_properties(args.driver_name) cliutils.print_dict( properties, wrap=args.wrap, dict_value='Description') @cliutils.arg('driver_name', metavar='', help='Name of the driver.') @cliutils.arg('method', metavar='', help="Vendor-passthru method to be called.") @cliutils.arg('arguments', metavar='', nargs='*', action='append', default=[], help="Argument to be passed to the vendor-passthru method. " "Can be specified multiple times.") @cliutils.arg('--http-method', metavar='', choices=v1_utils.HTTP_METHODS, help=("The HTTP method to use in the request. Valid HTTP " "methods are: %s. Defaults to 'POST'." % ', '.join(v1_utils.HTTP_METHODS))) @cliutils.arg('--http_method', help=argparse.SUPPRESS) def do_driver_vendor_passthru(cc, args): """Call a vendor-passthru extension for a driver.""" arguments = utils.key_value_pairs_to_dict(args.arguments[0]) resp = cc.driver.vendor_passthru(args.driver_name, args.method, http_method=args.http_method, args=arguments) if resp: # Print the raw response we don't know how it should be formated print(str(resp.to_dict())) @cliutils.arg('driver_name', metavar='', help='Name of the driver.') def do_driver_get_vendor_passthru_methods(cc, args): """Get the vendor passthru methods for a driver.""" methods = cc.driver.get_vendor_passthru_methods(args.driver_name) data = [] for method, response in methods.items(): response['name'] = method http_methods = ','.join(response['http_methods']) response['http_methods'] = http_methods data.append(response) fields = res_fields.VENDOR_PASSTHRU_METHOD_RESOURCE.fields field_labels = res_fields.VENDOR_PASSTHRU_METHOD_RESOURCE.labels cliutils.print_list(data, fields, field_labels=field_labels, sortby_index=None, json_flag=args.json) python-ironicclient-2.2.0/ironicclient/v1/create_resources.py0000666000175100017510000003256013232474343024474 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 functools import json import jsonschema import six import yaml from ironicclient import exc _CREATE_SCHEMA = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Schema for ironic resources file", "type": "object", "properties": { "chassis": { "type": "array", "items": { "type": "object" } }, "nodes": { "type": "array", "items": { "type": "object" } } }, "additionalProperties": False } def create_resources(client, filenames): """Create resources using their JSON or YAML descriptions. :param client: an instance of ironic client; :param filenames: a list of filenames containing JSON or YAML resources definitions. :raises: ClientException if any operation during files processing/resource creation fails. """ errors = [] resources = [] for resource_file in filenames: try: resource = load_from_file(resource_file) jsonschema.validate(resource, _CREATE_SCHEMA) resources.append(resource) except (exc.ClientException, jsonschema.ValidationError) as e: errors.append(e) if errors: raise exc.ClientException('While validating the resources file(s), the' ' following error(s) were encountered:\n%s' % '\n'.join(six.text_type(e) for e in errors)) for r in resources: errors.extend(create_chassis(client, r.get('chassis', []))) errors.extend(create_nodes(client, r.get('nodes', []))) if errors: raise exc.ClientException('During resources creation, the following ' 'error(s) were encountered:\n%s' % '\n'.join(six.text_type(e) for e in errors)) def load_from_file(filename): """Deserialize JSON or YAML from file. :param filename: name of the file containing JSON or YAML. :returns: a dictionary deserialized from JSON or YAML. :raises: ClientException if the file can not be loaded or if its contents is not a valid JSON or YAML, or if the file extension is not supported. """ try: with open(filename) as f: if filename.endswith('.yaml'): return yaml.safe_load(f) elif filename.endswith('.json'): return json.load(f) else: # The file is neither .json, nor .yaml, raise an exception raise exc.ClientException( 'Cannot process file "%(file)s" - it must have .json or ' '.yaml extension.' % {'file': filename}) except IOError as e: raise exc.ClientException('Cannot read file "%(file)s" due to ' 'error: %(err)s' % {'err': e, 'file': filename}) except (ValueError, yaml.YAMLError) as e: # json.load raises only ValueError raise exc.ClientException('File "%(file)s" is invalid due to error: ' '%(err)s' % {'err': e, 'file': filename}) def create_single_handler(resource_type): """Catch errors of the creation of a single resource. This decorator appends an error (which is an instance of some client exception class) to the return value of the create_method, changing the return value from just UUID to (UUID, error), and does some exception handling. :param resource_type: string value, the type of the resource being created, e.g. 'node', used purely for exception messages. """ def outer_wrapper(create_method): @functools.wraps(create_method) def wrapper(client, **params): uuid = None error = None try: uuid = create_method(client, **params) except exc.InvalidAttribute as e: error = exc.InvalidAttribute( 'Cannot create the %(resource)s with attributes ' '%(params)s. One or more attributes are invalid: %(err)s' % {'params': params, 'resource': resource_type, 'err': e} ) except Exception as e: error = exc.ClientException( 'Unable to create the %(resource)s with the specified ' 'attributes: %(params)s. The error is: %(error)s' % {'error': e, 'resource': resource_type, 'params': params}) return uuid, error return wrapper return outer_wrapper @create_single_handler('node') def create_single_node(client, **params): """Call the client to create a node. :param client: ironic client instance. :param params: dictionary to be POSTed to /nodes endpoint, excluding "ports" and "portgroups" keys. :returns: UUID of the created node or None in case of exception, and an exception, if it appears. :raises: InvalidAttribute, if some parameters passed to client's create_method are invalid. :raises: ClientException, if the creation of the node fails. """ params.pop('ports', None) params.pop('portgroups', None) ret = client.node.create(**params) return ret.uuid @create_single_handler('port') def create_single_port(client, **params): """Call the client to create a port. :param client: ironic client instance. :param params: dictionary to be POSTed to /ports endpoint. :returns: UUID of the created port or None in case of exception, and an exception, if it appears. :raises: InvalidAttribute, if some parameters passed to client's create_method are invalid. :raises: ClientException, if the creation of the port fails. """ ret = client.port.create(**params) return ret.uuid @create_single_handler('port group') def create_single_portgroup(client, **params): """Call the client to create a port group. :param client: ironic client instance. :param params: dictionary to be POSTed to /portgroups endpoint, excluding "ports" key. :returns: UUID of the created port group or None in case of exception, and an exception, if it appears. :raises: InvalidAttribute, if some parameters passed to client's create_method are invalid. :raises: ClientException, if the creation of the portgroup fails. """ params.pop('ports', None) ret = client.portgroup.create(**params) return ret.uuid @create_single_handler('chassis') def create_single_chassis(client, **params): """Call the client to create a chassis. :param client: ironic client instance. :param params: dictionary to be POSTed to /chassis endpoint, excluding "nodes" key. :returns: UUID of the created chassis or None in case of exception, and an exception, if it appears. :raises: InvalidAttribute, if some parameters passed to client's create_method are invalid. :raises: ClientException, if the creation of the chassis fails. """ params.pop('nodes', None) ret = client.chassis.create(**params) return ret.uuid def create_ports(client, port_list, node_uuid, portgroup_uuid=None): """Create ports from dictionaries. :param client: ironic client instance. :param port_list: list of dictionaries to be POSTed to /ports endpoint. :param node_uuid: UUID of a node the ports should be associated with. :param portgroup_uuid: UUID of a port group the ports should be associated with, if they are its members. :returns: array of exceptions encountered during creation. """ errors = [] for port in port_list: port_node_uuid = port.get('node_uuid') if port_node_uuid and port_node_uuid != node_uuid: errors.append(exc.ClientException( 'Cannot create a port as part of node %(node_uuid)s ' 'because the port %(port)s has a different node UUID ' 'specified.', {'node_uuid': node_uuid, 'port': port})) continue port['node_uuid'] = node_uuid if portgroup_uuid: port_portgroup_uuid = port.get('portgroup_uuid') if port_portgroup_uuid and port_portgroup_uuid != portgroup_uuid: errors.append(exc.ClientException( 'Cannot create a port as part of port group ' '%(portgroup_uuid)s because the port %(port)s has a ' 'different port group UUID specified.', {'portgroup_uuid': portgroup_uuid, 'port': port})) continue port['portgroup_uuid'] = portgroup_uuid port_uuid, error = create_single_port(client, **port) if error: errors.append(error) return errors def create_portgroups(client, portgroup_list, node_uuid): """Create port groups from dictionaries. :param client: ironic client instance. :param portgroup_list: list of dictionaries to be POSTed to /portgroups endpoint, if some of them contain "ports" key, its content is POSTed separately to /ports endpoint. :param node_uuid: UUID of a node the port groups should be associated with. :returns: array of exceptions encountered during creation. """ errors = [] for portgroup in portgroup_list: portgroup_node_uuid = portgroup.get('node_uuid') if portgroup_node_uuid and portgroup_node_uuid != node_uuid: errors.append(exc.ClientException( 'Cannot create a port group as part of node %(node_uuid)s ' 'because the port group %(portgroup)s has a different node ' 'UUID specified.', {'node_uuid': node_uuid, 'portgroup': portgroup})) continue portgroup['node_uuid'] = node_uuid portgroup_uuid, error = create_single_portgroup(client, **portgroup) if error: errors.append(error) ports = portgroup.get('ports') # Port group UUID == None means that port group creation failed, don't # create the ports inside it if ports is not None and portgroup_uuid is not None: errors.extend(create_ports(client, ports, node_uuid, portgroup_uuid=portgroup_uuid)) return errors def create_nodes(client, node_list, chassis_uuid=None): """Create nodes from dictionaries. :param client: ironic client instance. :param node_list: list of dictionaries to be POSTed to /nodes endpoint, if some of them contain "ports" key, its content is POSTed separately to /ports endpoint. :param chassis_uuid: UUID of a chassis the nodes should be associated with. :returns: array of exceptions encountered during creation. """ errors = [] for node in node_list: if chassis_uuid is not None: node_chassis_uuid = node.get('chassis_uuid') if node_chassis_uuid and node_chassis_uuid != chassis_uuid: errors.append(exc.ClientException( 'Cannot create a node as part of chassis %(chassis_uuid)s ' 'because the node %(node)s has a different chassis UUID ' 'specified.' % {'chassis_uuid': chassis_uuid, 'node': node})) continue node['chassis_uuid'] = chassis_uuid node_uuid, error = create_single_node(client, **node) if error: errors.append(error) ports = node.get('ports') portgroups = node.get('portgroups') # Node UUID == None means that node creation failed, don't # create the port(group)s inside it if node_uuid is not None: if portgroups is not None: errors.extend( create_portgroups(client, portgroups, node_uuid)) if ports is not None: errors.extend(create_ports(client, ports, node_uuid)) return errors def create_chassis(client, chassis_list): """Create chassis from dictionaries. :param client: ironic client instance. :param chassis_list: list of dictionaries to be POSTed to /chassis endpoint, if some of them contain "nodes" key, its content is POSTed separately to /nodes endpoint. :returns: array of exceptions encountered during creation. """ errors = [] for chassis in chassis_list: chassis_uuid, error = create_single_chassis(client, **chassis) if error: errors.append(error) nodes = chassis.get('nodes') # Chassis UUID == None means that chassis creation failed, don't # create the nodes inside it if nodes is not None and chassis_uuid is not None: errors.extend(create_nodes(client, nodes, chassis_uuid=chassis_uuid)) return errors python-ironicclient-2.2.0/ironicclient/v1/resource_fields.py0000666000175100017510000003126413232474373024317 0ustar zuulzuul00000000000000# Copyright 2014 Red Hat, Inc. # 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. from ironicclient.common.i18n import _ class Resource(object): """Resource class This class is used to manage the various fields that a resource (e.g. Chassis, Node, Port) contains. An individual field consists of a 'field_id' (key) and a 'label' (value). The caller only provides the 'field_ids' when instantiating the object. Ordering of the 'field_ids' will be preserved as specified by the caller. It also provides the ability to exclude some of these fields when they are being used for sorting. """ FIELDS = { 'address': 'Address', 'async': 'Async', 'attach': 'Response is attachment', 'boot_index': 'Boot Index', 'chassis_uuid': 'Chassis UUID', 'clean_step': 'Clean Step', 'console_enabled': 'Console Enabled', 'created_at': 'Created At', 'default_boot_interface': 'Default Boot Interface', 'default_console_interface': 'Default Console Interface', 'default_deploy_interface': 'Default Deploy Interface', 'default_inspect_interface': 'Default Inspect Interface', 'default_management_interface': 'Default Management Interface', 'default_network_interface': 'Default Network Interface', 'default_power_interface': 'Default Power Interface', 'default_raid_interface': 'Default RAID Interface', 'default_storage_interface': 'Default Storage Interface', 'default_vendor_interface': 'Default Vendor Interface', 'description': 'Description', 'driver': 'Driver', 'driver_info': 'Driver Info', 'driver_internal_info': 'Driver Internal Info', 'enabled_boot_interfaces': 'Enabled Boot Interfaces', 'enabled_console_interfaces': 'Enabled Console Interfaces', 'enabled_deploy_interfaces': 'Enabled Deploy Interfaces', 'enabled_inspect_interfaces': 'Enabled Inspect Interfaces', 'enabled_management_interfaces': 'Enabled Management Interfaces', 'enabled_network_interfaces': 'Enabled Network Interfaces', 'enabled_power_interfaces': 'Enabled Power Interfaces', 'enabled_raid_interfaces': 'Enabled RAID Interfaces', 'enabled_storage_interfaces': 'Enabled Storage Interfaces', 'enabled_vendor_interfaces': 'Enabled Vendor Interfaces', 'extra': 'Extra', 'hosts': 'Active host(s)', 'http_methods': 'Supported HTTP methods', 'inspection_finished_at': 'Inspection Finished At', 'inspection_started_at': 'Inspection Started At', 'instance_info': 'Instance Info', 'instance_uuid': 'Instance UUID', 'internal_info': 'Internal Info', 'last_error': 'Last Error', 'maintenance': 'Maintenance', 'maintenance_reason': 'Maintenance Reason', 'mode': 'Mode', 'name': 'Name', 'node_uuid': 'Node UUID', 'power_state': 'Power State', 'properties': 'Properties', 'provision_state': 'Provisioning State', 'provision_updated_at': 'Provision Updated At', 'raid_config': 'Current RAID configuration', 'reservation': 'Reservation', 'resource_class': 'Resource Class', 'target_power_state': 'Target Power State', 'target_provision_state': 'Target Provision State', 'target_raid_config': 'Target RAID configuration', 'traits': 'Traits', 'type': 'Type', 'updated_at': 'Updated At', 'uuid': 'UUID', 'volume_id': 'Volume ID', 'volume_type': 'Driver Volume Type', 'local_link_connection': 'Local Link Connection', 'pxe_enabled': 'PXE boot enabled', 'portgroup_uuid': 'Portgroup UUID', 'boot_interface': 'Boot Interface', 'console_interface': 'Console Interface', 'deploy_interface': 'Deploy Interface', 'inspect_interface': 'Inspect Interface', 'management_interface': 'Management Interface', 'network_interface': 'Network Interface', 'power_interface': 'Power Interface', 'raid_interface': 'RAID Interface', 'storage_interface': 'Storage Interface', 'vendor_interface': 'Vendor Interface', 'standalone_ports_supported': 'Standalone Ports Supported', 'physical_network': 'Physical Network', 'id': 'ID', 'connector_id': 'Connector ID', } def __init__(self, field_ids, sort_excluded=None, override_labels=None): """Create a Resource object :param field_ids: A list of strings that the Resource object will contain. Each string must match an existing key in FIELDS. :param sort_excluded: Optional. A list of strings that will not be used for sorting. Must be a subset of 'field_ids'. :param override_labels: Optional. A dictionary, where key is a field ID and value is the label to be used. If unspecified, uses the labels associated with the fields from global FIELDS. :raises: ValueError if sort_excluded or override_labels contains values not in field_ids """ def check_param_fields(param_name, param_fields): not_existing = set(param_fields) - set(field_ids) if not_existing: raise ValueError( _("%(param)s specified with value not contained in " "field_ids. Unknown value(s): %(unknown)s") % {'param': param_name, 'unknown': ', '.join(not_existing)}) if override_labels is None: override_labels = {} else: check_param_fields('override_labels', override_labels.keys()) self._fields = tuple(field_ids) self._labels = tuple([override_labels.get(x) or self.FIELDS[x] for x in field_ids]) if sort_excluded is None: sort_excluded = [] else: check_param_fields('sort_excluded', sort_excluded) self._sort_fields = tuple( [x for x in field_ids if x not in sort_excluded]) self._sort_labels = tuple([override_labels.get(x) or self.FIELDS[x] for x in self._sort_fields]) @property def fields(self): return self._fields @property def labels(self): return self._labels @property def sort_fields(self): return self._sort_fields @property def sort_labels(self): return self._sort_labels # Chassis CHASSIS_DETAILED_RESOURCE = Resource( ['uuid', 'description', 'created_at', 'updated_at', 'extra', ], sort_excluded=['extra']) CHASSIS_RESOURCE = Resource( ['uuid', 'description', ]) # Nodes NODE_DETAILED_RESOURCE = Resource( ['chassis_uuid', 'created_at', 'clean_step', 'console_enabled', 'driver', 'driver_info', 'driver_internal_info', 'extra', 'instance_info', 'instance_uuid', 'last_error', 'maintenance', 'maintenance_reason', 'power_state', 'properties', 'provision_state', 'provision_updated_at', 'raid_config', 'reservation', 'resource_class', 'target_power_state', 'target_provision_state', 'target_raid_config', 'traits', 'updated_at', 'inspection_finished_at', 'inspection_started_at', 'uuid', 'name', 'boot_interface', 'console_interface', 'deploy_interface', 'inspect_interface', 'management_interface', 'network_interface', 'power_interface', 'raid_interface', 'storage_interface', 'vendor_interface', ], sort_excluded=[ # The server cannot sort on "chassis_uuid" because it isn't a column in # the "nodes" database table. "chassis_id" is stored, but it is # internal to ironic. See bug #1443003 for more details. 'chassis_uuid', 'clean_step', 'driver_info', 'driver_internal_info', 'extra', 'instance_info', 'properties', 'raid_config', 'target_raid_config', 'traits', ]) NODE_RESOURCE = Resource( ['uuid', 'name', 'instance_uuid', 'power_state', 'provision_state', 'maintenance', ]) VENDOR_PASSTHRU_METHOD_RESOURCE = Resource( ['name', 'http_methods', 'async', 'description', 'attach' ]) # Ports PORT_DETAILED_RESOURCE = Resource( ['uuid', 'address', 'created_at', 'extra', 'node_uuid', 'local_link_connection', 'portgroup_uuid', 'pxe_enabled', 'physical_network', 'updated_at', 'internal_info', ], sort_excluded=[ 'extra', # The server cannot sort on "node_uuid" or "portgroup_uuid" because # they aren't columns in the "ports" database table. "node_id" and # "portgroup_id" are stored, but it is internal to ironic. # See bug #1443003 for more details. 'node_uuid', 'portgroup_uuid', 'internal_info', ]) PORT_RESOURCE = Resource( ['uuid', 'address', ]) # Portgroups PORTGROUP_DETAILED_RESOURCE = Resource( ['uuid', 'address', 'created_at', 'extra', 'standalone_ports_supported', 'node_uuid', 'name', 'updated_at', 'internal_info', 'mode', 'properties', ], sort_excluded=[ 'extra', # The server cannot sort on "node_uuid" because it isn't a column in # the "portgroups" database table. "node_id" is stored, but it is # internal to ironic. See bug #1443003 for more details. 'node_uuid', 'internal_info', 'properties', ]) PORTGROUP_RESOURCE = Resource( ['uuid', 'address', 'name', ]) # VIFs VIF_RESOURCE = Resource( ['id'], ) TRAIT_RESOURCE = Resource( ['traits'], ) # Drivers DRIVER_DETAILED_RESOURCE = Resource( ['name', 'type', 'hosts', 'default_boot_interface', 'default_console_interface', 'default_deploy_interface', 'default_inspect_interface', 'default_management_interface', 'default_network_interface', 'default_power_interface', 'default_raid_interface', 'default_storage_interface', 'default_vendor_interface', 'enabled_boot_interfaces', 'enabled_console_interfaces', 'enabled_deploy_interfaces', 'enabled_inspect_interfaces', 'enabled_management_interfaces', 'enabled_network_interfaces', 'enabled_power_interfaces', 'enabled_raid_interfaces', 'enabled_storage_interfaces', 'enabled_vendor_interfaces' ], override_labels={'name': 'Supported driver(s)'} ) DRIVER_RESOURCE = Resource( ['name', 'hosts', ], override_labels={'name': 'Supported driver(s)'} ) # Volume connectors VOLUME_CONNECTOR_DETAILED_RESOURCE = Resource( ['uuid', 'node_uuid', 'type', 'connector_id', 'extra', 'created_at', 'updated_at', ], sort_excluded=[ # The server cannot sort on "node_uuid" because it isn't a column in # the "volume_connectors" database table. "node_id" is stored, but it # is internal to ironic. See bug #1443003 for more details. 'node_uuid', 'extra', ]) VOLUME_CONNECTOR_RESOURCE = Resource( ['uuid', 'node_uuid', 'type', 'connector_id', ], sort_excluded=['node_uuid'] ) # Volume targets VOLUME_TARGET_DETAILED_RESOURCE = Resource( ['uuid', 'node_uuid', 'volume_type', 'properties', 'boot_index', 'extra', 'volume_id', 'created_at', 'updated_at', ], sort_excluded=[ # The server cannot sort on "node_uuid" because it isn't a column in # the "volume_targets" database table. "node_id" is stored, but it # is internal to ironic. See bug #1443003 for more details. 'node_uuid', 'extra', 'properties' ]) VOLUME_TARGET_RESOURCE = Resource( ['uuid', 'node_uuid', 'volume_type', 'boot_index', 'volume_id', ], sort_excluded=['node_uuid'] ) python-ironicclient-2.2.0/ironicclient/v1/volume_target_shell.py0000666000175100017510000001704013232474343025177 0ustar zuulzuul00000000000000# Copyright 2017 Hitachi, Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ironicclient.common.apiclient import exceptions from ironicclient.common import cliutils from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient.v1 import resource_fields as res_fields def _print_volume_target_show(volume_target, fields=None, json=False): if fields is None: fields = res_fields.VOLUME_TARGET_DETAILED_RESOURCE.fields data = dict([(f, getattr(volume_target, f, '')) for f in fields]) cliutils.print_dict(data, wrap=72, json_flag=json) @cliutils.arg( 'volume_target', metavar='', help=_("UUID of the volume target.")) @cliutils.arg( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], help=_("One or more volume target fields. Only these fields will be " "fetched from the server.")) def do_volume_target_show(cc, args): """Show detailed information about a volume target.""" fields = args.fields[0] if args.fields else None utils.check_for_invalid_fields( fields, res_fields.VOLUME_TARGET_DETAILED_RESOURCE.fields) utils.check_empty_arg(args.volume_target, '') volume_target = cc.volume_target.get(args.volume_target, fields=fields) _print_volume_target_show(volume_target, fields=fields, json=args.json) @cliutils.arg( '--detail', dest='detail', action='store_true', default=False, help=_("Show detailed information about volume targets.")) @cliutils.arg( '-n', '--node', metavar='', help=_('Only list volume targets of this node (name or UUID)')) @cliutils.arg( '--limit', metavar='', type=int, help=_('Maximum number of volume targets to return per request, ' '0 for no limit. Default is the maximum number used ' 'by the Baremetal API Service.')) @cliutils.arg( '--marker', metavar='', help=_('Volume target UUID (for example, of the last volume target in ' 'the list from a previous request). Returns the list of volume ' 'targets after this UUID.')) @cliutils.arg( '--sort-key', metavar='', help=_('Volume target field that will be used for sorting.')) @cliutils.arg( '--sort-dir', metavar='', choices=['asc', 'desc'], help=_('Sort direction: "asc" (the default) or "desc".')) @cliutils.arg( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], help=_("One or more volume target fields. Only these fields will be " "fetched from the server. Can not be used when '--detail' is " "specified.")) def do_volume_target_list(cc, args): """List the volume targets.""" params = {} if args.node is not None: params['node'] = args.node if args.detail: fields = res_fields.VOLUME_TARGET_DETAILED_RESOURCE.fields field_labels = res_fields.VOLUME_TARGET_DETAILED_RESOURCE.labels elif args.fields: utils.check_for_invalid_fields( args.fields[0], res_fields.VOLUME_TARGET_DETAILED_RESOURCE.fields) resource = res_fields.Resource(args.fields[0]) fields = resource.fields field_labels = resource.labels else: fields = res_fields.VOLUME_TARGET_RESOURCE.fields field_labels = res_fields.VOLUME_TARGET_RESOURCE.labels sort_fields = res_fields.VOLUME_TARGET_DETAILED_RESOURCE.sort_fields sort_field_labels = ( res_fields.VOLUME_TARGET_DETAILED_RESOURCE.sort_labels) params.update(utils.common_params_for_list(args, sort_fields, sort_field_labels)) volume_target = cc.volume_target.list(**params) cliutils.print_list(volume_target, fields, field_labels=field_labels, sortby_index=None, json_flag=args.json) @cliutils.arg( '-e', '--extra', metavar="", action='append', help=_("Record arbitrary key/value metadata. " "Can be specified multiple times.")) @cliutils.arg( '-n', '--node', dest='node_uuid', metavar='', required=True, help=_('UUID of the node that this volume target belongs to.')) @cliutils.arg( '-t', '--type', metavar="", required=True, help=_("Type of the volume target, e.g. 'iscsi', 'fibre_channel'.")) @cliutils.arg( '-p', '--properties', metavar="", action='append', help=_("Key/value property related to the type of this volume " "target. Can be specified multiple times.")) @cliutils.arg( '-b', '--boot-index', metavar="", required=True, help=_("Boot index of the volume target.")) @cliutils.arg( '-i', '--volume_id', metavar="", required=True, help=_("ID of the volume associated with this target.")) @cliutils.arg( '-u', '--uuid', metavar='', help=_("UUID of the volume target.")) def do_volume_target_create(cc, args): """Create a new volume target.""" field_list = ['extra', 'volume_type', 'properties', 'boot_index', 'node_uuid', 'volume_id', 'uuid'] fields = dict((k, v) for (k, v) in vars(args).items() if k in field_list and not (v is None)) fields = utils.args_array_to_dict(fields, 'properties') fields = utils.args_array_to_dict(fields, 'extra') volume_target = cc.volume_target.create(**fields) data = dict([(f, getattr(volume_target, f, '')) for f in field_list]) cliutils.print_dict(data, wrap=72, json_flag=args.json) @cliutils.arg('volume_target', metavar='', nargs='+', help=_("UUID of the volume target.")) def do_volume_target_delete(cc, args): """Delete a volume target.""" failures = [] for vt in args.volume_target: try: cc.volume_target.delete(vt) print(_('Deleted volume target %s') % vt) except exceptions.ClientException as e: failures.append(_("Failed to delete volume target %(vt)s: " "%(error)s") % {'vt': vt, 'error': e}) if failures: raise exceptions.ClientException("\n".join(failures)) @cliutils.arg('volume_target', metavar='', help=_("UUID of the volume target.")) @cliutils.arg( 'op', metavar='', choices=['add', 'replace', 'remove'], help=_("Operation: 'add', 'replace', or 'remove'.")) @cliutils.arg( 'attributes', metavar='', nargs='+', action='append', default=[], help=_("Attribute to add, replace, or remove. Can be specified multiple " "times. For 'remove', only is necessary.")) def do_volume_target_update(cc, args): """Update information about a volume target.""" patch = utils.args_array_to_patch(args.op, args.attributes[0]) volume_target = cc.volume_target.update(args.volume_target, patch) _print_volume_target_show(volume_target, json=args.json) python-ironicclient-2.2.0/ironicclient/v1/client.py0000666000175100017510000001124113232474343022406 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack LLC. # 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. from ironicclient.common import filecache from ironicclient.common import http from ironicclient.common.http import DEFAULT_VER from ironicclient.common.i18n import _ from ironicclient import exc from ironicclient.v1 import chassis from ironicclient.v1 import driver from ironicclient.v1 import node from ironicclient.v1 import port from ironicclient.v1 import portgroup from ironicclient.v1 import volume_connector from ironicclient.v1 import volume_target class Client(object): """Client for the Ironic v1 API. :param string endpoint: A user-supplied endpoint URL for the ironic service. :param function token: Provides token for authentication. :param integer timeout: Allows customization of the timeout for client http requests. (optional) """ def __init__(self, endpoint=None, *args, **kwargs): """Initialize a new client for the Ironic v1 API.""" allow_downgrade = kwargs.pop('allow_api_version_downgrade', False) if kwargs.get('os_ironic_api_version'): # TODO(TheJulia): We should sanity check os_ironic_api_version # against our maximum suported version, so the client fails # immediately upon an unsupported version being provided. # This logic should also likely live in common/http.py if allow_downgrade: if kwargs['os_ironic_api_version'] == 'latest': raise ValueError( "Invalid configuration defined. " "The os_ironic_api_versioncan not be set " "to 'latest' while allow_api_version_downgrade " "is set.") # NOTE(dtantsur): here we allow the HTTP client to negotiate a # lower version if the requested is too high kwargs['api_version_select_state'] = "default" else: kwargs['api_version_select_state'] = "user" else: if not endpoint: raise exc.EndpointException( _("Must provide 'endpoint' if os_ironic_api_version " "isn't specified")) # If the user didn't specify a version, use a cached version if # one has been stored host, netport = http.get_server(endpoint) saved_version = filecache.retrieve_data(host=host, port=netport) if saved_version: kwargs['api_version_select_state'] = "cached" kwargs['os_ironic_api_version'] = saved_version else: kwargs['api_version_select_state'] = "default" kwargs['os_ironic_api_version'] = DEFAULT_VER self.http_client = http._construct_http_client( endpoint, *args, **kwargs) self.chassis = chassis.ChassisManager(self.http_client) self.node = node.NodeManager(self.http_client) self.port = port.PortManager(self.http_client) self.volume_connector = volume_connector.VolumeConnectorManager( self.http_client) self.volume_target = volume_target.VolumeTargetManager( self.http_client) self.driver = driver.DriverManager(self.http_client) self.portgroup = portgroup.PortgroupManager(self.http_client) @property def current_api_version(self): """Return the current API version in use. This returns the version of the REST API that the API client is presently set to request. This value may change as a result of API version negotiation. """ return self.http_client.os_ironic_api_version @property def is_api_version_negotiated(self): """Returns True if microversion negotiation has occured.""" return self.http_client.api_version_select_state == 'negotiated' def negotiate_api_version(self): """Triggers negotiation with the remote API endpoint. :returns: the negotiated API version. """ return self.http_client.negotiate_version( self.http_client.session, None) python-ironicclient-2.2.0/ironicclient/v1/driver.py0000666000175100017510000001006713232474343022430 0ustar zuulzuul00000000000000# Copyright 2013 Red Hat, Inc. # 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. from ironicclient.common import base from ironicclient.common.i18n import _ from ironicclient import exc class Driver(base.Resource): def __repr__(self): return "" % self._info class DriverManager(base.Manager): resource_class = Driver _resource_name = 'drivers' def list(self, driver_type=None, detail=None): """Retrieve a list of drivers. :param driver_type: Optional, string to filter the drivers by type. Value should be 'classic' or 'dynamic'. :param detail: Optional, flag whether to return detailed information about drivers. Default is None means not to send the arg to the server due to older versions of the server cannot handle filtering on detail. :returns: A list of drivers. """ filters = [] if driver_type is not None: filters.append('type=%s' % driver_type) if detail is not None: filters.append('detail=%s' % detail) path = '' if filters: path = '?' + '&'.join(filters) return self._list(self._path(path), self._resource_name) def get(self, driver_name): return self._get(resource_id=driver_name) def update(self, driver_name, patch, http_method='PATCH'): return self._update(resource_id=driver_name, patch=patch, method=http_method) def delete(self, driver_name): return self._delete(resource_id=driver_name) def properties(self, driver_name): return self._get_as_dict('%s/properties' % driver_name) def raid_logical_disk_properties(self, driver_name): """Returns the RAID logical disk properties for the driver. :param driver_name: Name of the driver. :returns: A dictionary containing the properties that can be mentioned for RAID logical disks and a textual description for them. It returns an empty dictionary on error. """ info = None try: info = self._list( '/v1/drivers/%s/raid/logical_disk_properties' % driver_name)[0] except IndexError: pass if info: return info.to_dict() return {} def vendor_passthru(self, driver_name, method, args=None, http_method=None): """Issue requests for vendor-specific actions on a given driver. :param driver_name: The name of the driver. :param method: Name of the vendor method. :param args: Optional. The arguments to be passed to the method. :param http_method: The HTTP method to use on the request. Defaults to POST. """ if args is None: args = {} if http_method is None: http_method = 'POST' http_method = http_method.upper() path = "%s/vendor_passthru/%s" % (driver_name, method) if http_method in ('POST', 'PUT', 'PATCH'): return self.update(path, args, http_method=http_method) elif http_method == 'DELETE': return self.delete(path) elif http_method == 'GET': return self.get(path) else: raise exc.InvalidAttribute( _('Unknown HTTP method: %s') % http_method) def get_vendor_passthru_methods(self, driver_name): return self._get_as_dict("%s/vendor_passthru/methods" % driver_name) python-ironicclient-2.2.0/ironicclient/v1/port_shell.py0000666000175100017510000001670313232474343023313 0ustar zuulzuul00000000000000# Copyright 2013 Red Hat, Inc. # 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. from ironicclient.common.apiclient import exceptions from ironicclient.common import cliutils from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient.v1 import resource_fields as res_fields def _print_port_show(port, fields=None, json=False): if fields is None: fields = res_fields.PORT_DETAILED_RESOURCE.fields data = dict([(f, getattr(port, f, '')) for f in fields]) cliutils.print_dict(data, wrap=72, json_flag=json) @cliutils.arg( 'port', metavar='', help="UUID of the port (or MAC address if --address is specified).") @cliutils.arg( '--address', dest='address', action='store_true', default=False, help=' is the MAC address (instead of the UUID) of the port.') @cliutils.arg( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], help="One or more port fields. Only these fields will be fetched from " "the server.") def do_port_show(cc, args): """Show detailed information about a port.""" fields = args.fields[0] if args.fields else None utils.check_for_invalid_fields( fields, res_fields.PORT_DETAILED_RESOURCE.fields) if args.address: port = cc.port.get_by_address(args.port, fields=fields) else: utils.check_empty_arg(args.port, '') port = cc.port.get(args.port, fields=fields) _print_port_show(port, fields=fields, json=args.json) @cliutils.arg( '--detail', dest='detail', action='store_true', default=False, help="Show detailed information about ports.") @cliutils.arg( '--address', metavar='', help='Only show information for the port with this MAC address.') @cliutils.arg( '--limit', metavar='', type=int, help='Maximum number of ports to return per request, ' '0 for no limit. Default is the maximum number used ' 'by the Ironic API Service.') @cliutils.arg( '--marker', metavar='', help='Port UUID (for example, of the last port in the list from a ' 'previous request). Returns the list of ports after this UUID.') @cliutils.arg( '--sort-key', metavar='', help='Port field that will be used for sorting.') @cliutils.arg( '--sort-dir', metavar='', choices=['asc', 'desc'], help='Sort direction: "asc" (the default) or "desc".') @cliutils.arg( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], help="One or more port fields. Only these fields will be fetched from " "the server. Can not be used when '--detail' is specified.") def do_port_list(cc, args): """List the ports.""" params = {} if args.address is not None: params['address'] = args.address if args.detail: fields = res_fields.PORT_DETAILED_RESOURCE.fields field_labels = res_fields.PORT_DETAILED_RESOURCE.labels elif args.fields: utils.check_for_invalid_fields( args.fields[0], res_fields.PORT_DETAILED_RESOURCE.fields) resource = res_fields.Resource(args.fields[0]) fields = resource.fields field_labels = resource.labels else: fields = res_fields.PORT_RESOURCE.fields field_labels = res_fields.PORT_RESOURCE.labels sort_fields = res_fields.PORT_DETAILED_RESOURCE.sort_fields sort_field_labels = res_fields.PORT_DETAILED_RESOURCE.sort_labels params.update(utils.common_params_for_list(args, sort_fields, sort_field_labels)) port = cc.port.list(**params) cliutils.print_list(port, fields, field_labels=field_labels, sortby_index=None, json_flag=args.json) @cliutils.arg( '-a', '--address', metavar='
', required=True, help='MAC address for this port.') @cliutils.arg( '-n', '--node', '--node_uuid', dest='node_uuid', metavar='', required=True, help='UUID of the node that this port belongs to.') @cliutils.arg( '-l', '--local-link-connection', metavar="", action='append', help="Key/value metadata describing Local link connection information. " "Valid keys are switch_info, switch_id, port_id." "Can be specified multiple times.") @cliutils.arg( '--portgroup', metavar="", dest='portgroup_uuid', help='UUID of the portgroup that this port belongs to.') @cliutils.arg( '--pxe-enabled', metavar='', help='Indicates whether this Port should be used when ' 'PXE booting this Node.') @cliutils.arg( '--physical-network', metavar='', help="Physical network of the port.") @cliutils.arg( '-e', '--extra', metavar="", action='append', help="Record arbitrary key/value metadata. " "Can be specified multiple times.") @cliutils.arg( '-u', '--uuid', metavar='', help="UUID of the port.") def do_port_create(cc, args): """Create a new port.""" field_list = ['address', 'extra', 'node_uuid', 'uuid', 'local_link_connection', 'portgroup_uuid', 'pxe_enabled', 'physical_network'] fields = dict((k, v) for (k, v) in vars(args).items() if k in field_list and not (v is None)) fields = utils.args_array_to_dict(fields, 'extra') fields = utils.args_array_to_dict(fields, 'local_link_connection') port = cc.port.create(**fields) data = dict([(f, getattr(port, f, '')) for f in field_list]) cliutils.print_dict(data, wrap=72, json_flag=args.json) @cliutils.arg('port', metavar='', nargs='+', help="UUID of the port.") def do_port_delete(cc, args): """Delete a port.""" failures = [] for p in args.port: try: cc.port.delete(p) print(_('Deleted port %s') % p) except exceptions.ClientException as e: failures.append(_("Failed to delete port %(port)s: %(error)s") % {'port': p, 'error': e}) if failures: raise exceptions.ClientException("\n".join(failures)) @cliutils.arg('port', metavar='', help="UUID of the port.") @cliutils.arg( 'op', metavar='', choices=['add', 'replace', 'remove'], help="Operation: 'add', 'replace', or 'remove'.") @cliutils.arg( 'attributes', metavar='', nargs='+', action='append', default=[], help="Attribute to add, replace, or remove. Can be specified multiple " "times. For 'remove', only is necessary.") def do_port_update(cc, args): """Update information about a port.""" patch = utils.args_array_to_patch(args.op, args.attributes[0]) port = cc.port.update(args.port, patch) _print_port_show(port, json=args.json) python-ironicclient-2.2.0/ironicclient/v1/portgroup.py0000666000175100017510000001674713232474343023211 0ustar zuulzuul00000000000000# Copyright 2016 SAP Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ironicclient.common import base from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient import exc class Portgroup(base.Resource): def __repr__(self): return "" % self._info class PortgroupManager(base.CreateManager): resource_class = Portgroup _resource_name = 'portgroups' _creation_attributes = ['address', 'extra', 'name', 'node_uuid', 'standalone_ports_supported', 'mode', 'properties'] def list(self, node=None, address=None, limit=None, marker=None, sort_key=None, sort_dir=None, detail=False, fields=None): """Retrieve a list of portgroups. :param node: Optional, UUID or name of a node, to get the portgroups for that node. :param address: Optional, MAC address of a portgroup, to get the portgroup which has this MAC address. :param marker: Optional, the UUID of a portgroup, eg the last portgroup from a previous result set. Return the next result set. :param limit: The maximum number of results to return per request, if: 1) limit > 0, the maximum number of portgroups to return. 2) limit == 0, return the entire list of portgroups. 3) limit == None, the number of items returned respect the maximum imposed by the Ironic API (see Ironic's api.max_limit option). :param sort_key: Optional, field used for sorting. :param sort_dir: Optional, direction of sorting, either 'asc' (the default) or 'desc'. :param detail: Optional, boolean whether to return detailed information about portgroups. :param fields: Optional, a list with a specified set of fields of the resource to be returned. Can not be used when 'detail' is set. :returns: A list of portgroups. :raises: InvalidAttribute if a subset of fields is requested with detail option set. """ if limit is not None: limit = int(limit) if detail and fields: raise exc.InvalidAttribute(_("Can't fetch a subset of fields " "with 'detail' set")) filters = utils.common_filters(marker, limit, sort_key, sort_dir, fields) if address is not None: filters.append('address=%s' % address) if node is not None: filters.append('node=%s' % node) path = '' if detail: path += 'detail' if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(self._path(path), "portgroups") else: return self._list_pagination(self._path(path), "portgroups", limit=limit) def list_ports(self, portgroup_id, marker=None, limit=None, sort_key=None, sort_dir=None, detail=False, fields=None): """List all the ports for a given portgroup. :param portgroup_id: Name or UUID of the portgroup. :param marker: Optional, the UUID of a port, eg the last port from a previous result set. Return the next result set. :param limit: The maximum number of results to return per request, if: 1) limit > 0, the maximum number of ports to return. 2) limit == 0, return the entire list of ports. 3) limit param is NOT specified (None), the number of items returned respect the maximum imposed by the Ironic API (see Ironic's api.max_limit option). :param sort_key: Optional, field used for sorting. :param sort_dir: Optional, direction of sorting, either 'asc' (the default) or 'desc'. :param detail: Optional, boolean whether to return detailed information about ports. :param fields: Optional, a list with a specified set of fields of the resource to be returned. Can not be used when 'detail' is set. :returns: A list of ports. """ if limit is not None: limit = int(limit) if detail and fields: raise exc.InvalidAttribute(_("Can't fetch a subset of fields " "with 'detail' set")) filters = utils.common_filters(marker, limit, sort_key, sort_dir, fields) path = "%s/ports" % portgroup_id if detail: path += '/detail' if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(self._path(path), "ports") else: return self._list_pagination(self._path(path), "ports", limit=limit) def get(self, portgroup_id, fields=None): """Get a port group with the specified identifier. :param portgroup_id: The UUID or name of a portgroup. :param fields: Optional, a list with a specified set of fields of the resource to be returned. Can not be used when 'detail' is set. :returns: a :class:`Portgroup` object. """ return self._get(resource_id=portgroup_id, fields=fields) def get_by_address(self, address, fields=None): """Get a port group with the specified MAC address. :param address: The MAC address of a portgroup. :param fields: Optional, a list with a specified set of fields of the resource to be returned. Can not be used when 'detail' is set. :returns: a :class:`Portgroup` object. """ path = '?address=%s' % address if fields is not None: path += '&fields=' + ','.join(fields) else: path = 'detail' + path portgroups = self._list(self._path(path), 'portgroups') # get all the details of the portgroup assuming that # filtering by address returns a collection of one portgroup # if successful. if len(portgroups) == 1: return portgroups[0] else: raise exc.NotFound() def delete(self, portgroup_id): """Delete the Portgroup from the DB. :param portgroup_id: The UUID or name of a portgroup. """ return self._delete(resource_id=portgroup_id) def update(self, portgroup_id, patch): """Update the Portgroup. :param portgroup_id: The UUID or name of a portgroup. :param patch: The patch request with updates. """ return self._update(resource_id=portgroup_id, patch=patch) python-ironicclient-2.2.0/ironicclient/v1/create_resources_shell.py0000666000175100017510000000246013232474343025657 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 ironicclient.common import cliutils from ironicclient.v1 import create_resources @cliutils.arg('resource_files', nargs='+', metavar='', default=[], help='File (.yaml or .json) containing descriptions of the ' 'resources to create. Can be specified multiple times.') def do_create(cc, args): """Create baremetal resources (chassis, nodes, port groups and ports). The resources may be described in one or more JSON or YAML files. If any file cannot be validated, no resources are created. An attempt is made to create all the resources; those that could not be created are skipped (with a corresponding error message). """ create_resources.create_resources(cc, args.resource_files) python-ironicclient-2.2.0/ironicclient/v1/volume_connector_shell.py0000666000175100017510000001675313232474343025715 0ustar zuulzuul00000000000000# Copyright 2017 Hitachi Data Systems # # 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 ironicclient.common.apiclient import exceptions from ironicclient.common import cliutils from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient.v1 import resource_fields as res_fields def _print_volume_connector_show(volume_connector, fields=None, json=False): if fields is None: fields = res_fields.VOLUME_CONNECTOR_DETAILED_RESOURCE.fields data = dict([(f, getattr(volume_connector, f, '')) for f in fields]) cliutils.print_dict(data, wrap=72, json_flag=json) @cliutils.arg( 'volume_connector', metavar='', help=_("UUID of the volume connector.")) @cliutils.arg( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], help=_("One or more volume connector fields. Only these fields will be " "fetched from the server.")) def do_volume_connector_show(cc, args): """Show detailed information about a volume connector.""" fields = args.fields[0] if args.fields else None utils.check_for_invalid_fields( fields, res_fields.VOLUME_CONNECTOR_DETAILED_RESOURCE.fields) utils.check_empty_arg(args.volume_connector, '') volume_connector = cc.volume_connector.get(args.volume_connector, fields=fields) _print_volume_connector_show(volume_connector, fields=fields, json=args.json) @cliutils.arg( '--detail', dest='detail', action='store_true', default=False, help=_("Show detailed information about volume connectors.")) @cliutils.arg( '-n', '--node', metavar='', help=_('Only list volume connectors of this node (name or UUID)')) @cliutils.arg( '--limit', metavar='', type=int, help=_('Maximum number of volume connectors to return per request, ' '0 for no limit. Default is the maximum number used ' 'by the Baremetal API Service.')) @cliutils.arg( '--marker', metavar='', help=_('Volume connector UUID (for example, of the last volume connector ' 'in the list from a previous request). Returns the list of volume ' 'connectors after this UUID.')) @cliutils.arg( '--sort-key', metavar='', help=_('Volume connector field that will be used for sorting.')) @cliutils.arg( '--sort-dir', metavar='', choices=['asc', 'desc'], help=_('Sort direction: "asc" (the default) or "desc".')) @cliutils.arg( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], help=_("One or more volume connector fields. Only these fields will be " "fetched from the server. Can not be used when '--detail' is " "specified.")) def do_volume_connector_list(cc, args): """List the volume connectors.""" params = {} if args.node is not None: params['node'] = args.node if args.detail: fields = res_fields.VOLUME_CONNECTOR_DETAILED_RESOURCE.fields field_labels = res_fields.VOLUME_CONNECTOR_DETAILED_RESOURCE.labels elif args.fields: utils.check_for_invalid_fields( args.fields[0], res_fields.VOLUME_CONNECTOR_DETAILED_RESOURCE.fields) resource = res_fields.Resource(args.fields[0]) fields = resource.fields field_labels = resource.labels else: fields = res_fields.VOLUME_CONNECTOR_RESOURCE.fields field_labels = res_fields.VOLUME_CONNECTOR_RESOURCE.labels sort_fields = res_fields.VOLUME_CONNECTOR_DETAILED_RESOURCE.sort_fields sort_field_labels = ( res_fields.VOLUME_CONNECTOR_DETAILED_RESOURCE.sort_labels) params.update(utils.common_params_for_list(args, sort_fields, sort_field_labels)) volume_connector = cc.volume_connector.list(**params) cliutils.print_list(volume_connector, fields, field_labels=field_labels, sortby_index=None, json_flag=args.json) @cliutils.arg( '-e', '--extra', metavar="", action='append', help=_("Record arbitrary key/value metadata. " "Can be specified multiple times.")) @cliutils.arg( '-n', '--node', dest='node_uuid', metavar='', required=True, help=_('UUID of the node that this volume connector belongs to.')) @cliutils.arg( '-t', '--type', metavar="", required=True, choices=['iqn', 'ip', 'mac', 'wwnn', 'wwpn'], help=_("Type of the volume connector. Can be 'iqn', 'ip', 'mac', 'wwnn', " "'wwpn'.")) @cliutils.arg( '-i', '--connector_id', metavar="", required=True, help=_("ID of the Volume connector in the specified type.")) @cliutils.arg( '-u', '--uuid', metavar='', help=_("UUID of the volume connector.")) def do_volume_connector_create(cc, args): """Create a new volume connector.""" field_list = ['extra', 'type', 'connector_id', 'node_uuid', 'uuid'] fields = dict((k, v) for (k, v) in vars(args).items() if k in field_list and not (v is None)) fields = utils.args_array_to_dict(fields, 'extra') volume_connector = cc.volume_connector.create(**fields) data = dict([(f, getattr(volume_connector, f, '')) for f in field_list]) cliutils.print_dict(data, wrap=72, json_flag=args.json) @cliutils.arg('volume_connector', metavar='', nargs='+', help=_("UUID of the volume connector.")) def do_volume_connector_delete(cc, args): """Delete a volume connector.""" failures = [] for vc in args.volume_connector: try: cc.volume_connector.delete(vc) print(_('Deleted volume connector %s') % vc) except exceptions.ClientException as e: failures.append(_("Failed to delete volume connector %(vc)s: " "%(error)s") % {'vc': vc, 'error': e}) if failures: raise exceptions.ClientException("\n".join(failures)) @cliutils.arg('volume_connector', metavar='', help=_("UUID of the volume connector.")) @cliutils.arg( 'op', metavar='', choices=['add', 'replace', 'remove'], help=_("Operation: 'add', 'replace', or 'remove'.")) @cliutils.arg( 'attributes', metavar='', nargs='+', action='append', default=[], help=_("Attribute to add, replace, or remove. Can be specified multiple " "times. For 'remove', only is necessary.")) def do_volume_connector_update(cc, args): """Update information about a volume connector.""" patch = utils.args_array_to_patch(args.op, args.attributes[0]) volume_connector = cc.volume_connector.update(args.volume_connector, patch) _print_volume_connector_show(volume_connector, json=args.json) python-ironicclient-2.2.0/ironicclient/v1/shell.py0000666000175100017510000000326413232474343022245 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 ironicclient.common import utils from ironicclient.v1 import chassis_shell from ironicclient.v1 import create_resources_shell from ironicclient.v1 import driver_shell from ironicclient.v1 import node_shell from ironicclient.v1 import port_shell from ironicclient.v1 import portgroup_shell from ironicclient.v1 import volume_connector_shell from ironicclient.v1 import volume_target_shell COMMAND_MODULES = [ chassis_shell, node_shell, port_shell, portgroup_shell, driver_shell, create_resources_shell, volume_connector_shell, volume_target_shell, ] def enhance_parser(parser, subparsers, cmd_mapper): """Enhance parser with API version specific options. Take a basic (nonversioned) parser and enhance it with commands and options specific for this version of API. :param parser: top level parser :param subparsers: top level parser's subparsers collection where subcommands will go """ for command_module in COMMAND_MODULES: utils.define_commands_from_module(subparsers, command_module, cmd_mapper) python-ironicclient-2.2.0/ironicclient/v1/port.py0000666000175100017510000001100713232474343022114 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # # Copyright © 2013 Red Hat, Inc # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ironicclient.common import base from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient import exc class Port(base.Resource): def __repr__(self): return "" % self._info class PortManager(base.CreateManager): resource_class = Port _creation_attributes = ['address', 'extra', 'local_link_connection', 'node_uuid', 'physical_network', 'portgroup_uuid', 'pxe_enabled', 'uuid'] _resource_name = 'ports' def list(self, address=None, limit=None, marker=None, sort_key=None, sort_dir=None, detail=False, fields=None, node=None, portgroup=None): """Retrieve a list of ports. :param address: Optional, MAC address of a port, to get the port which has this MAC address :param marker: Optional, the UUID of a port, eg the last port from a previous result set. Return the next result set. :param limit: The maximum number of results to return per request, if: 1) limit > 0, the maximum number of ports to return. 2) limit == 0, return the entire list of ports. 3) limit param is NOT specified (None), the number of items returned respect the maximum imposed by the Ironic API (see Ironic's api.max_limit option). :param sort_key: Optional, field used for sorting. :param sort_dir: Optional, direction of sorting, either 'asc' (the default) or 'desc'. :param detail: Optional, boolean whether to return detailed information about ports. :param fields: Optional, a list with a specified set of fields of the resource to be returned. Can not be used when 'detail' is set. :param node: Optional, name or UUID of a node. Used to get ports of this node. :param portgroup: Optional, name or UUID of a portgroup. Used to get ports of this portgroup. :returns: A list of ports. """ if limit is not None: limit = int(limit) if detail and fields: raise exc.InvalidAttribute(_("Can't fetch a subset of fields " "with 'detail' set")) filters = utils.common_filters(marker, limit, sort_key, sort_dir, fields) if address is not None: filters.append('address=%s' % address) if node is not None: filters.append('node=%s' % node) if portgroup is not None: filters.append('portgroup=%s' % portgroup) path = '' if detail: path += 'detail' if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(self._path(path), "ports") else: return self._list_pagination(self._path(path), "ports", limit=limit) def get(self, port_id, fields=None): return self._get(resource_id=port_id, fields=fields) def get_by_address(self, address, fields=None): path = '?address=%s' % address if fields is not None: path += '&fields=' + ','.join(fields) else: path = 'detail' + path ports = self._list(self._path(path), 'ports') # get all the details of the port assuming that filtering by # address returns a collection of one port if successful. if len(ports) == 1: return ports[0] else: raise exc.NotFound() def delete(self, port_id): return self._delete(resource_id=port_id) def update(self, port_id, patch): return self._update(resource_id=port_id, patch=patch) python-ironicclient-2.2.0/ironicclient/v1/node_shell.py0000666000175100017510000006473213232474343023261 0ustar zuulzuul00000000000000# Copyright 2013 Red Hat, Inc. # 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 argparse import six from ironicclient.common.apiclient import exceptions from ironicclient.common import cliutils from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient import exc from ironicclient.v1 import resource_fields as res_fields from ironicclient.v1 import utils as v1_utils def _print_node_show(node, fields=None, json=False): if fields is None: fields = res_fields.NODE_DETAILED_RESOURCE.fields data = dict( [(f, getattr(node, f, '')) for f in fields]) cliutils.print_dict(data, wrap=72, json_flag=json) @cliutils.arg( 'node', metavar='', help="Name or UUID of the node " "(or instance UUID if --instance is specified).") @cliutils.arg( '--instance', dest='instance_uuid', action='store_true', default=False, help=' is an instance UUID.') @cliutils.arg( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], help="One or more node fields. Only these fields will be fetched from " "the server.") def do_node_show(cc, args): """Show detailed information about a node.""" fields = args.fields[0] if args.fields else None utils.check_empty_arg(args.node, '') utils.check_for_invalid_fields( fields, res_fields.NODE_DETAILED_RESOURCE.fields) if args.instance_uuid: node = cc.node.get_by_instance_uuid(args.node, fields=fields) else: node = cc.node.get(args.node, fields=fields) _print_node_show(node, fields=fields, json=args.json) @cliutils.arg( '--limit', metavar='', type=int, help='Maximum number of nodes to return per request, ' '0 for no limit. Default is the maximum number used ' 'by the Ironic API Service.') @cliutils.arg( '--marker', metavar='', help='Node UUID (for example, of the last node in the list from ' 'a previous request). Returns the list of nodes after this UUID.') @cliutils.arg( '--sort-key', metavar='', help='Node field that will be used for sorting.') @cliutils.arg( '--sort-dir', metavar='', choices=['asc', 'desc'], help='Sort direction: "asc" (the default) or "desc".') @cliutils.arg( '--maintenance', metavar='', help="List nodes in maintenance mode: 'true' or 'false'.") @cliutils.arg( '--associated', metavar='', help="List nodes by instance association: 'true' or 'false'.") @cliutils.arg( '--provision-state', metavar='', help="List nodes in specified provision state.") @cliutils.arg( '--driver', metavar='', help="List nodes using specified driver.") @cliutils.arg( '--detail', dest='detail', action='store_true', default=False, help="Show detailed information about the nodes.") @cliutils.arg( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], help="One or more node fields. Only these fields will be fetched from " "the server. Can not be used when '--detail' is specified.") @cliutils.arg( '--resource-class', dest='resource_class', metavar='', help="List nodes using specified resource class.") def do_node_list(cc, args): """List the nodes which are registered with the Ironic service.""" params = {} if args.associated is not None: params['associated'] = utils.bool_argument_value("--associated", args.associated) if args.maintenance is not None: params['maintenance'] = utils.bool_argument_value("--maintenance", args.maintenance) if args.provision_state is not None: params['provision_state'] = args.provision_state if args.driver is not None: params['driver'] = args.driver if args.resource_class is not None: params['resource_class'] = args.resource_class if args.detail: fields = res_fields.NODE_DETAILED_RESOURCE.fields field_labels = res_fields.NODE_DETAILED_RESOURCE.labels elif args.fields: utils.check_for_invalid_fields( args.fields[0], res_fields.NODE_DETAILED_RESOURCE.fields) resource = res_fields.Resource(args.fields[0]) fields = resource.fields field_labels = resource.labels else: fields = res_fields.NODE_RESOURCE.fields field_labels = res_fields.NODE_RESOURCE.labels sort_fields = res_fields.NODE_DETAILED_RESOURCE.sort_fields sort_field_labels = res_fields.NODE_DETAILED_RESOURCE.sort_labels params.update(utils.common_params_for_list(args, sort_fields, sort_field_labels)) nodes = cc.node.list(**params) cliutils.print_list(nodes, fields, field_labels=field_labels, sortby_index=None, json_flag=args.json) @cliutils.arg( '-c', '--chassis', dest='chassis_uuid', metavar='', help='UUID of the chassis that this node belongs to.') @cliutils.arg( '--chassis_uuid', help=argparse.SUPPRESS) @cliutils.arg( '-d', '--driver', metavar='', required=True, help='Driver used to control the node.') @cliutils.arg( '-i', '--driver-info', metavar='', action='append', help='Key/value pair used by the driver, such as out-of-band management ' 'credentials. Can be specified multiple times.') @cliutils.arg( '--driver_info', action='append', help=argparse.SUPPRESS) @cliutils.arg( '-p', '--properties', metavar='', action='append', help='Key/value pair describing the physical characteristics of the ' 'node. This is exported to Nova and used by the scheduler. ' 'Can be specified multiple times.') @cliutils.arg( '-e', '--extra', metavar='', action='append', help="Record arbitrary key/value metadata. " "Can be specified multiple times.") @cliutils.arg( '-u', '--uuid', metavar='', help="Unique UUID for the node.") @cliutils.arg( '-n', '--name', metavar='', help="Unique name for the node.") @cliutils.arg( '--boot-interface', metavar='', help='Boot interface used by the node\'s driver. This is ' 'only applicable when the specified --driver is a ' 'hardware type.') @cliutils.arg( '--console-interface', metavar='', help='Console interface used by the node\'s driver. This is ' 'only applicable when the specified --driver is a ' 'hardware type.') @cliutils.arg( '--deploy-interface', metavar='', help='Deploy interface used by the node\'s driver. This is ' 'only applicable when the specified --driver is a ' 'hardware type.') @cliutils.arg( '--inspect-interface', metavar='', help='Inspect interface used by the node\'s driver. This is ' 'only applicable when the specified --driver is a ' 'hardware type.') @cliutils.arg( '--management-interface', metavar='', help='Management interface used by the node\'s driver. This is ' 'only applicable when the specified --driver is a ' 'hardware type.') @cliutils.arg( '--network-interface', metavar='', help='Network interface used for switching node to cleaning/provisioning ' 'networks.') @cliutils.arg( '--power-interface', metavar='', help='Power interface used by the node\'s driver. This is ' 'only applicable when the specified --driver is a ' 'hardware type.') @cliutils.arg( '--raid-interface', metavar='', help='RAID interface used by the node\'s driver. This is ' 'only applicable when the specified --driver is a ' 'hardware type.') @cliutils.arg( '--storage-interface', metavar='', help='Storage interface used by the node\'s driver.') @cliutils.arg( '--vendor-interface', metavar='', help='Vendor interface used by the node\'s driver. This is ' 'only applicable when the specified --driver is a ' 'hardware type.') @cliutils.arg( '--resource-class', metavar='', help='Resource class for classifying or grouping nodes. Used, for ' 'example, to classify nodes in Nova\'s placement engine.') def do_node_create(cc, args): """Register a new node with the Ironic service.""" field_list = ['chassis_uuid', 'driver', 'driver_info', 'properties', 'extra', 'uuid', 'name', 'boot_interface', 'console_interface', 'deploy_interface', 'inspect_interface', 'management_interface', 'network_interface', 'power_interface', 'raid_interface', 'storage_interface', 'vendor_interface', 'resource_class'] fields = dict((k, v) for (k, v) in vars(args).items() if k in field_list and not (v is None)) fields = utils.args_array_to_dict(fields, 'driver_info') fields = utils.args_array_to_dict(fields, 'extra') fields = utils.args_array_to_dict(fields, 'properties') node = cc.node.create(**fields) data = dict([(f, getattr(node, f, '')) for f in field_list]) cliutils.print_dict(data, wrap=72, json_flag=args.json) @cliutils.arg('node', metavar='', nargs='+', help="Name or UUID of the node.") def do_node_delete(cc, args): """Unregister node(s) from the Ironic service. Returns errors for any nodes that could not be unregistered. """ failures = [] for n in args.node: try: cc.node.delete(n) print(_('Deleted node %s') % n) except exceptions.ClientException as e: failures.append(_("Failed to delete node %(node)s: %(error)s") % {'node': n, 'error': e}) if failures: raise exceptions.ClientException("\n".join(failures)) @cliutils.arg('node', metavar='', help="Name or UUID of the node.") @cliutils.arg( 'op', metavar='', choices=['add', 'replace', 'remove'], help="Operation: 'add', 'replace', or 'remove'.") @cliutils.arg( 'attributes', metavar='', nargs='+', action='append', default=[], help="Attribute to add, replace, or remove. Can be specified " "multiple times. For 'remove', only is necessary. " "For nested attributes, separate the components with slashes, eg " "'driver_info/deploy_kernel=uuid'.") def do_node_update(cc, args): """Update information about a registered node.""" patch = utils.args_array_to_patch(args.op, args.attributes[0]) node = cc.node.update(args.node, patch) _print_node_show(node, json=args.json) @cliutils.arg('node', metavar='', help="Name or UUID of the node.") @cliutils.arg('method', metavar='', help="Vendor-passthru method to be called.") @cliutils.arg('arguments', metavar='', nargs='*', action='append', default=[], help=("Argument to be passed to the vendor-passthru method. Can " "be specified multiple times.")) @cliutils.arg('--http-method', metavar='', choices=v1_utils.HTTP_METHODS, help=("The HTTP method to use in the request. Valid HTTP " "methods are: %s. Defaults to 'POST'." % ', '.join(v1_utils.HTTP_METHODS))) @cliutils.arg('--http_method', help=argparse.SUPPRESS) def do_node_vendor_passthru(cc, args): """Call a vendor-passthru extension for a node.""" arguments = utils.key_value_pairs_to_dict(args.arguments[0]) resp = cc.node.vendor_passthru(args.node, args.method, http_method=args.http_method, args=arguments) if resp: # Print the raw response we don't know how it should be formated print(str(resp.to_dict())) @cliutils.arg( '--detail', dest='detail', action='store_true', default=False, help="Show detailed information about the ports.") @cliutils.arg( '--limit', metavar='', type=int, help='Maximum number of ports to return per request, ' '0 for no limit. Default is the maximum number used ' 'by the Ironic API Service.') @cliutils.arg( '--marker', metavar='', help='Port UUID (for example, of the last port in the list from a ' 'previous request). Returns the list of ports after this UUID.') @cliutils.arg( '--sort-key', metavar='', help='Port field that will be used for sorting.') @cliutils.arg( '--sort-dir', metavar='', choices=['asc', 'desc'], help='Sort direction: "asc" (the default) or "desc".') @cliutils.arg('node', metavar='', help="Name or UUID of the node.") @cliutils.arg( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], help="One or more port fields. Only these fields will be fetched from " "the server. Can not be used when '--detail' is specified.") def do_node_port_list(cc, args): """List the ports associated with a node.""" if args.detail: fields = res_fields.PORT_DETAILED_RESOURCE.fields field_labels = res_fields.PORT_DETAILED_RESOURCE.labels elif args.fields: utils.check_for_invalid_fields( args.fields[0], res_fields.PORT_DETAILED_RESOURCE.fields) resource = res_fields.Resource(args.fields[0]) fields = resource.fields field_labels = resource.labels else: fields = res_fields.PORT_RESOURCE.fields field_labels = res_fields.PORT_RESOURCE.labels sort_fields = res_fields.PORT_DETAILED_RESOURCE.sort_fields sort_field_labels = res_fields.PORT_DETAILED_RESOURCE.sort_labels params = utils.common_params_for_list(args, sort_fields, sort_field_labels) ports = cc.node.list_ports(args.node, **params) cliutils.print_list(ports, fields, field_labels=field_labels, sortby_index=None, json_flag=args.json) @cliutils.arg('node', metavar='', help="Name or UUID of the node.") @cliutils.arg( 'maintenance_mode', metavar='', help="'true' or 'false'; 'on' or 'off'.") @cliutils.arg( '--reason', metavar='', default=None, help=("Reason for setting maintenance mode to 'true' or 'on';" " not valid when setting to 'false' or 'off'.")) def do_node_set_maintenance(cc, args): """Enable or disable maintenance mode for a node.""" maintenance_mode = utils.bool_argument_value("", args.maintenance_mode) if args.reason and not maintenance_mode: raise exceptions.CommandError(_('Cannot set "reason" when turning off ' 'maintenance mode.')) cc.node.set_maintenance(args.node, maintenance_mode, maint_reason=args.reason) @cliutils.arg('node', metavar='', help="Name or UUID of the node.") @cliutils.arg( 'power_state', metavar='', choices=['on', 'off', 'reboot'], help="'on', 'off', or 'reboot'.") @cliutils.arg( '--soft', dest='soft', action='store_true', default=False, help=("Gracefully change the power state. Only valid for 'off' and " "'reboot' power states.")) @cliutils.arg( '--power-timeout', metavar='', type=int, default=None, help=("Timeout (in seconds, positive integer) to wait for the target " "power state before erroring out.")) def do_node_set_power_state(cc, args): """Power a node on or off or reboot.""" try: cc.node.set_power_state(args.node, args.power_state, args.soft, timeout=args.power_timeout) except ValueError as e: raise exc.CommandError(six.text_type(e)) @cliutils.arg('node', metavar='', help="Name or UUID of the node.") @cliutils.arg( 'target_raid_config', metavar='', help=("A file containing JSON data of the desired RAID configuration. " "Use '-' to read the contents from standard input. " "It also accepts the valid json string as input if " "file/standard input are not used for providing input. " "The input can be an empty dictionary too which " "unsets the node.target_raid_config on the node.")) def do_node_set_target_raid_config(cc, args): """Set target RAID config on a node.""" target_raid_config = args.target_raid_config if not target_raid_config: raise exc.InvalidAttribute( _("target RAID configuration not provided")) if target_raid_config == '-': target_raid_config = utils.get_from_stdin('target_raid_config') target_raid_config = utils.handle_json_or_file_arg(target_raid_config) cc.node.set_target_raid_config(args.node, target_raid_config) @cliutils.arg('node', metavar='', help="Name or UUID of the node.") @cliutils.arg( 'provision_state', metavar='', choices=v1_utils.PROVISION_STATES, help="Supported states: %s." % ', '.join(v1_utils.PROVISION_STATES)) @cliutils.arg( '--config-drive', metavar='', default=None, help=("A gzipped, base64-encoded configuration drive string OR the path " "to the configuration drive file OR the path to a directory " "containing the config drive files. In case it's a directory, a " "config drive will be generated from it. This argument is only " "valid when setting provision-state to 'active'.")) @cliutils.arg( '--clean-steps', metavar='', default=None, help=("The clean steps in JSON format. May be the path to a file " "containing the clean steps; OR '-', with the clean steps being " "read from standard input; OR a string. The value should be " "a list of clean-step dictionaries; each dictionary should have " "keys 'interface' and 'step', and optional key 'args'. " "This argument must be specified (and is only valid) when " "setting provision-state to 'clean'.")) @cliutils.arg( '--wait', type=int, dest='wait_timeout', default=None, const=0, nargs='?', help=("Wait for a node to reach the expected state. Not supported " "for 'abort'. Optionally takes a timeout in seconds. " "The default value is 0, meaning no timeout. " "Fails if the node reaches an unexpected stable state, a failure " "state or a state with last_error set.")) def do_node_set_provision_state(cc, args): """Initiate a provisioning state change for a node.""" if args.config_drive and args.provision_state != 'active': raise exceptions.CommandError(_('--config-drive is only valid when ' 'setting provision state to "active"')) elif args.clean_steps and args.provision_state != 'clean': raise exceptions.CommandError(_('--clean-steps is only valid when ' 'setting provision state to "clean"')) elif args.provision_state == 'clean' and not args.clean_steps: raise exceptions.CommandError(_('--clean-steps must be specified when ' 'setting provision state to "clean"')) if args.wait_timeout is not None: wait_args = v1_utils.PROVISION_ACTIONS.get(args.provision_state) if wait_args is None: raise exceptions.CommandError( _("--wait is not supported for provision state '%s'") % args.provision_state) clean_steps = args.clean_steps if args.clean_steps == '-': clean_steps = utils.get_from_stdin('clean steps') if clean_steps: clean_steps = utils.handle_json_or_file_arg(clean_steps) cc.node.set_provision_state(args.node, args.provision_state, configdrive=args.config_drive, cleansteps=clean_steps) if args.wait_timeout is not None: print(_('Waiting for provision state %(state)s on node %(node)s') % {'state': wait_args['expected_state'], 'node': args.node}) cc.node.wait_for_provision_state(args.node, timeout=args.wait_timeout, **wait_args) @cliutils.arg('node', metavar='', help="Name or UUID of the node.") def do_node_validate(cc, args): """Validate a node's driver interfaces.""" ifaces = cc.node.validate(args.node) obj_list = [] for key, value in ifaces.to_dict().items(): data = {'interface': key} data.update(value) obj_list.append(type('iface', (object,), data)) field_labels = ['Interface', 'Result', 'Reason'] fields = ['interface', 'result', 'reason'] cliutils.print_list(obj_list, fields, field_labels=field_labels, json_flag=args.json) @cliutils.arg('node', metavar='', help="Name or UUID of the node.") def do_node_get_console(cc, args): """Get the connection information for a node's console, if enabled.""" info = cc.node.get_console(args.node) cliutils.print_dict(info, wrap=72, json_flag=args.json) @cliutils.arg('node', metavar='', help="Name or UUID of the node.") @cliutils.arg( 'enabled', metavar='', help="Enable or disable console access for a node: 'true' or 'false'.") def do_node_set_console_mode(cc, args): """Enable or disable serial console access for a node.""" enable = utils.bool_argument_value("", args.enabled) cc.node.set_console_mode(args.node, enable) @cliutils.arg('node', metavar='', help="Name or UUID of the node.") @cliutils.arg( 'device', metavar='', choices=v1_utils.BOOT_DEVICES, help="One of %s." % ', '.join(v1_utils.BOOT_DEVICES)) @cliutils.arg( '--persistent', dest='persistent', action='store_true', default=False, help="Make changes persistent for all future boots.") def do_node_set_boot_device(cc, args): """Set the boot device for a node.""" cc.node.set_boot_device(args.node, args.device, args.persistent) @cliutils.arg('node', metavar='', help="Name or UUID of the node.") def do_node_get_boot_device(cc, args): """Get the current boot device for a node.""" boot_device = cc.node.get_boot_device(args.node) cliutils.print_dict(boot_device, wrap=72, json_flag=args.json) @cliutils.arg('node', metavar='', help="Name or UUID of the node.") def do_node_inject_nmi(cc, args): """Inject NMI to a node.""" cc.node.inject_nmi(args.node) @cliutils.arg('node', metavar='', help="Name or UUID of the node.") def do_node_get_supported_boot_devices(cc, args): """Get the supported boot devices for a node.""" boot_devices = cc.node.get_supported_boot_devices(args.node) boot_device_list = boot_devices.get('supported_boot_devices', []) boot_devices['supported_boot_devices'] = ', '.join(boot_device_list) cliutils.print_dict(boot_devices, wrap=72, json_flag=args.json) @cliutils.arg('node', metavar='', help="Name or UUID of the node.") def do_node_show_states(cc, args): """Show information about the node's states.""" states = cc.node.states(args.node) cliutils.print_dict(states.to_dict(), wrap=72, json_flag=args.json) @cliutils.arg('node', metavar='', help="Name or UUID of the node.") def do_node_get_vendor_passthru_methods(cc, args): """Get the vendor passthru methods for a node.""" methods = cc.node.get_vendor_passthru_methods(args.node) data = [] for method, response in methods.items(): response['name'] = method http_methods = ','.join(response['http_methods']) response['http_methods'] = http_methods data.append(response) fields = res_fields.VENDOR_PASSTHRU_METHOD_RESOURCE.fields field_labels = res_fields.VENDOR_PASSTHRU_METHOD_RESOURCE.labels cliutils.print_list(data, fields, field_labels=field_labels, sortby_index=None, json_flag=args.json) @cliutils.arg('node', metavar='', help="Name or UUID of the node.") def do_node_vif_list(cc, args): """List VIFs for a given node.""" vifs = cc.node.vif_list(args.node) fields = res_fields.VIF_RESOURCE.fields field_labels = res_fields.VIF_RESOURCE.labels cliutils.print_list(vifs, fields, field_labels=field_labels, sortby_index=None, json_flag=args.json) @cliutils.arg('node', metavar='', help="Name or UUID of the node.") @cliutils.arg('vif_id', metavar='', help="Name or UUID of the VIF to attach to node.") @cliutils.arg('--vif-info', metavar='', action='append', help="Record arbitrary key/value metadata. " "Can be specified multiple times. The mandatory 'id' " "parameter cannot be specified as a key.") def do_node_vif_attach(cc, args): """Attach VIF to a given node.""" fields = utils.key_value_pairs_to_dict(args.vif_info or []) cc.node.vif_attach(args.node, args.vif_id, **fields) @cliutils.arg('node', metavar='', help="Name or UUID of the node.") @cliutils.arg('vif_id', metavar='', help="Name or UUID of the VIF to detach from node.") def do_node_vif_detach(cc, args): """Detach VIF from a given node.""" cc.node.vif_detach(args.node, args.vif_id) python-ironicclient-2.2.0/ironicclient/v1/volume_connector.py0000666000175100017510000000733013232474343024515 0ustar zuulzuul00000000000000# Copyright 2015 Hitachi Data Systems # # 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 ironicclient.common import base from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient import exc class VolumeConnector(base.Resource): def __repr__(self): return "" % self._info class VolumeConnectorManager(base.CreateManager): resource_class = VolumeConnector _creation_attributes = ['extra', 'node_uuid', 'type', 'connector_id', 'uuid'] _resource_name = 'volume/connectors' def list(self, node=None, limit=None, marker=None, sort_key=None, sort_dir=None, detail=False, fields=None): """Retrieve a list of volume connector. :param node: Optional, UUID or name of a node, to get volume connectors for this node only. :param marker: Optional, the UUID of a volume connector, eg the last volume connector from a previous result set. Return the next result set. :param limit: The maximum number of results to return per request, if: 1) limit > 0, the maximum number of volume connectors to return. 2) limit == 0, return the entire list of volume connectors. 3) limit param is NOT specified (None), the number of items returned respect the maximum imposed by the Ironic API (see Ironic's api.max_limit option). :param sort_key: Optional, field used for sorting. :param sort_dir: Optional, direction of sorting, either 'asc' (the default) or 'desc'. :param detail: Optional, boolean whether to return detailed information about volume connectors. :param fields: Optional, a list with a specified set of fields of the resource to be returned. Can not be used when 'detail' is set. :returns: A list of volume connectors. """ if limit is not None: limit = int(limit) if detail and fields: raise exc.InvalidAttribute(_("Can't fetch a subset of fields " "with 'detail' set")) filters = utils.common_filters(marker=marker, limit=limit, sort_key=sort_key, sort_dir=sort_dir, fields=fields, detail=detail) if node is not None: filters.append('node=%s' % node) path = '' if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(self._path(path), "connectors") else: return self._list_pagination(self._path(path), "connectors", limit=limit) def get(self, volume_connector_id, fields=None): return self._get(resource_id=volume_connector_id, fields=fields) def delete(self, volume_connector_id): return self._delete(resource_id=volume_connector_id) def update(self, volume_connector_id, patch): return self._update(resource_id=volume_connector_id, patch=patch) python-ironicclient-2.2.0/ironicclient/v1/chassis.py0000666000175100017510000001465113232474343022575 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # # Copyright © 2013 Red Hat, Inc # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ironicclient.common import base from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient import exc class Chassis(base.Resource): def __repr__(self): return "" % self._info class ChassisManager(base.CreateManager): resource_class = Chassis _resource_name = 'chassis' _creation_attributes = ['description', 'extra', 'uuid'] def list(self, marker=None, limit=None, sort_key=None, sort_dir=None, detail=False, fields=None): """Retrieve a list of chassis. :param marker: Optional, the UUID of a chassis, eg the last chassis from a previous result set. Return the next result set. :param limit: The maximum number of results to return per request, if: 1) limit > 0, the maximum number of chassis to return. 2) limit == 0, return the entire list of chassis. 3) limit param is NOT specified (None), the number of items returned respect the maximum imposed by the Ironic API (see Ironic's api.max_limit option). :param sort_key: Optional, field used for sorting. :param sort_dir: Optional, direction of sorting, either 'asc' (the default) or 'desc'. :param detail: Optional, boolean whether to return detailed information about chassis. :param fields: Optional, a list with a specified set of fields of the resource to be returned. Can not be used when 'detail' is set. :returns: A list of chassis. """ if limit is not None: limit = int(limit) if detail and fields: raise exc.InvalidAttribute(_("Can't fetch a subset of fields " "with 'detail' set")) filters = utils.common_filters(marker, limit, sort_key, sort_dir, fields) path = '' if detail: path += 'detail' if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(self._path(path), "chassis") else: return self._list_pagination(self._path(path), "chassis", limit=limit) def list_nodes(self, chassis_id, marker=None, limit=None, sort_key=None, sort_dir=None, detail=False, fields=None, associated=None, maintenance=None, provision_state=None): """List all the nodes for a given chassis. :param chassis_id: The UUID of the chassis. :param marker: Optional, the UUID of a node, eg the last node from a previous result set. Return the next result set. :param limit: The maximum number of results to return per request, if: 1) limit > 0, the maximum number of nodes to return. 2) limit == 0, return the entire list of nodes. 3) limit param is NOT specified (None), the number of items returned respect the maximum imposed by the Ironic API (see Ironic's api.max_limit option). :param sort_key: Optional, field used for sorting. :param sort_dir: Optional, direction of sorting, either 'asc' (the default) or 'desc'. :param detail: Optional, boolean whether to return detailed information about nodes. :param fields: Optional, a list with a specified set of fields of the resource to be returned. Can not be used when 'detail' is set. :param associated: Optional. Either a Boolean or a string representation of a Boolean that indicates whether to return a list of associated (True or "True") or unassociated (False or "False") nodes. :param maintenance: Optional. Either a Boolean or a string representation of a Boolean that indicates whether to return nodes in maintenance mode (True or "True"), or not in maintenance mode (False or "False"). :param provision_state: Optional. String value to get only nodes in that provision state. :returns: A list of nodes. """ if limit is not None: limit = int(limit) if detail and fields: raise exc.InvalidAttribute(_("Can't fetch a subset of fields " "with 'detail' set")) filters = utils.common_filters(marker, limit, sort_key, sort_dir, fields) if associated is not None: filters.append('associated=%s' % associated) if maintenance is not None: filters.append('maintenance=%s' % maintenance) if provision_state is not None: filters.append('provision_state=%s' % provision_state) path = "%s/nodes" % chassis_id if detail: path += '/detail' if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(self._path(path), "nodes") else: return self._list_pagination(self._path(path), "nodes", limit=limit) def get(self, chassis_id, fields=None): return self._get(resource_id=chassis_id, fields=fields) def delete(self, chassis_id): return self._delete(resource_id=chassis_id) def update(self, chassis_id, patch): return self._update(resource_id=chassis_id, patch=patch) python-ironicclient-2.2.0/ironicclient/v1/node.py0000666000175100017510000006566513232474373022103 0ustar zuulzuul00000000000000# Copyright 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging import os import time from oslo_utils import strutils from ironicclient.common import base from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient import exc from ironicclient.v1 import volume_connector from ironicclient.v1 import volume_target _power_states = { 'on': 'power on', 'off': 'power off', 'reboot': 'rebooting', 'soft off': 'soft power off', 'soft reboot': 'soft rebooting', } LOG = logging.getLogger(__name__) _DEFAULT_POLL_INTERVAL = 2 class Node(base.Resource): def __repr__(self): return "" % self._info class NodeManager(base.CreateManager): resource_class = Node _creation_attributes = ['chassis_uuid', 'driver', 'driver_info', 'extra', 'uuid', 'properties', 'name', 'boot_interface', 'console_interface', 'deploy_interface', 'inspect_interface', 'management_interface', 'network_interface', 'power_interface', 'raid_interface', 'storage_interface', 'vendor_interface', 'resource_class'] _resource_name = 'nodes' def list(self, associated=None, maintenance=None, marker=None, limit=None, detail=False, sort_key=None, sort_dir=None, fields=None, provision_state=None, driver=None, resource_class=None, chassis=None): """Retrieve a list of nodes. :param associated: Optional. Either a Boolean or a string representation of a Boolean that indicates whether to return a list of associated (True or "True") or unassociated (False or "False") nodes. :param maintenance: Optional. Either a Boolean or a string representation of a Boolean that indicates whether to return nodes in maintenance mode (True or "True"), or not in maintenance mode (False or "False"). :param provision_state: Optional. String value to get only nodes in that provision state. :param marker: Optional, the UUID of a node, eg the last node from a previous result set. Return the next result set. :param limit: The maximum number of results to return per request, if: 1) limit > 0, the maximum number of nodes to return. 2) limit == 0, return the entire list of nodes. 3) limit param is NOT specified (None), the number of items returned respect the maximum imposed by the Ironic API (see Ironic's api.max_limit option). :param detail: Optional, boolean whether to return detailed information about nodes. :param sort_key: Optional, field used for sorting. :param sort_dir: Optional, direction of sorting, either 'asc' (the default) or 'desc'. :param fields: Optional, a list with a specified set of fields of the resource to be returned. Can not be used when 'detail' is set. :param driver: Optional. String value to get only nodes using that driver. :param resource_class: Optional. String value to get only nodes with the given resource class set. :param chassis: Optional, the UUID of a chassis. Used to get only nodes of this chassis. :returns: A list of nodes. """ if limit is not None: limit = int(limit) if detail and fields: raise exc.InvalidAttribute(_("Can't fetch a subset of fields " "with 'detail' set")) filters = utils.common_filters(marker, limit, sort_key, sort_dir, fields) if associated is not None: filters.append('associated=%s' % associated) if maintenance is not None: filters.append('maintenance=%s' % maintenance) if provision_state is not None: filters.append('provision_state=%s' % provision_state) if driver is not None: filters.append('driver=%s' % driver) if resource_class is not None: filters.append('resource_class=%s' % resource_class) if chassis is not None: filters.append('chassis_uuid=%s' % chassis) path = '' if detail: path += 'detail' if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(self._path(path), "nodes") else: return self._list_pagination(self._path(path), "nodes", limit=limit) def list_ports(self, node_id, marker=None, limit=None, sort_key=None, sort_dir=None, detail=False, fields=None): """List all the ports for a given node. :param node_id: Name or UUID of the node. :param marker: Optional, the UUID of a port, eg the last port from a previous result set. Return the next result set. :param limit: The maximum number of results to return per request, if: 1) limit > 0, the maximum number of ports to return. 2) limit == 0, return the entire list of ports. 3) limit param is NOT specified (None), the number of items returned respect the maximum imposed by the Ironic API (see Ironic's api.max_limit option). :param sort_key: Optional, field used for sorting. :param sort_dir: Optional, direction of sorting, either 'asc' (the default) or 'desc'. :param detail: Optional, boolean whether to return detailed information about ports. :param fields: Optional, a list with a specified set of fields of the resource to be returned. Can not be used when 'detail' is set. :returns: A list of ports. """ if limit is not None: limit = int(limit) if detail and fields: raise exc.InvalidAttribute(_("Can't fetch a subset of fields " "with 'detail' set")) filters = utils.common_filters(marker, limit, sort_key, sort_dir, fields) path = "%s/ports" % node_id if detail: path += '/detail' if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(self._path(path), "ports") else: return self._list_pagination(self._path(path), "ports", limit=limit) def list_volume_connectors(self, node_id, marker=None, limit=None, sort_key=None, sort_dir=None, detail=False, fields=None): """List all the volume connectors for a given node. :param node_id: Name or UUID of the node. :param marker: Optional, the UUID of a volume connector, eg the last volume connector from a previous result set. Return the next result set. :param limit: The maximum number of results to return per request, if: 1) limit > 0, the maximum number of volume connectors to return. 2) limit == 0, return the entire list of volume connectors. 3) limit param is NOT specified (None), the number of items returned respect the maximum imposed by the Ironic API (see Ironic's api.max_limit option). :param sort_key: Optional, field used for sorting. :param sort_dir: Optional, direction of sorting, either 'asc' (the default) or 'desc'. :param detail: Optional, boolean whether to return detailed information about volume connectors. :param fields: Optional, a list with a specified set of fields of the resource to be returned. Can not be used when 'detail' is set. :returns: A list of volume connectors. """ if limit is not None: limit = int(limit) if detail and fields: raise exc.InvalidAttribute(_("Can't fetch a subset of fields " "with 'detail' set")) filters = utils.common_filters(marker=marker, limit=limit, sort_key=sort_key, sort_dir=sort_dir, fields=fields, detail=detail) path = "%s/volume/connectors" % node_id if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(self._path(path), response_key="connectors", obj_class=volume_connector.VolumeConnector) else: return self._list_pagination( self._path(path), response_key="connectors", limit=limit, obj_class=volume_connector.VolumeConnector) def list_volume_targets(self, node_id, marker=None, limit=None, sort_key=None, sort_dir=None, detail=False, fields=None): """List all the volume targets for a given node. :param node_id: Name or UUID of the node. :param marker: Optional, the UUID of a volume target, eg the last volume target from a previous result set. Return the next result set. :param limit: The maximum number of results to return per request, if: 1) limit > 0, the maximum number of volume targets to return. 2) limit == 0, return the entire list of volume targets. 3) limit param is NOT specified (None), the number of items returned respect the maximum imposed by the Ironic API (see Ironic's api.max_limit option). :param sort_key: Optional, field used for sorting. :param sort_dir: Optional, direction of sorting, either 'asc' (the default) or 'desc'. :param detail: Optional, boolean whether to return detailed information about volume targets. :param fields: Optional, a list with a specified set of fields of the resource to be returned. Can not be used when 'detail' is set. :returns: A list of volume targets. """ if limit is not None: limit = int(limit) if detail and fields: raise exc.InvalidAttribute(_("Can't fetch a subset of fields " "with 'detail' set")) filters = utils.common_filters(marker=marker, limit=limit, sort_key=sort_key, sort_dir=sort_dir, fields=fields, detail=detail) path = "%s/volume/targets" % node_id if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(self._path(path), response_key="targets", obj_class=volume_target.VolumeTarget) else: return self._list_pagination( self._path(path), response_key="targets", limit=limit, obj_class=volume_target.VolumeTarget) def get(self, node_id, fields=None): return self._get(resource_id=node_id, fields=fields) def get_by_instance_uuid(self, instance_uuid, fields=None): path = '?instance_uuid=%s' % instance_uuid if fields is not None: path += '&fields=' + ','.join(fields) else: path = 'detail' + path nodes = self._list(self._path(path), 'nodes') # get all the details of the node assuming that # filtering by instance_uuid returns a collection # of one node if successful. if len(nodes) == 1: return nodes[0] else: raise exc.NotFound() def delete(self, node_id): return self._delete(resource_id=node_id) def update(self, node_id, patch, http_method='PATCH'): return self._update(resource_id=node_id, patch=patch, method=http_method) def vendor_passthru(self, node_id, method, args=None, http_method=None): """Issue requests for vendor-specific actions on a given node. :param node_id: The UUID of the node. :param method: Name of the vendor method. :param args: Optional. The arguments to be passed to the method. :param http_method: The HTTP method to use on the request. Defaults to POST. """ if args is None: args = {} if http_method is None: http_method = 'POST' http_method = http_method.upper() path = "%s/vendor_passthru/%s" % (node_id, method) if http_method in ('POST', 'PUT', 'PATCH'): return self.update(path, args, http_method=http_method) elif http_method == 'DELETE': return self.delete(path) elif http_method == 'GET': return self.get(path) else: raise exc.InvalidAttribute( _('Unknown HTTP method: %s') % http_method) def vif_list(self, node_ident): """List VIFs attached to a given node. :param node_ident: The UUID or Name of the node. """ path = "%s/vifs" % node_ident return self._list(self._path(path), "vifs") def vif_attach(self, node_ident, vif_id, **kwargs): """Attach VIF to a given node. :param node_ident: The UUID or Name of the node. :param vif_id: The UUID or Name of the VIF to attach. :param kwargs: A dictionary containing the attributes of the resource that will be created. """ path = "%s/vifs" % node_ident data = {"id": vif_id} if 'id' in kwargs: raise exc.InvalidAttribute("The attribute 'id' can't be " "specified in vif-info") data.update(kwargs) # TODO(vdrok): cleanup places doing custom path and http_method self.update(path, data, http_method="POST") def vif_detach(self, node_ident, vif_id): """Detach VIF from a given node. :param node_ident: The UUID or Name of the node. :param vif_id: The UUID or Name of the VIF to detach. """ path = "%s/vifs/%s" % (node_ident, vif_id) self.delete(path) def set_maintenance(self, node_id, state, maint_reason=None): """Set the maintenance mode for the node. :param node_id: The UUID of the node. :param state: the maintenance mode; either a Boolean or a string representation of a Boolean (eg, 'true', 'on', 'false', 'off'). True to put the node in maintenance mode; False to take the node out of maintenance mode. :param maint_reason: Optional string. Reason for putting node into maintenance mode. :raises: InvalidAttribute if state is an invalid string (that doesn't represent a Boolean). """ if isinstance(state, bool): maintenance_mode = state else: try: maintenance_mode = strutils.bool_from_string(state, True) except ValueError as e: raise exc.InvalidAttribute(_("Argument 'state': %(err)s") % {'err': e}) path = "%s/maintenance" % node_id if maintenance_mode: reason = {'reason': maint_reason} return self.update(path, reason, http_method='PUT') else: return self.delete(path) def set_power_state(self, node_id, state, soft=False, timeout=None): """Sets power state for a node. :param node_id: Node identifier :param state: One of target power state, 'on', 'off', or 'reboot' :param soft: The flag for graceful power 'off' or 'reboot' :param timeout: The timeout (in seconds) positive integer value (> 0) :raises: ValueError if 'soft' or 'timeout' option is invalid :returns: The status of the request """ if state == 'on' and soft: raise ValueError( _("'soft' option is invalid for the power-state 'on'")) path = "%s/states/power" % node_id requested_state = 'soft ' + state if soft else state target = _power_states.get(requested_state, state) body = {'target': target} if timeout is not None: msg = _("'timeout' option for setting power state must have " "positive integer value (> 0)") try: timeout = int(timeout) except (ValueError, TypeError): raise ValueError(msg) if timeout <= 0: raise ValueError(msg) body = {'target': target, 'timeout': timeout} return self.update(path, body, http_method='PUT') def set_target_raid_config(self, node_ident, target_raid_config): """Sets target_raid_config for a node. :param node_ident: Node identifier :param target_raid_config: A dictionary with the target RAID configuration; may be empty. :returns: status of the request """ path = "%s/states/raid" % node_ident return self.update(path, target_raid_config, http_method='PUT') def validate(self, node_uuid): path = "%s/validate" % node_uuid return self.get(path) def set_provision_state(self, node_uuid, state, configdrive=None, cleansteps=None): """Set the provision state for the node. :param node_uuid: The UUID or name of the node. :param state: The desired provision state. One of 'active', 'deleted', 'rebuild', 'inspect', 'provide', 'manage', 'clean', 'abort'. :param configdrive: A gzipped, base64-encoded configuration drive string OR the path to the configuration drive file OR the path to a directory containing the config drive files. In case it's a directory, a config drive will be generated from it. This is only valid when setting state to 'active'. :param cleansteps: The clean steps as a list of clean-step dictionaries; each dictionary should have keys 'interface' and 'step', and optional key 'args'. This must be specified (and is only valid) when setting provision-state to 'clean'. :raises: InvalidAttribute if there was an error with the clean steps :returns: The status of the request """ path = "%s/states/provision" % node_uuid body = {'target': state} if configdrive: if os.path.isfile(configdrive): with open(configdrive, 'rb') as f: configdrive = f.read() if os.path.isdir(configdrive): configdrive = utils.make_configdrive(configdrive) body['configdrive'] = configdrive elif cleansteps: body['clean_steps'] = cleansteps return self.update(path, body, http_method='PUT') def states(self, node_uuid): path = "%s/states" % node_uuid return self.get(path) def get_console(self, node_uuid): path = "%s/states/console" % node_uuid return self._get_as_dict(path) def set_console_mode(self, node_uuid, enabled): """Set the console mode for the node. :param node_uuid: The UUID of the node. :param enabled: Either a Boolean or a string representation of a Boolean. True to enable the console; False to disable. """ path = "%s/states/console" % node_uuid target = {'enabled': enabled} return self.update(path, target, http_method='PUT') def set_boot_device(self, node_uuid, boot_device, persistent=False): path = "%s/management/boot_device" % node_uuid target = {'boot_device': boot_device, 'persistent': persistent} return self.update(path, target, http_method='PUT') def get_boot_device(self, node_uuid): path = "%s/management/boot_device" % node_uuid return self._get_as_dict(path) def inject_nmi(self, node_uuid): path = "%s/management/inject_nmi" % node_uuid return self.update(path, {}, http_method='PUT') def get_supported_boot_devices(self, node_uuid): path = "%s/management/boot_device/supported" % node_uuid return self._get_as_dict(path) def get_vendor_passthru_methods(self, node_ident): path = "%s/vendor_passthru/methods" % node_ident return self._get_as_dict(path) def get_traits(self, node_ident): """Get traits for a node. :param node_ident: node UUID or name. """ path = "%s/traits" % node_ident return self._list_primitives(self._path(path), 'traits') def add_trait(self, node_ident, trait): """Add a trait to a node. :param node_ident: node UUID or name. :param trait: trait to add to the node. """ path = "%s/traits/%s" % (node_ident, trait) return self.update(path, None, http_method='PUT') def set_traits(self, node_ident, traits): """Set traits for a node. Removes any existing traits and adds the traits passed in to this method. :param node_ident: node UUID or name. :param traits: list of traits to add to the node. """ path = "%s/traits" % node_ident body = {'traits': traits} return self.update(path, body, http_method='PUT') def remove_trait(self, node_ident, trait): """Remove a trait from a node. :param node_ident: node UUID or name. :param trait: trait to remove from the node. """ path = "%s/traits/%s" % (node_ident, trait) return self.delete(path) def remove_all_traits(self, node_ident): """Remove all traits from a node. :param node_ident: node UUID or name. """ path = "%s/traits" % node_ident return self.delete(path) def wait_for_provision_state(self, node_ident, expected_state, timeout=0, poll_interval=_DEFAULT_POLL_INTERVAL, poll_delay_function=None, fail_on_unexpected_state=True): """Helper function to wait for a node to reach a given state. Polls Ironic API in a loop until node gets to a requested state. Fails in the following cases: * Timeout (if provided) is reached * Node's last_error gets set to a non-empty value * Unexpected stable state is reached and fail_on_unexpected_state is on * Error state is reached (if it's not equal to expected_state) :param node_ident: node UUID or name :param expected_state: expected final provision state :param timeout: timeout in seconds, no timeout if 0 :param poll_interval: interval in seconds between 2 poll :param poll_delay_function: function to use to wait between polls (defaults to time.sleep). Should take one argument - delay time in seconds. Any exceptions raised inside it will abort the wait. :param fail_on_unexpected_state: whether to fail if the nodes reaches a different stable state. :raises: StateTransitionFailed if node reached an error state :raises: StateTransitionTimeout on timeout """ if not isinstance(timeout, (int, float)) or timeout < 0: raise ValueError(_('Timeout must be a non-negative number')) threshold = time.time() + timeout expected_state = expected_state.lower() poll_delay_function = (time.sleep if poll_delay_function is None else poll_delay_function) if not callable(poll_delay_function): raise TypeError(_('poll_delay_function must be callable')) # TODO(dtantsur): use version negotiation to request API 1.8 and use # the "fields" argument to reduce amount of data sent. while not timeout or time.time() < threshold: node = self.get(node_ident) if node.provision_state == expected_state: LOG.debug('Node %(node)s reached provision state %(state)s', {'node': node_ident, 'state': expected_state}) return # Note that if expected_state == 'error' we still succeed if (node.last_error or node.provision_state == 'error' or node.provision_state.endswith(' failed')): raise exc.StateTransitionFailed( _('Node %(node)s failed to reach state %(state)s. ' 'It\'s in state %(actual)s, and has error: %(error)s') % {'node': node_ident, 'state': expected_state, 'actual': node.provision_state, 'error': node.last_error}) if fail_on_unexpected_state and not node.target_provision_state: raise exc.StateTransitionFailed( _('Node %(node)s failed to reach state %(state)s. ' 'It\'s in unexpected stable state %(actual)s') % {'node': node_ident, 'state': expected_state, 'actual': node.provision_state}) LOG.debug('Still waiting for node %(node)s to reach state ' '%(state)s, the current state is %(actual)s', {'node': node_ident, 'state': expected_state, 'actual': node.provision_state}) poll_delay_function(poll_interval) raise exc.StateTransitionTimeout( _('Node %(node)s failed to reach state %(state)s in ' '%(timeout)s seconds') % {'node': node_ident, 'state': expected_state, 'timeout': timeout}) python-ironicclient-2.2.0/ironicclient/v1/volume_target.py0000666000175100017510000000731613232474343024015 0ustar zuulzuul00000000000000# Copyright 2016 Hitachi, Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ironicclient.common import base from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient import exc class VolumeTarget(base.Resource): def __repr__(self): return "" % self._info class VolumeTargetManager(base.CreateManager): resource_class = VolumeTarget _creation_attributes = ['extra', 'node_uuid', 'volume_type', 'properties', 'boot_index', 'volume_id', 'uuid'] _resource_name = 'volume/targets' def list(self, node=None, limit=None, marker=None, sort_key=None, sort_dir=None, detail=False, fields=None): """Retrieve a list of volume target. :param node: Optional, UUID or name of a node, to get volume targets for this node only. :param marker: Optional, the UUID of a volume target, eg the last volume target from a previous result set. Return the next result set. :param limit: The maximum number of results to return per request, if: 1) limit > 0, the maximum number of volume targets to return. 2) limit == 0, return the entire list of volume targets. 3) limit param is NOT specified (None), the number of items returned respect the maximum imposed by the Ironic API (see Ironic's api.max_limit option). :param sort_key: Optional, field used for sorting. :param sort_dir: Optional, direction of sorting, either 'asc' (the default) or 'desc'. :param detail: Optional, boolean whether to return detailed information about volume targets. :param fields: Optional, a list with a specified set of fields of the resource to be returned. Can not be used when 'detail' is set. :returns: A list of volume targets. """ if limit is not None: limit = int(limit) if detail and fields: raise exc.InvalidAttribute(_("Can't fetch a subset of fields " "with 'detail' set")) filters = utils.common_filters(marker=marker, limit=limit, sort_key=sort_key, sort_dir=sort_dir, fields=fields, detail=detail) if node is not None: filters.append('node=%s' % node) path = '' if filters: path += '?' + '&'.join(filters) if limit is None: return self._list(self._path(path), "targets") else: return self._list_pagination(self._path(path), "targets", limit=limit) def get(self, volume_target_id, fields=None): return self._get(resource_id=volume_target_id, fields=fields) def delete(self, volume_target_id): return self._delete(resource_id=volume_target_id) def update(self, volume_target_id, patch): return self._update(resource_id=volume_target_id, patch=patch) python-ironicclient-2.2.0/ironicclient/v1/__init__.py0000666000175100017510000000000013232474343022656 0ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/v1/chassis_shell.py0000666000175100017510000002141313232474343023756 0ustar zuulzuul00000000000000# Copyright 2013 Red Hat, Inc. # 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. from ironicclient.common import cliutils from ironicclient.common import utils from ironicclient.v1 import resource_fields as res_fields def _print_chassis_show(chassis, fields=None, json=False): if fields is None: fields = res_fields.CHASSIS_DETAILED_RESOURCE.fields data = dict([(f, getattr(chassis, f, '')) for f in fields]) cliutils.print_dict(data, wrap=72, json_flag=json) @cliutils.arg('chassis', metavar='', help="UUID of the chassis.") @cliutils.arg( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], help="One or more chassis fields. Only these fields will be fetched from " "the server.") def do_chassis_show(cc, args): """Show detailed information about a chassis.""" utils.check_empty_arg(args.chassis, '') fields = args.fields[0] if args.fields else None utils.check_for_invalid_fields( fields, res_fields.CHASSIS_DETAILED_RESOURCE.fields) chassis = cc.chassis.get(args.chassis, fields=fields) _print_chassis_show(chassis, fields=fields, json=args.json) @cliutils.arg( '--detail', dest='detail', action='store_true', default=False, help="Show detailed information about the chassis.") @cliutils.arg( '--limit', metavar='', type=int, help='Maximum number of chassis to return per request, ' '0 for no limit. Default is the maximum number used ' 'by the Ironic API Service.') @cliutils.arg( '--marker', metavar='', help='Chassis UUID (for example, of the last chassis in the list ' 'from a previous request). Returns the list of chassis ' 'after this UUID.') @cliutils.arg( '--sort-key', metavar='', help='Chassis field that will be used for sorting.') @cliutils.arg( '--sort-dir', metavar='', choices=['asc', 'desc'], help='Sort direction: "asc" (the default) or "desc".') @cliutils.arg( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], help="One or more chassis fields. Only these fields will be fetched from " "the server. Can not be used when '--detail' is specified.") def do_chassis_list(cc, args): """List the chassis.""" if args.detail: fields = res_fields.CHASSIS_DETAILED_RESOURCE.fields field_labels = res_fields.CHASSIS_DETAILED_RESOURCE.labels elif args.fields: utils.check_for_invalid_fields( args.fields[0], res_fields.CHASSIS_DETAILED_RESOURCE.fields) resource = res_fields.Resource(args.fields[0]) fields = resource.fields field_labels = resource.labels else: fields = res_fields.CHASSIS_RESOURCE.fields field_labels = res_fields.CHASSIS_RESOURCE.labels sort_fields = res_fields.CHASSIS_DETAILED_RESOURCE.sort_fields sort_field_labels = res_fields.CHASSIS_DETAILED_RESOURCE.sort_labels params = utils.common_params_for_list(args, sort_fields, sort_field_labels) chassis = cc.chassis.list(**params) cliutils.print_list(chassis, fields, field_labels=field_labels, sortby_index=None, json_flag=args.json) @cliutils.arg( '-d', '--description', metavar='', help='Description of the chassis.') @cliutils.arg( '-e', '--extra', metavar="", action='append', help="Record arbitrary key/value metadata. " "Can be specified multiple times.") @cliutils.arg( '-u', '--uuid', metavar='', help="UUID of the chassis.") def do_chassis_create(cc, args): """Create a new chassis.""" field_list = ['description', 'extra', 'uuid'] fields = dict((k, v) for (k, v) in vars(args).items() if k in field_list and not (v is None)) fields = utils.args_array_to_dict(fields, 'extra') chassis = cc.chassis.create(**fields) data = dict([(f, getattr(chassis, f, '')) for f in field_list]) cliutils.print_dict(data, wrap=72, json_flag=args.json) @cliutils.arg( 'chassis', metavar='', nargs='+', help="UUID of the chassis.") def do_chassis_delete(cc, args): """Delete a chassis.""" for c in args.chassis: cc.chassis.delete(c) print('Deleted chassis %s' % c) @cliutils.arg('chassis', metavar='', help="UUID of the chassis.") @cliutils.arg( 'op', metavar='', choices=['add', 'replace', 'remove'], help="Operation: 'add', 'replace', or 'remove'.") @cliutils.arg( 'attributes', metavar='', nargs='+', action='append', default=[], help="Attribute to add, replace, or remove. Can be specified " "multiple times. For 'remove', only is necessary.") def do_chassis_update(cc, args): """Update information about a chassis.""" patch = utils.args_array_to_patch(args.op, args.attributes[0]) chassis = cc.chassis.update(args.chassis, patch) _print_chassis_show(chassis, json=args.json) @cliutils.arg( '--detail', dest='detail', action='store_true', default=False, help="Show detailed information about the nodes.") @cliutils.arg( '--limit', metavar='', type=int, help='Maximum number of nodes to return per request, ' '0 for no limit. Default is the maximum number used ' 'by the Ironic API Service.') @cliutils.arg( '--marker', metavar='', help='Node UUID (for example, of the last node in the list from ' 'a previous request). Returns the list of nodes after this UUID.') @cliutils.arg( '--sort-key', metavar='', help='Node field that will be used for sorting.') @cliutils.arg( '--sort-dir', metavar='', choices=['asc', 'desc'], help='Sort direction: "asc" (the default) or "desc".') @cliutils.arg('chassis', metavar='', help="UUID of the chassis.") @cliutils.arg( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], help="One or more node fields. Only these fields will be fetched from " "the server. Can not be used when '--detail' is specified.") @cliutils.arg( '--maintenance', metavar='', help="List nodes in maintenance mode: 'true' or 'false'.") @cliutils.arg( '--associated', metavar='', help="List nodes by instance association: 'true' or 'false'.") @cliutils.arg( '--provision-state', metavar='', help="List nodes in specified provision state.") def do_chassis_node_list(cc, args): """List the nodes contained in a chassis.""" params = {} if args.associated is not None: params['associated'] = utils.bool_argument_value("--associated", args.associated) if args.maintenance is not None: params['maintenance'] = utils.bool_argument_value("--maintenance", args.maintenance) if args.provision_state is not None: params['provision_state'] = args.provision_state if args.detail: fields = res_fields.NODE_DETAILED_RESOURCE.fields field_labels = res_fields.NODE_DETAILED_RESOURCE.labels elif args.fields: utils.check_for_invalid_fields( args.fields[0], res_fields.NODE_DETAILED_RESOURCE.fields) resource = res_fields.Resource(args.fields[0]) fields = resource.fields field_labels = resource.labels else: fields = res_fields.NODE_RESOURCE.fields field_labels = res_fields.NODE_RESOURCE.labels sort_fields = res_fields.NODE_DETAILED_RESOURCE.sort_fields sort_field_labels = res_fields.NODE_DETAILED_RESOURCE.sort_labels params.update(utils.common_params_for_list(args, sort_fields, sort_field_labels)) nodes = cc.chassis.list_nodes(args.chassis, **params) cliutils.print_list(nodes, fields, field_labels=field_labels, sortby_index=None, json_flag=args.json) python-ironicclient-2.2.0/ironicclient/osc/0000775000175100017510000000000013232474761021017 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/osc/v1/0000775000175100017510000000000013232474761021345 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/osc/v1/baremetal_portgroup.py0000666000175100017510000004200513232474373025776 0ustar zuulzuul00000000000000# # Copyright 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import itertools import logging from osc_lib.command import command from osc_lib import utils as oscutils from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient import exc from ironicclient.v1 import resource_fields as res_fields class CreateBaremetalPortGroup(command.ShowOne): """Create a new baremetal port group.""" log = logging.getLogger(__name__ + ".CreateBaremetalPortGroup") def get_parser(self, prog_name): parser = super(CreateBaremetalPortGroup, self).get_parser(prog_name) parser.add_argument( '--node', dest='node_uuid', metavar='', required=True, help=_('UUID of the node that this port group belongs to.')) parser.add_argument( '--address', metavar='', help=_('MAC address for this port group.')) parser.add_argument( '--name', dest='name', help=_('Name of the port group.')) parser.add_argument( '--uuid', dest='uuid', help=_('UUID of the port group.')) parser.add_argument( '--extra', metavar="", action='append', help=_("Record arbitrary key/value metadata. " "Can be specified multiple times.")) parser.add_argument( '--mode', help=_('Mode of the port group. For possible values, refer to ' 'https://www.kernel.org/doc/Documentation/networking' '/bonding.txt.')) parser.add_argument( '--property', dest='properties', metavar="", action='append', help=_("Key/value property related to this port group's " "configuration. Can be specified multiple times.")) standalone_ports_group = parser.add_mutually_exclusive_group() standalone_ports_group.add_argument( '--support-standalone-ports', action='store_true', help=_("Ports that are members of this port group " "can be used as stand-alone ports. (default)")) standalone_ports_group.add_argument( '--unsupport-standalone-ports', action='store_true', help=_("Ports that are members of this port group " "cannot be used as stand-alone ports.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal field_list = ['node_uuid', 'address', 'name', 'uuid', 'extra', 'mode', 'properties'] fields = dict((k, v) for (k, v) in vars(parsed_args).items() if k in field_list and v is not None) if parsed_args.support_standalone_ports: fields['standalone_ports_supported'] = True if parsed_args.unsupport_standalone_ports: fields['standalone_ports_supported'] = False fields = utils.args_array_to_dict(fields, 'extra') fields = utils.args_array_to_dict(fields, 'properties') portgroup = baremetal_client.portgroup.create(**fields) data = dict([(f, getattr(portgroup, f, '')) for f in res_fields.PORTGROUP_DETAILED_RESOURCE.fields]) return self.dict2columns(data) class ShowBaremetalPortGroup(command.ShowOne): """Show baremetal port group details.""" log = logging.getLogger(__name__ + ".ShowBaremetalPortGroup") def get_parser(self, prog_name): parser = super(ShowBaremetalPortGroup, self).get_parser(prog_name) parser.add_argument( "portgroup", metavar="", help=_("UUID or name of the port group " "(or MAC address if --address is specified).")) parser.add_argument( '--address', dest='address', action='store_true', default=False, help=_(' is the MAC address (instead of UUID or name) ' 'of the port group.')) parser.add_argument( '--fields', nargs='+', dest='fields', metavar='', action='append', choices=res_fields.PORTGROUP_DETAILED_RESOURCE.fields, default=[], help=_("One or more port group fields. Only these fields will be " "fetched from the server.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal fields = list(itertools.chain.from_iterable(parsed_args.fields)) fields = fields if fields else None if parsed_args.address: portgroup = baremetal_client.portgroup.get_by_address( parsed_args.portgroup, fields=fields)._info else: portgroup = baremetal_client.portgroup.get( parsed_args.portgroup, fields=fields)._info portgroup.pop("links", None) portgroup.pop("ports", None) return zip(*sorted(portgroup.items())) class ListBaremetalPortGroup(command.Lister): """List baremetal port groups.""" log = logging.getLogger(__name__ + ".ListBaremetalPortGroup") def get_parser(self, prog_name): parser = super(ListBaremetalPortGroup, self).get_parser(prog_name) parser.add_argument( '--limit', metavar='', type=int, help=_('Maximum number of port groups to return per request, ' '0 for no limit. Default is the maximum number used ' 'by the Baremetal API Service.')) parser.add_argument( '--marker', metavar='', help=_('Port group UUID (for example, of the last port group in ' 'the list from a previous request). Returns the list of ' 'port groups after this UUID.')) parser.add_argument( '--sort', metavar="[:]", help=_('Sort output by specified port group fields and directions ' '(asc or desc) (default: asc). Multiple fields and ' 'directions can be specified, separated by comma.')) parser.add_argument( '--address', metavar='', help=_("Only show information for the port group with this MAC " "address.")) parser.add_argument( '--node', dest='node', metavar='', help=_("Only list port groups of this node (name or UUID).")) display_group = parser.add_mutually_exclusive_group(required=False) display_group.add_argument( '--long', default=False, dest='detail', help=_("Show detailed information about the port groups."), action='store_true') display_group.add_argument( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], choices=res_fields.PORTGROUP_DETAILED_RESOURCE.fields, help=_("One or more port group fields. Only these fields will be " "fetched from the server. Can not be used when '--long' is " "specified.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) client = self.app.client_manager.baremetal columns = res_fields.PORTGROUP_RESOURCE.fields labels = res_fields.PORTGROUP_RESOURCE.labels params = {} if parsed_args.limit is not None and parsed_args.limit < 0: raise exc.CommandError( _('Expected non-negative --limit, got %s') % parsed_args.limit) params['limit'] = parsed_args.limit params['marker'] = parsed_args.marker if parsed_args.address is not None: params['address'] = parsed_args.address if parsed_args.node is not None: params['node'] = parsed_args.node if parsed_args.detail: params['detail'] = parsed_args.detail columns = res_fields.PORTGROUP_DETAILED_RESOURCE.fields labels = res_fields.PORTGROUP_DETAILED_RESOURCE.labels elif parsed_args.fields: params['detail'] = False fields = itertools.chain.from_iterable(parsed_args.fields) resource = res_fields.Resource(list(fields)) columns = resource.fields labels = resource.labels params['fields'] = columns self.log.debug("params(%s)", params) data = client.portgroup.list(**params) data = oscutils.sort_items(data, parsed_args.sort) return (labels, (oscutils.get_item_properties(s, columns, formatters={ 'Properties': oscutils.format_dict},) for s in data)) class DeleteBaremetalPortGroup(command.Command): """Unregister baremetal port group(s).""" log = logging.getLogger(__name__ + ".DeleteBaremetalPortGroup") def get_parser(self, prog_name): parser = super(DeleteBaremetalPortGroup, self).get_parser(prog_name) parser.add_argument( "portgroups", metavar="", nargs="+", help=_("Port group(s) to delete (name or UUID).")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal failures = [] for portgroup in parsed_args.portgroups: try: baremetal_client.portgroup.delete(portgroup) print(_('Deleted port group %s') % portgroup) except exc.ClientException as e: failures.append(_("Failed to delete port group %(portgroup)s: " " %(error)s") % {'portgroup': portgroup, 'error': e}) if failures: raise exc.ClientException("\n".join(failures)) class SetBaremetalPortGroup(command.Command): """Set baremetal port group properties.""" log = logging.getLogger(__name__ + ".SetBaremetalPortGroup") def get_parser(self, prog_name): parser = super(SetBaremetalPortGroup, self).get_parser(prog_name) parser.add_argument( 'portgroup', metavar='', help=_("Name or UUID of the port group."), ) parser.add_argument( '--node', dest='node_uuid', metavar='', help=_('Update UUID of the node that this port group belongs to.') ) parser.add_argument( "--address", metavar="", help=_("MAC address for this port group."), ) parser.add_argument( "--name", metavar="", help=_("Name of the port group."), ) parser.add_argument( "--extra", metavar="", action='append', help=_('Extra to set on this baremetal port group ' '(repeat option to set multiple extras).'), ) parser.add_argument( '--mode', help=_('Mode of the port group. For possible values, refer to ' 'https://www.kernel.org/doc/Documentation/networking' '/bonding.txt.')) parser.add_argument( '--property', dest='properties', metavar="", action='append', help=_("Key/value property related to this port group's " "configuration (repeat option to set multiple " "properties).")) standalone_ports_group = parser.add_mutually_exclusive_group() standalone_ports_group.add_argument( '--support-standalone-ports', action='store_true', default=None, help=_("Ports that are members of this port group " "can be used as stand-alone ports.") ) standalone_ports_group.add_argument( '--unsupport-standalone-ports', action='store_true', help=_("Ports that are members of this port group " "cannot be used as stand-alone ports.") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal properties = [] if parsed_args.node_uuid: properties.extend(utils.args_array_to_patch( 'add', ["node_uuid=%s" % parsed_args.node_uuid])) if parsed_args.address: properties.extend(utils.args_array_to_patch( 'add', ["address=%s" % parsed_args.address])) if parsed_args.name: name = ["name=%s" % parsed_args.name] properties.extend(utils.args_array_to_patch( 'add', name)) if parsed_args.support_standalone_ports: properties.extend(utils.args_array_to_patch( 'add', ["standalone_ports_supported=True"])) if parsed_args.unsupport_standalone_ports: properties.extend(utils.args_array_to_patch( 'add', ["standalone_ports_supported=False"])) if parsed_args.mode: properties.extend(utils.args_array_to_patch( 'add', ["mode=\"%s\"" % parsed_args.mode])) if parsed_args.extra: properties.extend(utils.args_array_to_patch( 'add', ['extra/' + x for x in parsed_args.extra])) if parsed_args.properties: properties.extend(utils.args_array_to_patch( 'add', ['properties/' + x for x in parsed_args.properties])) if properties: baremetal_client.portgroup.update(parsed_args.portgroup, properties) else: self.log.warning("Please specify what to set.") class UnsetBaremetalPortGroup(command.Command): """Unset baremetal port group properties.""" log = logging.getLogger(__name__ + ".UnsetBaremetalPortGroup") def get_parser(self, prog_name): parser = super(UnsetBaremetalPortGroup, self).get_parser(prog_name) parser.add_argument( 'portgroup', metavar='', help=_("Name or UUID of the port group.") ) parser.add_argument( "--name", action='store_true', help=_("Unset the name of the port group."), ) parser.add_argument( "--address", action='store_true', help=_("Unset the address of the port group."), ) parser.add_argument( "--extra", metavar="", action='append', help=_('Extra to unset on this baremetal port group ' '(repeat option to unset multiple extras).'), ) parser.add_argument( "--property", dest='properties', metavar="", action='append', help=_('Property to unset on this baremetal port group ' '(repeat option to unset multiple properties).'), ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal properties = [] if parsed_args.name: properties.extend(utils.args_array_to_patch('remove', ['name'])) if parsed_args.address: properties.extend(utils.args_array_to_patch('remove', ['address'])) if parsed_args.extra: properties.extend(utils.args_array_to_patch('remove', ['extra/' + x for x in parsed_args.extra])) if parsed_args.properties: properties.extend(utils.args_array_to_patch( 'remove', ['properties/' + x for x in parsed_args.properties])) if properties: baremetal_client.portgroup.update(parsed_args.portgroup, properties) else: self.log.warning("Please specify what to unset.") python-ironicclient-2.2.0/ironicclient/osc/v1/baremetal_node.py0000777000175100017510000016604213232474373024675 0ustar zuulzuul00000000000000# # Copyright 2015 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import argparse import itertools import logging from osc_lib.command import command from osc_lib import utils as oscutils from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient import exc from ironicclient.v1 import resource_fields as res_fields from ironicclient.v1 import utils as v1_utils CONFIG_DRIVE_ARG_HELP = _( "A gzipped, base64-encoded configuration drive string OR " "the path to the configuration drive file OR the path to a " "directory containing the config drive files. In case it's " "a directory, a config drive will be generated from it.") class ProvisionStateBaremetalNode(command.Command): """Base provision state class""" log = logging.getLogger(__name__ + ".ProvisionStateBaremetalNode") def get_parser(self, prog_name): parser = super(ProvisionStateBaremetalNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node.") ) parser.add_argument( '--provision-state', default=self.PROVISION_STATE, required=False, choices=[self.PROVISION_STATE], help=argparse.SUPPRESS) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal clean_steps = getattr(parsed_args, 'clean_steps', None) if clean_steps == '-': clean_steps = utils.get_from_stdin('clean steps') if clean_steps: clean_steps = utils.handle_json_or_file_arg(clean_steps) config_drive = getattr(parsed_args, 'config_drive', None) baremetal_client.node.set_provision_state( parsed_args.node, parsed_args.provision_state, configdrive=config_drive, cleansteps=clean_steps) class ProvisionStateWithWait(ProvisionStateBaremetalNode): """Provision state class adding --wait flag.""" log = logging.getLogger(__name__ + ".ProvisionStateWithWait") def get_parser(self, prog_name): parser = super(ProvisionStateWithWait, self).get_parser(prog_name) desired_state = v1_utils.PROVISION_ACTIONS.get( self.PROVISION_STATE)['expected_state'] parser.add_argument( '--wait', type=int, dest='wait_timeout', default=None, metavar='', const=0, nargs='?', help=_("Wait for a node to reach the desired state, %(state)s. " "Optionally takes a timeout value (in seconds). The " "default value is 0, meaning it will wait indefinitely.") % {'state': desired_state}) return parser def take_action(self, parsed_args): super(ProvisionStateWithWait, self).take_action(parsed_args) self.log.debug("take_action(%s)", parsed_args) if (parsed_args.wait_timeout is None): return baremetal_client = self.app.client_manager.baremetal wait_args = v1_utils.PROVISION_ACTIONS.get( parsed_args.provision_state) if wait_args is None: # This should never happen in reality, but checking just in case raise exc.CommandError( _("'--wait is not supported for provision state '%s'") % parsed_args.provision_state) print(_('Waiting for provision state %(state)s on node %(node)s') % {'state': wait_args['expected_state'], 'node': parsed_args.node}) baremetal_client.node.wait_for_provision_state( parsed_args.node, timeout=parsed_args.wait_timeout, **wait_args) class AbortBaremetalNode(ProvisionStateBaremetalNode): """Set provision state of baremetal node to 'abort'""" log = logging.getLogger(__name__ + ".AbortBaremetalNode") PROVISION_STATE = 'abort' class AdoptBaremetalNode(ProvisionStateWithWait): """Set provision state of baremetal node to 'adopt'""" log = logging.getLogger(__name__ + ".AdoptBaremetalNode") PROVISION_STATE = 'adopt' class BootdeviceSetBaremetalNode(command.Command): """Set the boot device for a node""" log = logging.getLogger(__name__ + ".BootdeviceSetBaremetalNode") def get_parser(self, prog_name): parser = super(BootdeviceSetBaremetalNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node") ) parser.add_argument( 'device', metavar='', choices=v1_utils.BOOT_DEVICES, help=_("One of %s") % (oscutils.format_list(v1_utils.BOOT_DEVICES)) ) parser.add_argument( '--persistent', dest='persistent', action='store_true', default=False, help=_("Make changes persistent for all future boots") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal baremetal_client.node.set_boot_device( parsed_args.node, parsed_args.device, parsed_args.persistent) class BootdeviceShowBaremetalNode(command.ShowOne): """Show the boot device information for a node""" log = logging.getLogger(__name__ + ".BootdeviceShowBaremetalNode") def get_parser(self, prog_name): parser = super(BootdeviceShowBaremetalNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node") ) parser.add_argument( '--supported', dest='supported', action='store_true', default=False, help=_("Show the supported boot devices") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal if parsed_args.supported: info = baremetal_client.node.get_supported_boot_devices( parsed_args.node) boot_device_list = info.get('supported_boot_devices', []) info['supported_boot_devices'] = ', '.join(boot_device_list) else: info = baremetal_client.node.get_boot_device(parsed_args.node) return zip(*sorted(info.items())) class CleanBaremetalNode(ProvisionStateWithWait): """Set provision state of baremetal node to 'clean'""" log = logging.getLogger(__name__ + ".CleanBaremetalNode") PROVISION_STATE = 'clean' def get_parser(self, prog_name): parser = super(CleanBaremetalNode, self).get_parser(prog_name) parser.add_argument( '--clean-steps', metavar='', required=True, default=None, help=_("The clean steps in JSON format. May be the path to a file " "containing the clean steps; OR '-', with the clean steps " "being read from standard input; OR a string. The value " "should be a list of clean-step dictionaries; each " "dictionary should have keys 'interface' and 'step', and " "optional key 'args'.")) return parser class ConsoleDisableBaremetalNode(command.Command): """Disable console access for a node""" log = logging.getLogger(__name__ + ".ConsoleDisableBaremetalNode") def get_parser(self, prog_name): parser = super(ConsoleDisableBaremetalNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal baremetal_client.node.set_console_mode(parsed_args.node, False) class ConsoleEnableBaremetalNode(command.Command): """Enable console access for a node""" log = logging.getLogger(__name__ + ".ConsoleEnableBaremetalNode") def get_parser(self, prog_name): parser = super(ConsoleEnableBaremetalNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal baremetal_client.node.set_console_mode(parsed_args.node, True) class ConsoleShowBaremetalNode(command.ShowOne): """Show console information for a node""" log = logging.getLogger(__name__ + ".ConsoleShowBaremetalNode") def get_parser(self, prog_name): parser = super(ConsoleShowBaremetalNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal info = baremetal_client.node.get_console(parsed_args.node) return zip(*sorted(info.items())) class CreateBaremetalNode(command.ShowOne): """Register a new node with the baremetal service""" log = logging.getLogger(__name__ + ".CreateBaremetalNode") def get_parser(self, prog_name): parser = super(CreateBaremetalNode, self).get_parser(prog_name) parser.add_argument( '--chassis-uuid', dest='chassis_uuid', metavar='', help=_('UUID of the chassis that this node belongs to.')) parser.add_argument( '--driver', metavar='', required=True, help=_('Driver used to control the node [REQUIRED].')) parser.add_argument( '--driver-info', metavar='', action='append', help=_('Key/value pair used by the driver, such as out-of-band ' 'management credentials. Can be specified multiple times.')) parser.add_argument( '--property', dest='properties', metavar='', action='append', help=_('Key/value pair describing the physical characteristics of ' 'the node. This is exported to Nova and used by the ' 'scheduler. Can be specified multiple times.')) parser.add_argument( '--extra', metavar='', action='append', help=_("Record arbitrary key/value metadata. " "Can be specified multiple times.")) parser.add_argument( '--uuid', metavar='', help=_("Unique UUID for the node.")) parser.add_argument( '--name', metavar='', help=_("Unique name for the node.")) parser.add_argument( '--boot-interface', metavar='', help=_('Boot interface used by the node\'s driver. This is ' 'only applicable when the specified --driver is a ' 'hardware type.')) parser.add_argument( '--console-interface', metavar='', help=_('Console interface used by the node\'s driver. This is ' 'only applicable when the specified --driver is a ' 'hardware type.')) parser.add_argument( '--deploy-interface', metavar='', help=_('Deploy interface used by the node\'s driver. This is ' 'only applicable when the specified --driver is a ' 'hardware type.')) parser.add_argument( '--inspect-interface', metavar='', help=_('Inspect interface used by the node\'s driver. This is ' 'only applicable when the specified --driver is a ' 'hardware type.')) parser.add_argument( '--management-interface', metavar='', help=_('Management interface used by the node\'s driver. This is ' 'only applicable when the specified --driver is a ' 'hardware type.')) parser.add_argument( '--network-interface', metavar='', help=_('Network interface used for switching node to ' 'cleaning/provisioning networks.')) parser.add_argument( '--power-interface', metavar='', help=_('Power interface used by the node\'s driver. This is ' 'only applicable when the specified --driver is a ' 'hardware type.')) parser.add_argument( '--raid-interface', metavar='', help=_('RAID interface used by the node\'s driver. This is ' 'only applicable when the specified --driver is a ' 'hardware type.')) parser.add_argument( '--storage-interface', metavar='', help=_('Storage interface used by the node\'s driver.')) parser.add_argument( '--vendor-interface', metavar='', help=_('Vendor interface used by the node\'s driver. This is ' 'only applicable when the specified --driver is a ' 'hardware type.')) parser.add_argument( '--resource-class', metavar='', help=_('Resource class for mapping nodes to Nova flavors')) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal field_list = ['chassis_uuid', 'driver', 'driver_info', 'properties', 'extra', 'uuid', 'name', 'boot_interface', 'console_interface', 'deploy_interface', 'inspect_interface', 'management_interface', 'network_interface', 'power_interface', 'raid_interface', 'storage_interface', 'vendor_interface', 'resource_class'] fields = dict((k, v) for (k, v) in vars(parsed_args).items() if k in field_list and not (v is None)) fields = utils.args_array_to_dict(fields, 'driver_info') fields = utils.args_array_to_dict(fields, 'extra') fields = utils.args_array_to_dict(fields, 'properties') node = baremetal_client.node.create(**fields)._info node.pop('links', None) node.pop('ports', None) node.pop('portgroups', None) node.pop('states', None) node.pop('volume', None) node.setdefault('chassis_uuid', '') return self.dict2columns(node) class DeleteBaremetalNode(command.Command): """Unregister baremetal node(s)""" log = logging.getLogger(__name__ + ".DeleteBaremetalNode") def get_parser(self, prog_name): parser = super(DeleteBaremetalNode, self).get_parser(prog_name) parser.add_argument( "nodes", metavar="", nargs="+", help=_("Node(s) to delete (name or UUID)")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal failures = [] for node in parsed_args.nodes: try: baremetal_client.node.delete(node) print(_('Deleted node %s') % node) except exc.ClientException as e: failures.append(_("Failed to delete node %(node)s: %(error)s") % {'node': node, 'error': e}) if failures: raise exc.ClientException("\n".join(failures)) class DeployBaremetalNode(ProvisionStateWithWait): """Set provision state of baremetal node to 'deploy'""" log = logging.getLogger(__name__ + ".DeployBaremetalNode") PROVISION_STATE = 'active' def get_parser(self, prog_name): parser = super(DeployBaremetalNode, self).get_parser(prog_name) parser.add_argument( '--config-drive', metavar='', default=None, help=CONFIG_DRIVE_ARG_HELP) return parser class InspectBaremetalNode(ProvisionStateWithWait): """Set provision state of baremetal node to 'inspect'""" log = logging.getLogger(__name__ + ".InspectBaremetalNode") PROVISION_STATE = 'inspect' class ListBaremetalNode(command.Lister): """List baremetal nodes""" log = logging.getLogger(__name__ + ".ListBaremetalNode") PROVISION_STATES = ['active', 'deleted', 'rebuild', 'inspect', 'provide', 'manage', 'clean', 'adopt', 'abort'] def get_parser(self, prog_name): parser = super(ListBaremetalNode, self).get_parser(prog_name) parser.add_argument( '--limit', metavar='', type=int, help=_('Maximum number of nodes to return per request, ' '0 for no limit. Default is the maximum number used ' 'by the Baremetal API Service.') ) parser.add_argument( '--marker', metavar='', help=_('Node UUID (for example, of the last node in the list from ' 'a previous request). Returns the list of nodes after this ' 'UUID.') ) parser.add_argument( '--sort', metavar="[:]", help=_('Sort output by specified node fields and directions ' '(asc or desc) (default: asc). Multiple fields and ' 'directions can be specified, separated by comma.'), ) maint_group = parser.add_mutually_exclusive_group(required=False) maint_group.add_argument( '--maintenance', dest='maintenance', action='store_true', default=None, help=_("Limit list to nodes in maintenance mode"), ) maint_group.add_argument( '--no-maintenance', dest='maintenance', action='store_false', default=None, help=_("Limit list to nodes not in maintenance mode"), ) associated_group = parser.add_mutually_exclusive_group() associated_group.add_argument( '--associated', action='store_true', help=_("List only nodes associated with an instance."), ) associated_group.add_argument( '--unassociated', action='store_true', help=_('List only nodes not associated with an instance.'), ) parser.add_argument( '--provision-state', dest='provision_state', metavar='', help=_("List nodes in specified provision state.")) parser.add_argument( '--driver', dest='driver', metavar='', help=_("Limit list to nodes with driver ")) parser.add_argument( '--resource-class', dest='resource_class', metavar='', help=_("Limit list to nodes with resource class ")) parser.add_argument( '--chassis', dest='chassis', metavar='', help=_("Limit list to nodes of this chassis")) display_group = parser.add_mutually_exclusive_group(required=False) display_group.add_argument( '--long', default=False, help=_("Show detailed information about the nodes."), action='store_true') display_group.add_argument( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], choices=res_fields.NODE_DETAILED_RESOURCE.fields, help=_("One or more node fields. Only these fields will be " "fetched from the server. Can not be used when '--long' " "is specified.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) client = self.app.client_manager.baremetal columns = res_fields.NODE_RESOURCE.fields labels = res_fields.NODE_RESOURCE.labels params = {} if parsed_args.limit is not None and parsed_args.limit < 0: raise exc.CommandError( _('Expected non-negative --limit, got %s') % parsed_args.limit) params['limit'] = parsed_args.limit params['marker'] = parsed_args.marker if parsed_args.associated: params['associated'] = True if parsed_args.unassociated: params['associated'] = False if parsed_args.maintenance is not None: params['maintenance'] = parsed_args.maintenance if parsed_args.provision_state: params['provision_state'] = parsed_args.provision_state if parsed_args.driver: params['driver'] = parsed_args.driver if parsed_args.resource_class: params['resource_class'] = parsed_args.resource_class if parsed_args.chassis: params['chassis'] = parsed_args.chassis if parsed_args.long: params['detail'] = parsed_args.long columns = res_fields.NODE_DETAILED_RESOURCE.fields labels = res_fields.NODE_DETAILED_RESOURCE.labels elif parsed_args.fields: params['detail'] = False fields = itertools.chain.from_iterable(parsed_args.fields) resource = res_fields.Resource(list(fields)) columns = resource.fields labels = resource.labels params['fields'] = columns self.log.debug("params(%s)", params) data = client.node.list(**params) data = oscutils.sort_items(data, parsed_args.sort) return (labels, (oscutils.get_item_properties(s, columns, formatters={ 'Properties': oscutils.format_dict},) for s in data)) class MaintenanceSetBaremetalNode(command.Command): """Set baremetal node to maintenance mode""" log = logging.getLogger(__name__ + ".MaintenanceSetBaremetalNode") def get_parser(self, prog_name): parser = super(MaintenanceSetBaremetalNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node.") ) parser.add_argument( '--reason', metavar='', default=None, help=_("Reason for setting maintenance mode.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal baremetal_client.node.set_maintenance( parsed_args.node, True, maint_reason=parsed_args.reason) class MaintenanceUnsetBaremetalNode(command.Command): """Unset baremetal node from maintenance mode""" log = logging.getLogger(__name__ + ".MaintenanceUnsetBaremetalNode") def get_parser(self, prog_name): parser = super(MaintenanceUnsetBaremetalNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node.") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal baremetal_client.node.set_maintenance( parsed_args.node, False) class ManageBaremetalNode(ProvisionStateWithWait): """Set provision state of baremetal node to 'manage'""" log = logging.getLogger(__name__ + ".ManageBaremetalNode") PROVISION_STATE = 'manage' class PassthruCallBaremetalNode(command.Command): """Call a vendor passthu method for a node""" log = logging.getLogger(__name__ + ".PassthuCallBaremetalNode") def get_parser(self, prog_name): parser = super(PassthruCallBaremetalNode, self).get_parser( prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node") ) parser.add_argument( 'method', metavar='', help=_("Vendor passthru method to be executed") ) parser.add_argument( '--arg', metavar='', action='append', help=_("Argument to pass to the passthru method (repeat option " "to specify multiple arguments)") ) parser.add_argument( '--http-method', metavar='', choices=v1_utils.HTTP_METHODS, default='POST', help=(_("The HTTP method to use in the passthru request. One of " "%s. Defaults to POST.") % oscutils.format_list(v1_utils.HTTP_METHODS)) ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal arguments = utils.key_value_pairs_to_dict(parsed_args.arg) resp = baremetal_client.node.vendor_passthru( parsed_args.node, parsed_args.method, http_method=parsed_args.http_method, args=arguments) if resp: # Print the raw response; we don't know how it should be formatted print(str(resp.to_dict())) class PassthruListBaremetalNode(command.Lister): """List vendor passthru methods for a node""" log = logging.getLogger(__name__ + ".PassthruListBaremetalNode") def get_parser(self, prog_name): parser = super(PassthruListBaremetalNode, self).get_parser( prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal methods = baremetal_client.node.get_vendor_passthru_methods( parsed_args.node) data = [] for method, response in methods.items(): response['name'] = method response['http_methods'] = oscutils.format_list( response['http_methods']) data.append(response) return ( res_fields.VENDOR_PASSTHRU_METHOD_RESOURCE.labels, (oscutils.get_dict_properties( s, res_fields.VENDOR_PASSTHRU_METHOD_RESOURCE.fields) for s in data)) class PowerBaremetalNode(command.Command): """Base power state class, for setting the power of a node""" log = logging.getLogger(__name__ + ".PowerBaremetalNode") def get_parser(self, prog_name): parser = super(PowerBaremetalNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node.") ) parser.add_argument( '--power-timeout', metavar='', default=None, type=int, help=_("Timeout (in seconds, positive integer) to wait for the " "target power state before erroring out.") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal soft = getattr(parsed_args, 'soft', False) baremetal_client.node.set_power_state( parsed_args.node, self.POWER_STATE, soft, timeout=parsed_args.power_timeout) class PowerOffBaremetalNode(PowerBaremetalNode): """Power off a node""" log = logging.getLogger(__name__ + ".PowerOffBaremetalNode") POWER_STATE = 'off' def get_parser(self, prog_name): parser = super(PowerOffBaremetalNode, self).get_parser(prog_name) parser.add_argument( '--soft', dest='soft', action='store_true', default=False, help=_("Request graceful power-off.") ) return parser class PowerOnBaremetalNode(PowerBaremetalNode): """Power on a node""" log = logging.getLogger(__name__ + ".PowerOnBaremetalNode") POWER_STATE = 'on' class ProvideBaremetalNode(ProvisionStateWithWait): """Set provision state of baremetal node to 'provide'""" log = logging.getLogger(__name__ + ".ProvideBaremetalNode") PROVISION_STATE = 'provide' class RebootBaremetalNode(command.Command): """Reboot baremetal node""" log = logging.getLogger(__name__ + ".RebootBaremetalNode") def get_parser(self, prog_name): parser = super(RebootBaremetalNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node.") ) parser.add_argument( '--soft', dest='soft', action='store_true', default=False, help=_("Request Graceful reboot.") ) parser.add_argument( '--power-timeout', metavar='', default=None, type=int, help=_("Timeout (in seconds, positive integer) to wait for the " "target power state before erroring out.") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal baremetal_client.node.set_power_state( parsed_args.node, 'reboot', parsed_args.soft, timeout=parsed_args.power_timeout) class RebuildBaremetalNode(ProvisionStateWithWait): """Set provision state of baremetal node to 'rebuild'""" log = logging.getLogger(__name__ + ".RebuildBaremetalNode") PROVISION_STATE = 'rebuild' def get_parser(self, prog_name): parser = super(RebuildBaremetalNode, self).get_parser(prog_name) parser.add_argument( '--config-drive', metavar='', default=None, help=CONFIG_DRIVE_ARG_HELP) return parser class SetBaremetalNode(command.Command): """Set baremetal properties""" log = logging.getLogger(__name__ + ".SetBaremetalNode") def get_parser(self, prog_name): parser = super(SetBaremetalNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node."), ) parser.add_argument( "--instance-uuid", metavar="", help=_("Set instance UUID of node to "), ) parser.add_argument( "--name", metavar="", help=_("Set the name of the node"), ) parser.add_argument( "--chassis-uuid", metavar="", help=_("Set the chassis for the node"), ) parser.add_argument( "--driver", metavar="", help=_("Set the driver for the node"), ) parser.add_argument( '--boot-interface', metavar='', help=_('Set the boot interface for the node'), ) parser.add_argument( '--console-interface', metavar='', help=_('Set the console interface for the node'), ) parser.add_argument( '--deploy-interface', metavar='', help=_('Set the deploy interface for the node'), ) parser.add_argument( '--inspect-interface', metavar='', help=_('Set the inspect interface for the node'), ) parser.add_argument( '--management-interface', metavar='', help=_('Set the management interface for the node'), ) parser.add_argument( '--network-interface', metavar='', help=_('Set the network interface for the node'), ) parser.add_argument( '--power-interface', metavar='', help=_('Set the power interface for the node'), ) parser.add_argument( '--raid-interface', metavar='', help=_('Set the RAID interface for the node'), ) parser.add_argument( '--storage-interface', metavar='', help=_('Set the storage interface for the node'), ) parser.add_argument( '--vendor-interface', metavar='', help=_('Set the vendor interface for the node'), ) parser.add_argument( '--resource-class', metavar='', help=_('Set the resource class for the node'), ) parser.add_argument( '--target-raid-config', metavar='', help=_('Set the target RAID configuration (JSON) for the node. ' 'This can be one of: 1. a file containing JSON data of the ' 'RAID configuration; 2. "-" to read the contents from ' 'standard input; or 3. a valid JSON string.'), ) parser.add_argument( "--property", metavar="", action='append', help=_('Property to set on this baremetal node ' '(repeat option to set multiple properties)'), ) parser.add_argument( "--extra", metavar="", action='append', help=_('Extra to set on this baremetal node ' '(repeat option to set multiple extras)'), ) parser.add_argument( "--driver-info", metavar="", action='append', help=_('Driver information to set on this baremetal node ' '(repeat option to set multiple driver infos)'), ) parser.add_argument( "--instance-info", metavar="", action='append', help=_('Instance information to set on this baremetal node ' '(repeat option to set multiple instance infos)'), ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal # NOTE(rloo): Do this before updating the rest. Otherwise, it won't # work if parsed_args.node is the name and the name is # also being modified. if parsed_args.target_raid_config: raid_config = parsed_args.target_raid_config if raid_config == '-': raid_config = utils.get_from_stdin('target_raid_config') raid_config = utils.handle_json_or_file_arg(raid_config) baremetal_client.node.set_target_raid_config(parsed_args.node, raid_config) properties = [] if parsed_args.instance_uuid: instance_uuid = ["instance_uuid=%s" % parsed_args.instance_uuid] properties.extend(utils.args_array_to_patch( 'add', instance_uuid)) if parsed_args.name: name = ["name=%s" % parsed_args.name] properties.extend(utils.args_array_to_patch( 'add', name)) if parsed_args.chassis_uuid: chassis_uuid = ["chassis_uuid=%s" % parsed_args.chassis_uuid] properties.extend(utils.args_array_to_patch( 'add', chassis_uuid)) if parsed_args.driver: driver = ["driver=%s" % parsed_args.driver] properties.extend(utils.args_array_to_patch( 'add', driver)) if parsed_args.boot_interface: boot_interface = [ "boot_interface=%s" % parsed_args.boot_interface] properties.extend(utils.args_array_to_patch( 'add', boot_interface)) if parsed_args.console_interface: console_interface = [ "console_interface=%s" % parsed_args.console_interface] properties.extend(utils.args_array_to_patch( 'add', console_interface)) if parsed_args.deploy_interface: deploy_interface = [ "deploy_interface=%s" % parsed_args.deploy_interface] properties.extend(utils.args_array_to_patch( 'add', deploy_interface)) if parsed_args.inspect_interface: inspect_interface = [ "inspect_interface=%s" % parsed_args.inspect_interface] properties.extend(utils.args_array_to_patch( 'add', inspect_interface)) if parsed_args.management_interface: management_interface = [ "management_interface=%s" % parsed_args.management_interface] properties.extend(utils.args_array_to_patch( 'add', management_interface)) if parsed_args.network_interface: network_interface = [ "network_interface=%s" % parsed_args.network_interface] properties.extend(utils.args_array_to_patch( 'add', network_interface)) if parsed_args.power_interface: power_interface = [ "power_interface=%s" % parsed_args.power_interface] properties.extend(utils.args_array_to_patch( 'add', power_interface)) if parsed_args.raid_interface: raid_interface = [ "raid_interface=%s" % parsed_args.raid_interface] properties.extend(utils.args_array_to_patch( 'add', raid_interface)) if parsed_args.storage_interface: storage_interface = [ "storage_interface=%s" % parsed_args.storage_interface] properties.extend(utils.args_array_to_patch( 'add', storage_interface)) if parsed_args.vendor_interface: vendor_interface = [ "vendor_interface=%s" % parsed_args.vendor_interface] properties.extend(utils.args_array_to_patch( 'add', vendor_interface)) if parsed_args.resource_class: resource_class = [ "resource_class=%s" % parsed_args.resource_class] properties.extend(utils.args_array_to_patch( 'add', resource_class)) if parsed_args.property: properties.extend(utils.args_array_to_patch( 'add', ['properties/' + x for x in parsed_args.property])) if parsed_args.extra: properties.extend(utils.args_array_to_patch( 'add', ['extra/' + x for x in parsed_args.extra])) if parsed_args.driver_info: properties.extend(utils.args_array_to_patch( 'add', ['driver_info/' + x for x in parsed_args.driver_info])) if parsed_args.instance_info: properties.extend(utils.args_array_to_patch( 'add', ['instance_info/' + x for x in parsed_args.instance_info])) if properties: baremetal_client.node.update(parsed_args.node, properties) elif not parsed_args.target_raid_config: self.log.warning("Please specify what to set.") class ShowBaremetalNode(command.ShowOne): """Show baremetal node details""" log = logging.getLogger(__name__ + ".ShowBaremetalNode") def get_parser(self, prog_name): parser = super(ShowBaremetalNode, self).get_parser(prog_name) parser.add_argument( "node", metavar="", help=_("Name or UUID of the node (or instance UUID if --instance " "is specified)")) parser.add_argument( '--instance', dest='instance_uuid', action='store_true', default=False, help=_(' is an instance UUID.')) parser.add_argument( '--fields', nargs='+', dest='fields', metavar='', action='append', choices=res_fields.NODE_DETAILED_RESOURCE.fields, default=[], help=_("One or more node fields. Only these fields will be " "fetched from the server.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal fields = list(itertools.chain.from_iterable(parsed_args.fields)) fields = fields if fields else None if parsed_args.instance_uuid: node = baremetal_client.node.get_by_instance_uuid( parsed_args.node, fields=fields)._info else: node = baremetal_client.node.get( parsed_args.node, fields=fields)._info node.pop("links", None) node.pop("ports", None) node.pop('portgroups', None) node.pop('states', None) node.pop('volume', None) if not fields or 'chassis_uuid' in fields: node.setdefault('chassis_uuid', '') return self.dict2columns(node) class UndeployBaremetalNode(ProvisionStateWithWait): """Set provision state of baremetal node to 'deleted'""" log = logging.getLogger(__name__ + ".UndeployBaremetalNode") PROVISION_STATE = 'deleted' class UnsetBaremetalNode(command.Command): """Unset baremetal properties""" log = logging.getLogger(__name__ + ".UnsetBaremetalNode") def get_parser(self, prog_name): parser = super(UnsetBaremetalNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node.") ) parser.add_argument( '--instance-uuid', action='store_true', default=False, help=_('Unset instance UUID on this baremetal node') ) parser.add_argument( "--name", action='store_true', help=_("Unset the name of the node"), ) parser.add_argument( "--resource-class", dest='resource_class', action='store_true', help=_("Unset the resource class of the node"), ) parser.add_argument( "--target-raid-config", action='store_true', help=_("Unset the target RAID configuration of the node"), ) parser.add_argument( '--property', metavar='', action='append', help=_('Property to unset on this baremetal node ' '(repeat option to unset multiple properties)'), ) parser.add_argument( "--extra", metavar="", action='append', help=_('Extra to unset on this baremetal node ' '(repeat option to unset multiple extras)'), ) parser.add_argument( "--driver-info", metavar="", action='append', help=_('Driver information to unset on this baremetal node ' '(repeat option to unset multiple driver informations)'), ) parser.add_argument( "--instance-info", metavar="", action='append', help=_('Instance information to unset on this baremetal node ' '(repeat option to unset multiple instance informations)'), ) parser.add_argument( "--chassis-uuid", dest='chassis_uuid', action='store_true', help=_('Unset chassis UUID on this baremetal node'), ) parser.add_argument( "--boot-interface", dest='boot_interface', action='store_true', help=_('Unset boot interface on this baremetal node'), ) parser.add_argument( "--console-interface", dest='console_interface', action='store_true', help=_('Unset console interface on this baremetal node'), ) parser.add_argument( "--deploy-interface", dest='deploy_interface', action='store_true', help=_('Unset deploy interface on this baremetal node'), ) parser.add_argument( "--inspect-interface", dest='inspect_interface', action='store_true', help=_('Unset inspect interface on this baremetal node'), ) parser.add_argument( "--management-interface", dest='management_interface', action='store_true', help=_('Unset management interface on this baremetal node'), ) parser.add_argument( "--network-interface", dest='network_interface', action='store_true', help=_('Unset network interface on this baremetal node'), ) parser.add_argument( "--power-interface", dest='power_interface', action='store_true', help=_('Unset power interface on this baremetal node'), ) parser.add_argument( "--raid-interface", dest='raid_interface', action='store_true', help=_('Unset RAID interface on this baremetal node'), ) parser.add_argument( "--storage-interface", dest='storage_interface', action='store_true', help=_('Unset storage interface on this baremetal node'), ) parser.add_argument( "--vendor-interface", dest='vendor_interface', action='store_true', help=_('Unset vendor interface on this baremetal node'), ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal # NOTE(rloo): Do this before removing the rest. Otherwise, it won't # work if parsed_args.node is the name and the name is # also being removed. if parsed_args.target_raid_config: baremetal_client.node.set_target_raid_config(parsed_args.node, {}) properties = [] if parsed_args.instance_uuid: properties.extend(utils.args_array_to_patch('remove', ['instance_uuid'])) if parsed_args.name: properties.extend(utils.args_array_to_patch('remove', ['name'])) if parsed_args.resource_class: properties.extend(utils.args_array_to_patch('remove', ['resource_class'])) if parsed_args.property: properties.extend(utils.args_array_to_patch('remove', ['properties/' + x for x in parsed_args.property])) if parsed_args.extra: properties.extend(utils.args_array_to_patch('remove', ['extra/' + x for x in parsed_args.extra])) if parsed_args.driver_info: properties.extend(utils.args_array_to_patch('remove', ['driver_info/' + x for x in parsed_args.driver_info])) if parsed_args.instance_info: properties.extend(utils.args_array_to_patch('remove', ['instance_info/' + x for x in parsed_args.instance_info])) if parsed_args.chassis_uuid: properties.extend(utils.args_array_to_patch('remove', ['chassis_uuid'])) if parsed_args.boot_interface: properties.extend(utils.args_array_to_patch('remove', ['boot_interface'])) if parsed_args.console_interface: properties.extend(utils.args_array_to_patch('remove', ['console_interface'])) if parsed_args.deploy_interface: properties.extend(utils.args_array_to_patch('remove', ['deploy_interface'])) if parsed_args.inspect_interface: properties.extend(utils.args_array_to_patch('remove', ['inspect_interface'])) if parsed_args.management_interface: properties.extend(utils.args_array_to_patch('remove', ['management_interface'])) if parsed_args.network_interface: properties.extend(utils.args_array_to_patch('remove', ['network_interface'])) if parsed_args.power_interface: properties.extend(utils.args_array_to_patch('remove', ['power_interface'])) if parsed_args.raid_interface: properties.extend(utils.args_array_to_patch('remove', ['raid_interface'])) if parsed_args.storage_interface: properties.extend(utils.args_array_to_patch('remove', ['storage_interface'])) if parsed_args.vendor_interface: properties.extend(utils.args_array_to_patch('remove', ['vendor_interface'])) if properties: baremetal_client.node.update(parsed_args.node, properties) elif not parsed_args.target_raid_config: self.log.warning("Please specify what to unset.") class ValidateBaremetalNode(command.Lister): """Validate a node's driver interfaces""" log = logging.getLogger(__name__ + ".ValidateBaremetalNode") def get_parser(self, prog_name): parser = super(ValidateBaremetalNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal interfaces = baremetal_client.node.validate(parsed_args.node)._info data = [] for key, value in interfaces.items(): interface = {'interface': key} interface.update(value) data.append(interface) field_labels = ['Interface', 'Result', 'Reason'] fields = ['interface', 'result', 'reason'] data = oscutils.sort_items(data, 'interface') return (field_labels, (oscutils.get_dict_properties(s, fields) for s in data)) class VifListBaremetalNode(command.Lister): """Show attached VIFs for a node""" log = logging.getLogger(__name__ + ".VifListBaremetalNode") def get_parser(self, prog_name): parser = super(VifListBaremetalNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) columns = res_fields.VIF_RESOURCE.fields labels = res_fields.VIF_RESOURCE.labels baremetal_client = self.app.client_manager.baremetal data = baremetal_client.node.vif_list(parsed_args.node) return (labels, (oscutils.get_item_properties(s, columns) for s in data)) class VifAttachBaremetalNode(command.Command): """Attach VIF to a given node""" log = logging.getLogger(__name__ + ".VifAttachBaremetalNode") def get_parser(self, prog_name): parser = super(VifAttachBaremetalNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node") ) parser.add_argument( 'vif_id', metavar='', help=_("Name or UUID of the VIF to attach to a node.") ) parser.add_argument( '--vif-info', metavar='', action='append', help=_("Record arbitrary key/value metadata. " "Can be specified multiple times. The mandatory 'id' " "parameter cannot be specified as a key.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal fields = utils.key_value_pairs_to_dict(parsed_args.vif_info or []) baremetal_client.node.vif_attach(parsed_args.node, parsed_args.vif_id, **fields) class VifDetachBaremetalNode(command.Command): """Detach VIF from a given node""" log = logging.getLogger(__name__ + ".VifDetachBaremetalNode") def get_parser(self, prog_name): parser = super(VifDetachBaremetalNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node") ) parser.add_argument( 'vif_id', metavar='', help=_("Name or UUID of the VIF to detach from a node.") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal baremetal_client.node.vif_detach(parsed_args.node, parsed_args.vif_id) class InjectNmiBaremetalNode(command.Command): """Inject NMI to baremetal node""" log = logging.getLogger(__name__ + ".InjectNmiBaremetalNode") def get_parser(self, prog_name): parser = super(InjectNmiBaremetalNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node.") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal baremetal_client.node.inject_nmi(parsed_args.node) class ListTraitsBaremetalNode(command.Lister): """List a node's traits.""" log = logging.getLogger(__name__ + ".ListTraitsBaremetalNode") def get_parser(self, prog_name): parser = super(ListTraitsBaremetalNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) labels = res_fields.TRAIT_RESOURCE.labels baremetal_client = self.app.client_manager.baremetal traits = baremetal_client.node.get_traits(parsed_args.node) return (labels, [[trait] for trait in traits]) class AddTraitBaremetalNode(command.Command): """Add traits to a node.""" log = logging.getLogger(__name__ + ".AddTraitBaremetalNode") def get_parser(self, prog_name): parser = super(AddTraitBaremetalNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node")) parser.add_argument( 'traits', nargs='+', metavar='', help=_("Trait(s) to add")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal failures = [] for trait in parsed_args.traits: try: baremetal_client.node.add_trait(parsed_args.node, trait) print(_('Added trait %s') % trait) except exc.ClientException as e: failures.append(_("Failed to add trait %(trait)s: %(error)s") % {'trait': trait, 'error': e}) if failures: raise exc.ClientException("\n".join(failures)) class RemoveTraitBaremetalNode(command.Command): """Remove trait(s) from a node.""" log = logging.getLogger(__name__ + ".RemoveTraitBaremetalNode") def get_parser(self, prog_name): parser = super(RemoveTraitBaremetalNode, self).get_parser(prog_name) parser.add_argument( 'node', metavar='', help=_("Name or UUID of the node")) all_or_trait = parser.add_mutually_exclusive_group(required=True) all_or_trait.add_argument( '--all', dest='remove_all', action='store_true', help=_("Remove all traits")) all_or_trait.add_argument( 'traits', metavar='', nargs='*', default=[], help=_("Trait(s) to remove")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal failures = [] if parsed_args.remove_all: baremetal_client.node.remove_all_traits(parsed_args.node) else: for trait in parsed_args.traits: try: baremetal_client.node.remove_trait(parsed_args.node, trait) print(_('Removed trait %s') % trait) except exc.ClientException as e: failures.append(_("Failed to remove trait %(trait)s: " "%(error)s") % {'trait': trait, 'error': e}) if failures: raise exc.ClientException("\n".join(failures)) python-ironicclient-2.2.0/ironicclient/osc/v1/baremetal_create.py0000666000175100017510000000256613232474343025205 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 logging from osc_lib.command import command from ironicclient.common.i18n import _ from ironicclient.v1 import create_resources class CreateBaremetal(command.Command): """Create resources from files""" log = logging.getLogger(__name__ + ".CreateBaremetal") def get_parser(self, prog_name): parser = super(CreateBaremetal, self).get_parser(prog_name) parser.add_argument( "resource_files", metavar="", nargs="+", help=_("File (.yaml or .json) containing descriptions of the " "resources to create. Can be specified multiple times.")) return parser def take_action(self, parsed_args): create_resources.create_resources(self.app.client_manager.baremetal, parsed_args.resource_files) python-ironicclient-2.2.0/ironicclient/osc/v1/baremetal_chassis.py0000666000175100017510000002557613232474343025405 0ustar zuulzuul00000000000000# # Copyright 2016 Intel Corporation # # 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 itertools import logging from osc_lib.command import command from osc_lib import utils as oscutils from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient import exc from ironicclient.v1 import resource_fields as res_fields class CreateBaremetalChassis(command.ShowOne): """Create a new chassis.""" log = logging.getLogger(__name__ + ".CreateBaremetalChassis") def get_parser(self, prog_name): parser = super(CreateBaremetalChassis, self).get_parser(prog_name) parser.add_argument( '--description', dest='description', metavar='', help=_('Description for the chassis') ) parser.add_argument( '--extra', metavar='', action='append', help=_("Record arbitrary key/value metadata. " "Can be specified multiple times.") ) parser.add_argument( '--uuid', metavar='', help=_("Unique UUID of the chassis") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal field_list = ['description', 'extra', 'uuid'] fields = dict((k, v) for (k, v) in vars(parsed_args).items() if k in field_list and not (v is None)) fields = utils.args_array_to_dict(fields, 'extra') chassis = baremetal_client.chassis.create(**fields)._info chassis.pop('links', None) chassis.pop('nodes', None) return self.dict2columns(chassis) class DeleteBaremetalChassis(command.Command): """Delete a chassis.""" log = logging.getLogger(__name__ + ".DeleteBaremetalChassis") def get_parser(self, prog_name): parser = super(DeleteBaremetalChassis, self).get_parser(prog_name) parser.add_argument( "chassis", metavar="", nargs="+", help=_("UUIDs of chassis to delete") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal failures = [] for chassis in parsed_args.chassis: try: baremetal_client.chassis.delete(chassis) print(_('Deleted chassis %s') % chassis) except exc.ClientException as e: failures.append(_("Failed to delete chassis %(chassis)s: " "%(error)s") % {'chassis': chassis, 'error': e}) if failures: raise exc.ClientException("\n".join(failures)) class ListBaremetalChassis(command.Lister): """List the chassis.""" log = logging.getLogger(__name__ + ".ListBaremetalChassis") def get_parser(self, prog_name): parser = super(ListBaremetalChassis, self).get_parser(prog_name) display_group = parser.add_mutually_exclusive_group(required=False) display_group.add_argument( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], choices=res_fields.CHASSIS_DETAILED_RESOURCE.fields, help=_("One or more chassis fields. Only these fields will be " "fetched from the server. Cannot be used when '--long' is " "specified.") ) parser.add_argument( '--limit', metavar='', type=int, help=_('Maximum number of chassis to return per request, ' '0 for no limit. Default is the maximum number used ' 'by the Baremetal API Service.') ) display_group.add_argument( '--long', default=False, action='store_true', help=_("Show detailed information about the chassis") ) parser.add_argument( '--marker', metavar='', help=_('Chassis UUID (for example, of the last chassis in the ' 'list from a previous request). Returns the list of ' 'chassis after this UUID.') ) parser.add_argument( '--sort', metavar="[:]", help=_('Sort output by specified chassis fields and directions ' '(asc or desc) (default: asc). Multiple fields and ' 'directions can be specified, separated by comma.') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) client = self.app.client_manager.baremetal columns = res_fields.CHASSIS_RESOURCE.fields labels = res_fields.CHASSIS_RESOURCE.labels params = {} if parsed_args.limit is not None and parsed_args.limit < 0: raise exc.CommandError( _('Expected non-negative --limit, got %s') % parsed_args.limit) params['limit'] = parsed_args.limit params['marker'] = parsed_args.marker if parsed_args.long: params['detail'] = parsed_args.long columns = res_fields.CHASSIS_DETAILED_RESOURCE.fields labels = res_fields.CHASSIS_DETAILED_RESOURCE.labels elif parsed_args.fields: params['detail'] = False fields = itertools.chain.from_iterable(parsed_args.fields) resource = res_fields.Resource(list(fields)) columns = resource.fields labels = resource.labels params['fields'] = columns self.log.debug("params(%s)", params) data = client.chassis.list(**params) data = oscutils.sort_items(data, parsed_args.sort) return (labels, (oscutils.get_item_properties(s, columns, formatters={ 'Properties': oscutils.format_dict},) for s in data)) class SetBaremetalChassis(command.Command): """Set chassis properties.""" log = logging.getLogger(__name__ + ".SetBaremetalChassis") def get_parser(self, prog_name): parser = super(SetBaremetalChassis, self).get_parser(prog_name) parser.add_argument( 'chassis', metavar='', help=_("UUID of the chassis") ) parser.add_argument( "--description", metavar="", help=_("Set the description of the chassis") ) parser.add_argument( "--extra", metavar="", action='append', help=_('Extra to set on this chassis ' '(repeat option to set multiple extras)') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal properties = [] if parsed_args.description: description = ["description=%s" % parsed_args.description] properties.extend(utils.args_array_to_patch( 'add', description)) if parsed_args.extra: properties.extend(utils.args_array_to_patch( 'add', ['extra/' + x for x in parsed_args.extra])) if properties: baremetal_client.chassis.update(parsed_args.chassis, properties) else: self.log.warning("Please specify what to set.") class ShowBaremetalChassis(command.ShowOne): """Show chassis details.""" log = logging.getLogger(__name__ + ".ShowBaremetalChassis") def get_parser(self, prog_name): parser = super(ShowBaremetalChassis, self).get_parser(prog_name) parser.add_argument( "chassis", metavar="", help=_("UUID of the chassis") ) parser.add_argument( '--fields', nargs='+', dest='fields', metavar='', action='append', choices=res_fields.CHASSIS_DETAILED_RESOURCE.fields, default=[], help=_("One or more chassis fields. Only these fields will be " "fetched from the server.") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal fields = list(itertools.chain.from_iterable(parsed_args.fields)) fields = fields if fields else None chassis = baremetal_client.chassis.get(parsed_args.chassis, fields=fields)._info chassis.pop("links", None) chassis.pop("nodes", None) return zip(*sorted(chassis.items())) class UnsetBaremetalChassis(command.Command): """Unset chassis properties.""" log = logging.getLogger(__name__ + ".UnsetBaremetalChassis") def get_parser(self, prog_name): parser = super(UnsetBaremetalChassis, self).get_parser(prog_name) parser.add_argument( 'chassis', metavar='', help=_("UUID of the chassis") ) parser.add_argument( '--description', action='store_true', default=False, help=_('Clear the chassis description') ) parser.add_argument( "--extra", metavar="", action='append', help=_('Extra to unset on this chassis ' '(repeat option to unset multiple extras)') ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal properties = [] if parsed_args.description: properties.extend(utils.args_array_to_patch('remove', ['description'])) if parsed_args.extra: properties.extend(utils.args_array_to_patch('remove', ['extra/' + x for x in parsed_args.extra])) if properties: baremetal_client.chassis.update(parsed_args.chassis, properties) else: self.log.warning("Please specify what to unset.") python-ironicclient-2.2.0/ironicclient/osc/v1/baremetal_volume_connector.py0000666000175100017510000003234013232474373027317 0ustar zuulzuul00000000000000# Copyright 2017 FUJITSU LIMITED # # 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 itertools import logging from osc_lib.command import command from osc_lib import utils as oscutils from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient import exc from ironicclient.v1 import resource_fields as res_fields class CreateBaremetalVolumeConnector(command.ShowOne): """Create a new baremetal volume connector.""" log = logging.getLogger(__name__ + ".CreateBaremetalVolumeConnector") def get_parser(self, prog_name): parser = ( super(CreateBaremetalVolumeConnector, self).get_parser(prog_name)) parser.add_argument( '--node', dest='node_uuid', metavar='', required=True, help=_('UUID of the node that this volume connector belongs to.')) parser.add_argument( '--type', dest='type', metavar="", required=True, choices=('iqn', 'ip', 'mac', 'wwnn', 'wwpn', 'port', 'portgroup'), help=_("Type of the volume connector. Can be 'iqn', 'ip', 'mac', " "'wwnn', 'wwpn', 'port', 'portgroup'.")) parser.add_argument( '--connector-id', dest='connector_id', required=True, metavar="", help=_("ID of the volume connector in the specified type. For " "example, the iSCSI initiator IQN for the node if the type " "is 'iqn'.")) parser.add_argument( '--uuid', dest='uuid', metavar='', help=_("UUID of the volume connector.")) parser.add_argument( '--extra', dest='extra', metavar="", action='append', help=_("Record arbitrary key/value metadata. " "Can be specified multiple times.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) baremetal_client = self.app.client_manager.baremetal field_list = ['extra', 'type', 'connector_id', 'node_uuid', 'uuid'] fields = dict((k, v) for (k, v) in vars(parsed_args).items() if k in field_list and v is not None) fields = utils.args_array_to_dict(fields, 'extra') volume_connector = baremetal_client.volume_connector.create(**fields) data = dict([(f, getattr(volume_connector, f, '')) for f in res_fields.VOLUME_CONNECTOR_DETAILED_RESOURCE.fields]) return self.dict2columns(data) class ShowBaremetalVolumeConnector(command.ShowOne): """Show baremetal volume connector details.""" log = logging.getLogger(__name__ + ".ShowBaremetalVolumeConnector") def get_parser(self, prog_name): parser = ( super(ShowBaremetalVolumeConnector, self).get_parser(prog_name)) parser.add_argument( 'volume_connector', metavar='', help=_("UUID of the volume connector.")) parser.add_argument( '--fields', nargs='+', dest='fields', metavar='', action='append', choices=res_fields.VOLUME_CONNECTOR_DETAILED_RESOURCE.fields, default=[], help=_("One or more volume connector fields. Only these fields " "will be fetched from the server.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal fields = list(itertools.chain.from_iterable(parsed_args.fields)) fields = fields if fields else None volume_connector = baremetal_client.volume_connector.get( parsed_args.volume_connector, fields=fields)._info volume_connector.pop("links", None) return zip(*sorted(volume_connector.items())) class ListBaremetalVolumeConnector(command.Lister): """List baremetal volume connectors.""" log = logging.getLogger(__name__ + ".ListBaremetalVolumeConnector") def get_parser(self, prog_name): parser = ( super(ListBaremetalVolumeConnector, self).get_parser(prog_name)) parser.add_argument( '--node', dest='node', metavar='', help=_("Only list volume connectors of this node (name or UUID).")) parser.add_argument( '--limit', dest='limit', metavar='', type=int, help=_('Maximum number of volume connectors to return per ' 'request, 0 for no limit. Default is the maximum number ' 'used by the Baremetal API Service.')) parser.add_argument( '--marker', dest='marker', metavar='', help=_('Volume connector UUID (for example, of the last volume ' 'connector in the list from a previous request). Returns ' 'the list of volume connectors after this UUID.')) parser.add_argument( '--sort', dest='sort', metavar='[:]', help=_('Sort output by specified volume connector fields and ' 'directions (asc or desc) (default:asc). Multiple fields ' 'and directions can be specified, separated by comma.')) display_group = parser.add_mutually_exclusive_group(required=False) display_group.add_argument( '--long', dest='detail', action='store_true', default=False, help=_("Show detailed information about volume connectors.")) display_group.add_argument( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], choices=res_fields.VOLUME_CONNECTOR_DETAILED_RESOURCE.fields, help=_("One or more volume connector fields. Only these fields " "will be fetched from the server. Can not be used when " "'--long' is specified.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) client = self.app.client_manager.baremetal columns = res_fields.VOLUME_CONNECTOR_RESOURCE.fields labels = res_fields.VOLUME_CONNECTOR_RESOURCE.labels params = {} if parsed_args.limit is not None and parsed_args.limit < 0: raise exc.CommandError( _('Expected non-negative --limit, got %s') % parsed_args.limit) params['limit'] = parsed_args.limit params['marker'] = parsed_args.marker if parsed_args.node is not None: params['node'] = parsed_args.node if parsed_args.detail: params['detail'] = parsed_args.detail columns = res_fields.VOLUME_CONNECTOR_DETAILED_RESOURCE.fields labels = res_fields.VOLUME_CONNECTOR_DETAILED_RESOURCE.labels elif parsed_args.fields: params['detail'] = False fields = itertools.chain.from_iterable(parsed_args.fields) resource = res_fields.Resource(list(fields)) columns = resource.fields labels = resource.labels params['fields'] = columns self.log.debug("params(%s)" % params) data = client.volume_connector.list(**params) data = oscutils.sort_items(data, parsed_args.sort) return (labels, (oscutils.get_item_properties(s, columns, formatters={ 'Properties': oscutils.format_dict},) for s in data)) class DeleteBaremetalVolumeConnector(command.Command): """Unregister baremetal volume connector(s).""" log = logging.getLogger(__name__ + ".DeleteBaremetalVolumeConnector") def get_parser(self, prog_name): parser = ( super(DeleteBaremetalVolumeConnector, self).get_parser(prog_name)) parser.add_argument( 'volume_connectors', metavar='', nargs='+', help=_("UUID(s) of the volume connector(s) to delete.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal failures = [] for volume_connector in parsed_args.volume_connectors: try: baremetal_client.volume_connector.delete(volume_connector) print(_('Deleted volume connector %s') % volume_connector) except exc.ClientException as e: failures.append(_("Failed to delete volume connector " "%(volume_connector)s: %(error)s") % {'volume_connector': volume_connector, 'error': e}) if failures: raise exc.ClientException("\n".join(failures)) class SetBaremetalVolumeConnector(command.Command): """Set baremetal volume connector properties.""" log = logging.getLogger(__name__ + ".SetBaremetalVolumeConnector") def get_parser(self, prog_name): parser = ( super(SetBaremetalVolumeConnector, self).get_parser(prog_name)) parser.add_argument( 'volume_connector', metavar='', help=_("UUID of the volume connector.")) parser.add_argument( '--node', dest='node_uuid', metavar='', help=_('UUID of the node that this volume connector belongs to.')) parser.add_argument( '--type', dest='type', metavar="", choices=('iqn', 'ip', 'mac', 'wwnn', 'wwpn', 'port', 'portgroup'), help=_("Type of the volume connector. Can be 'iqn', 'ip', 'mac', " "'wwnn', 'wwpn', 'port', 'portgroup'.")) parser.add_argument( '--connector-id', dest='connector_id', metavar="", help=_("ID of the volume connector in the specified type.")) parser.add_argument( '--extra', dest='extra', metavar="", action='append', help=_("Record arbitrary key/value metadata. " "Can be specified multiple times.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal properties = [] if parsed_args.node_uuid: properties.extend(utils.args_array_to_patch( 'add', ["node_uuid=%s" % parsed_args.node_uuid])) if parsed_args.type: properties.extend(utils.args_array_to_patch( 'add', ["type=%s" % parsed_args.type])) if parsed_args.connector_id: properties.extend(utils.args_array_to_patch( 'add', ["connector_id=%s" % parsed_args.connector_id])) if parsed_args.extra: properties.extend(utils.args_array_to_patch( 'add', ["extra/" + x for x in parsed_args.extra])) if properties: baremetal_client.volume_connector.update( parsed_args.volume_connector, properties) else: self.log.warning("Please specify what to set.") class UnsetBaremetalVolumeConnector(command.Command): """Unset baremetal volume connector properties.""" log = logging.getLogger(__name__ + "UnsetBaremetalVolumeConnector") def get_parser(self, prog_name): parser = ( super(UnsetBaremetalVolumeConnector, self).get_parser(prog_name)) parser.add_argument( 'volume_connector', metavar='', help=_("UUID of the volume connector.")) parser.add_argument( '--extra', dest='extra', metavar="", action='append', help=_('Extra to unset (repeat option to unset multiple extras)')) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal properties = [] if parsed_args.extra: properties.extend(utils.args_array_to_patch('remove', ['extra/' + x for x in parsed_args.extra])) if properties: baremetal_client.volume_connector.update( parsed_args.volume_connector, properties) else: self.log.warning("Please specify what to unset.") python-ironicclient-2.2.0/ironicclient/osc/v1/baremetal_volume_target.py0000666000175100017510000003564413232474343026622 0ustar zuulzuul00000000000000# Copyright 2017 FUJITSU LIMITED # # 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 itertools import logging from osc_lib.command import command from osc_lib import utils as oscutils from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient import exc from ironicclient.v1 import resource_fields as res_fields class CreateBaremetalVolumeTarget(command.ShowOne): """Create a new baremetal volume target.""" log = logging.getLogger(__name__ + ".CreateBaremetalVolumeTarget") def get_parser(self, prog_name): parser = super(CreateBaremetalVolumeTarget, self).get_parser(prog_name) parser.add_argument( '--node', dest='node_uuid', metavar='', required=True, help=_('UUID of the node that this volume target belongs to.')) parser.add_argument( '--type', dest='volume_type', metavar="", required=True, help=_("Type of the volume target, e.g. 'iscsi', " "'fibre_channel'.")) parser.add_argument( '--property', dest='properties', metavar="", action='append', help=_("Key/value property related to the type of this volume " "target. Can be specified multiple times." )) parser.add_argument( '--boot-index', dest='boot_index', metavar="", type=int, required=True, help=_("Boot index of the volume target.")) parser.add_argument( '--volume-id', dest='volume_id', metavar="", required=True, help=_("ID of the volume associated with this target.")) parser.add_argument( '--uuid', dest='uuid', metavar='', help=_("UUID of the volume target.")) parser.add_argument( '--extra', dest='extra', metavar="", action='append', help=_("Record arbitrary key/value metadata. " "Can be specified multiple times.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) baremetal_client = self.app.client_manager.baremetal if parsed_args.boot_index < 0: raise exc.CommandError( _('Expected non-negative --boot-index, got %s') % parsed_args.boot_index) field_list = ['extra', 'volume_type', 'properties', 'boot_index', 'node_uuid', 'volume_id', 'uuid'] fields = dict((k, v) for (k, v) in vars(parsed_args).items() if k in field_list and v is not None) fields = utils.args_array_to_dict(fields, 'properties') fields = utils.args_array_to_dict(fields, 'extra') volume_target = baremetal_client.volume_target.create(**fields) data = dict([(f, getattr(volume_target, f, '')) for f in res_fields.VOLUME_TARGET_DETAILED_RESOURCE.fields]) return self.dict2columns(data) class ShowBaremetalVolumeTarget(command.ShowOne): """Show baremetal volume target details.""" log = logging.getLogger(__name__ + ".ShowBaremetalVolumeTarget") def get_parser(self, prog_name): parser = super(ShowBaremetalVolumeTarget, self).get_parser(prog_name) parser.add_argument( 'volume_target', metavar='', help=_("UUID of the volume target.")) parser.add_argument( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], choices=res_fields.VOLUME_TARGET_DETAILED_RESOURCE.fields, help=_("One or more volume target fields. Only these fields will " "be fetched from the server.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal fields = list(itertools.chain.from_iterable(parsed_args.fields)) fields = fields if fields else None volume_target = baremetal_client.volume_target.get( parsed_args.volume_target, fields=fields)._info volume_target.pop("links", None) return zip(*sorted(volume_target.items())) class ListBaremetalVolumeTarget(command.Lister): """List baremetal volume targets.""" log = logging.getLogger(__name__ + ".ListBaremetalVolumeTarget") def get_parser(self, prog_name): parser = super(ListBaremetalVolumeTarget, self).get_parser(prog_name) parser.add_argument( '--node', dest='node', metavar='', help=_("Only list volume targets of this node (name or UUID).")) parser.add_argument( '--limit', dest='limit', metavar='', type=int, help=_('Maximum number of volume targets to return per request, ' '0 for no limit. Default is the maximum number used ' 'by the Baremetal API Service.')) parser.add_argument( '--marker', dest='marker', metavar='', help=_('Volume target UUID (for example, of the last ' 'volume target in the list from a previous request). ' 'Returns the list of volume targets after this UUID.')) parser.add_argument( '--sort', dest='sort', metavar='[:]', help=_('Sort output by specified volume target fields and ' 'directions (asc or desc) (default:asc). Multiple fields ' 'and directions can be specified, separated by comma.')) display_group = parser.add_mutually_exclusive_group(required=False) display_group.add_argument( '--long', dest='detail', action='store_true', default=False, help=_("Show detailed information about volume targets.")) display_group.add_argument( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], choices=res_fields.VOLUME_TARGET_DETAILED_RESOURCE.fields, help=_("One or more volume target fields. Only these fields will " "be fetched from the server. Can not be used when " "'--long' is specified.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) client = self.app.client_manager.baremetal columns = res_fields.VOLUME_TARGET_RESOURCE.fields labels = res_fields.VOLUME_TARGET_RESOURCE.labels params = {} if parsed_args.limit is not None and parsed_args.limit < 0: raise exc.CommandError( _('Expected non-negative --limit, got %s') % parsed_args.limit) params['limit'] = parsed_args.limit params['marker'] = parsed_args.marker if parsed_args.node is not None: params['node'] = parsed_args.node if parsed_args.detail: params['detail'] = parsed_args.detail columns = res_fields.VOLUME_TARGET_DETAILED_RESOURCE.fields labels = res_fields.VOLUME_TARGET_DETAILED_RESOURCE.labels elif parsed_args.fields: params['detail'] = False fields = itertools.chain.from_iterable(parsed_args.fields) resource = res_fields.Resource(list(fields)) columns = resource.fields labels = resource.labels params['fields'] = columns self.log.debug("params(%s)" % params) data = client.volume_target.list(**params) data = oscutils.sort_items(data, parsed_args.sort) return (labels, (oscutils.get_item_properties(s, columns, formatters={ 'Properties': oscutils.format_dict},) for s in data)) class DeleteBaremetalVolumeTarget(command.Command): """Unregister baremetal volume target(s).""" log = logging.getLogger(__name__ + ".DeleteBaremetalVolumeTarget") def get_parser(self, prog_name): parser = ( super(DeleteBaremetalVolumeTarget, self).get_parser(prog_name)) parser.add_argument( 'volume_targets', metavar='', nargs='+', help=_("UUID(s) of the volume target(s) to delete.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal failures = [] for volume_target in parsed_args.volume_targets: try: baremetal_client.volume_target.delete(volume_target) print(_('Deleted volume target %s') % volume_target) except exc.ClientException as e: failures.append(_("Failed to delete volume target " "%(volume_target)s: %(error)s") % {'volume_target': volume_target, 'error': e}) if failures: raise exc.ClientException("\n".join(failures)) class SetBaremetalVolumeTarget(command.Command): """Set baremetal volume target properties.""" log = logging.getLogger(__name__ + ".SetBaremetalVolumeTarget") def get_parser(self, prog_name): parser = ( super(SetBaremetalVolumeTarget, self).get_parser(prog_name)) parser.add_argument( 'volume_target', metavar='', help=_("UUID of the volume target.")) parser.add_argument( '--node', dest='node_uuid', metavar='', help=_('UUID of the node that this volume target belongs to.')) parser.add_argument( '--type', dest='volume_type', metavar="", help=_("Type of the volume target, e.g. 'iscsi', " "'fibre_channel'.")) parser.add_argument( '--property', dest='properties', metavar="", action='append', help=_("Key/value property related to the type of this volume " "target. Can be specified multiple times.")) parser.add_argument( '--boot-index', dest='boot_index', metavar="", type=int, help=_("Boot index of the volume target.")) parser.add_argument( '--volume-id', dest='volume_id', metavar="", help=_("ID of the volume associated with this target.")) parser.add_argument( '--extra', dest='extra', metavar="", action='append', help=_("Record arbitrary key/value metadata. " "Can be specified multiple times.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal if parsed_args.boot_index is not None and parsed_args.boot_index < 0: raise exc.CommandError( _('Expected non-negative --boot-index, got %s') % parsed_args.boot_index) properties = [] if parsed_args.node_uuid: properties.extend(utils.args_array_to_patch( 'add', ["node_uuid=%s" % parsed_args.node_uuid])) if parsed_args.volume_type: properties.extend(utils.args_array_to_patch( 'add', ["volume_type=%s" % parsed_args.volume_type])) if parsed_args.boot_index: properties.extend(utils.args_array_to_patch( 'add', ["boot_index=%s" % parsed_args.boot_index])) if parsed_args.volume_id: properties.extend(utils.args_array_to_patch( 'add', ["volume_id=%s" % parsed_args.volume_id])) if parsed_args.properties: properties.extend(utils.args_array_to_patch( 'add', ["properties/" + x for x in parsed_args.properties])) if parsed_args.extra: properties.extend(utils.args_array_to_patch( 'add', ["extra/" + x for x in parsed_args.extra])) if properties: baremetal_client.volume_target.update( parsed_args.volume_target, properties) else: self.log.warning("Please specify what to set.") class UnsetBaremetalVolumeTarget(command.Command): """Unset baremetal volume target properties.""" log = logging.getLogger(__name__ + "UnsetBaremetalVolumeTarget") def get_parser(self, prog_name): parser = ( super(UnsetBaremetalVolumeTarget, self).get_parser(prog_name)) parser.add_argument( 'volume_target', metavar='', help=_("UUID of the volume target.")) parser.add_argument( '--extra', dest='extra', metavar="", action='append', help=_('Extra to unset (repeat option to unset multiple extras)')) parser.add_argument( "--property", dest='properties', metavar="", action='append', help='Property to unset on this baremetal volume target ' '(repeat option to unset multiple properties).', ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal properties = [] if parsed_args.extra: properties.extend(utils.args_array_to_patch('remove', ['extra/' + x for x in parsed_args.extra])) if parsed_args.properties: properties.extend(utils.args_array_to_patch( 'remove', ['properties/' + x for x in parsed_args.properties])) if properties: baremetal_client.volume_target.update( parsed_args.volume_target, properties) else: self.log.warning("Please specify what to unset.") python-ironicclient-2.2.0/ironicclient/osc/v1/baremetal_port.py0000666000175100017510000004330213232474343024717 0ustar zuulzuul00000000000000# # Copyright 2015 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import itertools import logging from osc_lib.command import command from osc_lib import utils as oscutils from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient import exc from ironicclient.v1 import resource_fields as res_fields class CreateBaremetalPort(command.ShowOne): """Create a new port""" log = logging.getLogger(__name__ + ".CreateBaremetalPort") def get_parser(self, prog_name): parser = super(CreateBaremetalPort, self).get_parser(prog_name) parser.add_argument( 'address', metavar='
', help=_('MAC address for this port.') ) parser.add_argument( '--node', dest='node_uuid', metavar='', required=True, help=_('UUID of the node that this port belongs to.') ) parser.add_argument( '--uuid', dest='uuid', metavar='', help=_('UUID of the port.')) parser.add_argument( '--extra', metavar="", action='append', help=_("Record arbitrary key/value metadata. " "Can be specified multiple times.") ) parser.add_argument( '--local-link-connection', metavar="", action='append', help=_("Key/value metadata describing Local link connection " "information. Valid keys are 'switch_info', 'switch_id', " "and 'port_id'. The keys 'switch_id' and 'port_id' are " "required. Can be specified multiple times.") ) parser.add_argument( '-l', dest='local_link_connection_deprecated', metavar="", action='append', help=_("DEPRECATED. Please use --local-link-connection instead. " "Key/value metadata describing Local link connection " "information. Valid keys are 'switch_info', 'switch_id', " "and 'port_id'. The keys 'switch_id' and 'port_id' are " "required. Can be specified multiple times.") ) parser.add_argument( '--pxe-enabled', metavar='', help=_('Indicates whether this Port should be used when ' 'PXE booting this Node.') ) parser.add_argument( '--port-group', dest='portgroup_uuid', metavar='', help=_("UUID of the port group that this port belongs to.")) parser.add_argument( '--physical-network', dest='physical_network', metavar='', help=_("Name of the physical network to which this port is " "connected.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal if parsed_args.local_link_connection_deprecated: self.log.warning("Please use --local-link-connection instead " "of -l, as it is deprecated and will be " "removed in future releases.") # It is parsed to either None, or to an array if parsed_args.local_link_connection: parsed_args.local_link_connection.extend( parsed_args.local_link_connection_deprecated) else: parsed_args.local_link_connection = ( parsed_args.local_link_connection_deprecated) field_list = ['address', 'uuid', 'extra', 'node_uuid', 'pxe_enabled', 'local_link_connection', 'portgroup_uuid', 'physical_network'] fields = dict((k, v) for (k, v) in vars(parsed_args).items() if k in field_list and v is not None) fields = utils.args_array_to_dict(fields, 'extra') fields = utils.args_array_to_dict(fields, 'local_link_connection') port = baremetal_client.port.create(**fields) data = dict([(f, getattr(port, f, '')) for f in res_fields.PORT_DETAILED_RESOURCE.fields]) return self.dict2columns(data) class ShowBaremetalPort(command.ShowOne): """Show baremetal port details.""" log = logging.getLogger(__name__ + ".ShowBaremetalPort") def get_parser(self, prog_name): parser = super(ShowBaremetalPort, self).get_parser(prog_name) parser.add_argument( "port", metavar="", help=_("UUID of the port (or MAC address if --address is " "specified).") ) parser.add_argument( '--address', dest='address', action='store_true', default=False, help=_(' is the MAC address (instead of the UUID) of the ' 'port.') ) parser.add_argument( '--fields', nargs='+', dest='fields', metavar='', action='append', choices=res_fields.PORT_DETAILED_RESOURCE.fields, default=[], help=_("One or more port fields. Only these fields will be " "fetched from the server.") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal fields = list(itertools.chain.from_iterable(parsed_args.fields)) fields = fields if fields else None if parsed_args.address: port = baremetal_client.port.get_by_address( parsed_args.port, fields=fields)._info else: port = baremetal_client.port.get( parsed_args.port, fields=fields)._info port.pop("links", None) return zip(*sorted(port.items())) class UnsetBaremetalPort(command.Command): """Unset baremetal port properties.""" log = logging.getLogger(__name__ + ".UnsetBaremetalPort") def get_parser(self, prog_name): parser = super(UnsetBaremetalPort, self).get_parser(prog_name) parser.add_argument( 'port', metavar='', help=_("UUID of the port.") ) parser.add_argument( "--extra", metavar="", action='append', help=_('Extra to unset on this baremetal port ' '(repeat option to unset multiple extras)') ) parser.add_argument( '--port-group', action='store_true', dest='portgroup', help=_("Remove port from the port group")) parser.add_argument( '--physical-network', action='store_true', dest='physical_network', help=_("Unset the physical network on this baremetal port.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal properties = [] if parsed_args.extra: properties.extend(utils.args_array_to_patch( 'remove', ['extra/' + x for x in parsed_args.extra])) if parsed_args.portgroup: properties.extend(utils.args_array_to_patch('remove', ['portgroup_uuid'])) if parsed_args.physical_network: properties.extend(utils.args_array_to_patch('remove', ['physical_network'])) if properties: baremetal_client.port.update(parsed_args.port, properties) else: self.log.warning("Please specify what to unset.") class SetBaremetalPort(command.Command): """Set baremetal port properties.""" log = logging.getLogger(__name__ + ".SetBaremetalPort") def get_parser(self, prog_name): parser = super(SetBaremetalPort, self).get_parser(prog_name) parser.add_argument( 'port', metavar='', help=_("UUID of the port") ) parser.add_argument( '--node', dest='node_uuid', metavar='', help=_('Set UUID of the node that this port belongs to') ) parser.add_argument( "--address", metavar="
", dest='address', help=_("Set MAC address for this port") ) parser.add_argument( "--extra", metavar="", action='append', help=_('Extra to set on this baremetal port ' '(repeat option to set multiple extras)') ) parser.add_argument( "--port-group", metavar="", dest='portgroup_uuid', help=_('Set UUID of the port group that this port belongs to.')) parser.add_argument( "--local-link-connection", metavar="", action='append', help=_("Key/value metadata describing local link connection " "information. Valid keys are 'switch_info', 'switch_id', " "and 'port_id'. The keys 'switch_id' and 'port_id' are " "required. Can be specified multiple times.") ) pxe_enabled_group = parser.add_mutually_exclusive_group(required=False) pxe_enabled_group.add_argument( "--pxe-enabled", dest='pxe_enabled', default=None, action='store_true', help=_("Indicates that this port should be used when " "PXE booting this node (default)") ) pxe_enabled_group.add_argument( "--pxe-disabled", dest='pxe_enabled', default=None, action='store_false', help=_("Indicates that this port should not be used when " "PXE booting this node") ) parser.add_argument( '--physical-network', metavar='', dest='physical_network', help=_("Set the name of the physical network to which this port " "is connected.")) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal properties = [] if parsed_args.node_uuid: node_uuid = ["node_uuid=%s" % parsed_args.node_uuid] properties.extend(utils.args_array_to_patch( 'add', node_uuid)) if parsed_args.address: address = ["address=%s" % parsed_args.address] properties.extend(utils.args_array_to_patch('add', address)) if parsed_args.extra: properties.extend(utils.args_array_to_patch( 'add', ['extra/' + x for x in parsed_args.extra])) if parsed_args.portgroup_uuid: portgroup_uuid = ["portgroup_uuid=%s" % parsed_args.portgroup_uuid] properties.extend(utils.args_array_to_patch('add', portgroup_uuid)) if parsed_args.local_link_connection: properties.extend(utils.args_array_to_patch( 'add', ['local_link_connection/' + x for x in parsed_args.local_link_connection])) if parsed_args.pxe_enabled is not None: properties.extend(utils.args_array_to_patch( 'add', ['pxe_enabled=%s' % parsed_args.pxe_enabled])) if parsed_args.physical_network: physical_network = ["physical_network=%s" % parsed_args.physical_network] properties.extend(utils.args_array_to_patch('add', physical_network)) if properties: baremetal_client.port.update(parsed_args.port, properties) else: self.log.warning("Please specify what to set.") class DeleteBaremetalPort(command.Command): """Delete port(s).""" log = logging.getLogger(__name__ + ".DeleteBaremetalPort") def get_parser(self, prog_name): parser = super(DeleteBaremetalPort, self).get_parser(prog_name) parser.add_argument( "ports", metavar="", nargs="+", help=_("UUID(s) of the port(s) to delete.") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal failures = [] for port in parsed_args.ports: try: baremetal_client.port.delete(port) print(_('Deleted port %s') % port) except exc.ClientException as e: failures.append(_("Failed to delete port %(port)s: %(error)s") % {'port': port, 'error': e}) if failures: raise exc.ClientException("\n".join(failures)) class ListBaremetalPort(command.Lister): """List baremetal ports.""" log = logging.getLogger(__name__ + ".ListBaremetalPort") def get_parser(self, prog_name): parser = super(ListBaremetalPort, self).get_parser(prog_name) parser.add_argument( '--address', dest='address', metavar='', help=_("Only show information for the port with this MAC address.") ) parser.add_argument( '--node', dest='node', metavar='', help=_("Only list ports of this node (name or UUID).") ) parser.add_argument( "--port-group", metavar="", dest='portgroup', help=_('Only list ports of this port group (name or UUID).')) parser.add_argument( '--limit', metavar='', type=int, help=_('Maximum number of ports to return per request, ' '0 for no limit. Default is the maximum number used ' 'by the Ironic API Service.') ) parser.add_argument( '--marker', metavar='', help=_('Port UUID (for example, of the last port in the list ' 'from a previous request). Returns the list of ports ' 'after this UUID.') ) parser.add_argument( '--sort', metavar="[:]", help=_('Sort output by specified port fields and directions ' '(asc or desc) (default: asc). Multiple fields and ' 'directions can be specified, separated by comma.') ) display_group = parser.add_mutually_exclusive_group() display_group.add_argument( '--long', dest='detail', action='store_true', default=False, help=_("Show detailed information about ports.") ) display_group.add_argument( '--fields', nargs='+', dest='fields', metavar='', action='append', default=[], choices=res_fields.PORT_DETAILED_RESOURCE.fields, help=_("One or more port fields. Only these fields will be " "fetched from the server. Can not be used when " "'--long' is specified.") ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) client = self.app.client_manager.baremetal columns = res_fields.PORT_RESOURCE.fields labels = res_fields.PORT_RESOURCE.labels params = {} if parsed_args.limit is not None and parsed_args.limit < 0: raise exc.CommandError( _('Expected non-negative --limit, got %s') % parsed_args.limit) params['limit'] = parsed_args.limit params['marker'] = parsed_args.marker if parsed_args.address is not None: params['address'] = parsed_args.address if parsed_args.node is not None: params['node'] = parsed_args.node if parsed_args.portgroup is not None: params['portgroup'] = parsed_args.portgroup if parsed_args.detail: params['detail'] = parsed_args.detail columns = res_fields.PORT_DETAILED_RESOURCE.fields labels = res_fields.PORT_DETAILED_RESOURCE.labels elif parsed_args.fields: params['detail'] = False fields = itertools.chain.from_iterable(parsed_args.fields) resource = res_fields.Resource(list(fields)) columns = resource.fields labels = resource.labels params['fields'] = columns self.log.debug("params(%s)", params) data = client.port.list(**params) data = oscutils.sort_items(data, parsed_args.sort) return (labels, (oscutils.get_item_properties(s, columns, formatters={ 'extra': oscutils.format_dict},) for s in data)) python-ironicclient-2.2.0/ironicclient/osc/v1/__init__.py0000666000175100017510000000000013232474343023442 0ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/osc/v1/baremetal_driver.py0000666000175100017510000001760613232474343025236 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import logging from osc_lib.command import command from osc_lib import utils as oscutils from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient.v1 import resource_fields as res_fields from ironicclient.v1 import utils as v1_utils class ListBaremetalDriver(command.Lister): """List the enabled drivers.""" log = logging.getLogger(__name__ + ".ListBaremetalDriver") def get_parser(self, prog_name): parser = super(ListBaremetalDriver, self).get_parser(prog_name) parser.add_argument( '--type', metavar='', choices=["classic", "dynamic"], help='Type of driver ("classic" or "dynamic"). ' 'The default is to list all of them.' ) parser.add_argument( '--long', action='store_true', default=None, help="Show detailed information about the drivers.") return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) client = self.app.client_manager.baremetal params = {'driver_type': parsed_args.type, 'detail': parsed_args.long} if parsed_args.long: labels = res_fields.DRIVER_DETAILED_RESOURCE.labels columns = res_fields.DRIVER_DETAILED_RESOURCE.fields else: labels = res_fields.DRIVER_RESOURCE.labels columns = res_fields.DRIVER_RESOURCE.fields drivers = client.driver.list(**params) drivers = oscutils.sort_items(drivers, 'name') # For list-type properties, show the values as comma separated # strings. It's easier to read. data = [utils.convert_list_props_to_comma_separated(d._info) for d in drivers] return (labels, (oscutils.get_dict_properties(s, columns) for s in data)) class ListBaremetalDriverProperty(command.Lister): """List the driver properties.""" log = logging.getLogger(__name__ + ".ListBaremetalDriverProperty") def get_parser(self, prog_name): parser = super(ListBaremetalDriverProperty, self).get_parser(prog_name) parser.add_argument( 'driver', metavar='', help='Name of the driver.') return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal driver_properties = baremetal_client.driver.properties( parsed_args.driver) labels = ['Property', 'Description'] return labels, sorted(driver_properties.items()) class ListBaremetalDriverRaidProperty(command.Lister): """List a driver's RAID logical disk properties.""" log = logging.getLogger(__name__ + ".ListBaremetalDriverRaidProperty") def get_parser(self, prog_name): parser = super(ListBaremetalDriverRaidProperty, self).get_parser( prog_name) parser.add_argument( 'driver', metavar='', help='Name of the driver.') return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal raid_props = baremetal_client.driver.raid_logical_disk_properties( parsed_args.driver) labels = ['Property', 'Description'] return labels, sorted(raid_props.items()) class PassthruCallBaremetalDriver(command.ShowOne): """Call a vendor passthru method for a driver.""" log = logging.getLogger(__name__ + ".PassthruCallBaremetalDriver") def get_parser(self, prog_name): parser = super(PassthruCallBaremetalDriver, self).get_parser(prog_name) parser.add_argument( 'driver', metavar='', help=_('Name of the driver.') ) parser.add_argument( 'method', metavar='', help=_("Vendor passthru method to be called.") ) parser.add_argument( '--arg', metavar='', action='append', help=_("Argument to pass to the passthru method (repeat option " "to specify multiple arguments).") ) parser.add_argument( '--http-method', dest='http_method', metavar='', choices=v1_utils.HTTP_METHODS, default='POST', help=_("The HTTP method to use in the passthru request. One of " "%s. Defaults to 'POST'.") % oscutils.format_list(v1_utils.HTTP_METHODS) ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal arguments = utils.key_value_pairs_to_dict(parsed_args.arg) response = (baremetal_client.driver. vendor_passthru(parsed_args.driver, parsed_args.method, http_method=parsed_args.http_method, args=arguments)) return self.dict2columns(response) class PassthruListBaremetalDriver(command.Lister): """List available vendor passthru methods for a driver.""" log = logging.getLogger(__name__ + ".PassthruListBaremetalDriver") def get_parser(self, prog_name): parser = super(PassthruListBaremetalDriver, self).get_parser(prog_name) parser.add_argument( 'driver', metavar='', help=_('Name of the driver.')) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal columns = res_fields.VENDOR_PASSTHRU_METHOD_RESOURCE.fields labels = res_fields.VENDOR_PASSTHRU_METHOD_RESOURCE.labels methods = baremetal_client.driver.get_vendor_passthru_methods( parsed_args.driver) params = [] for method, response in methods.items(): response['name'] = method http_methods = ', '.join(response['http_methods']) response['http_methods'] = http_methods params.append(response) return (labels, (oscutils.get_dict_properties(s, columns) for s in params)) class ShowBaremetalDriver(command.ShowOne): """Show information about a driver.""" log = logging.getLogger(__name__ + ".ShowBaremetalDriver") def get_parser(self, prog_name): parser = super(ShowBaremetalDriver, self).get_parser(prog_name) parser.add_argument( 'driver', metavar='', help=_('Name of the driver.')) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) baremetal_client = self.app.client_manager.baremetal driver = baremetal_client.driver.get(parsed_args.driver)._info driver.pop("links", None) driver.pop("properties", None) # For list-type properties, show the values as comma separated # strings. It's easier to read. driver = utils.convert_list_props_to_comma_separated(driver) return zip(*sorted(driver.items())) python-ironicclient-2.2.0/ironicclient/osc/plugin.py0000666000175100017510000001160613232474343022671 0ustar zuulzuul00000000000000# # Copyright 2015 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # """OpenStackClient plugin for Bare Metal service.""" import argparse import logging from ironicclient.common import http from osc_lib import utils LOG = logging.getLogger(__name__) CLIENT_CLASS = 'ironicclient.v1.client.Client' API_VERSION_OPTION = 'os_baremetal_api_version' API_NAME = 'baremetal' # NOTE(TheJulia) Latest known version tracking has been moved # to the ironicclient/common/http.py file as the OSC committment # is latest known, and we should only store it in one location. LAST_KNOWN_API_VERSION = http.LAST_KNOWN_API_VERSION LATEST_VERSION = http.LATEST_VERSION API_VERSIONS = { '1.%d' % i: CLIENT_CLASS for i in range(1, LAST_KNOWN_API_VERSION + 1) } API_VERSIONS['1'] = CLIENT_CLASS # NOTE(dtantsur): flag to indicate that the requested version was "latest". # Due to how OSC works we cannot just add "latest" to the list of supported # versions - it breaks the major version detection. OS_BAREMETAL_API_LATEST = True def make_client(instance): """Returns a baremetal service client.""" requested_api_version = instance._api_version[API_NAME] baremetal_client_class = utils.get_client_class( API_NAME, requested_api_version, API_VERSIONS) LOG.debug('Instantiating baremetal client: %s', baremetal_client_class) LOG.debug('Baremetal API version: %s', requested_api_version if not OS_BAREMETAL_API_LATEST else "latest") if requested_api_version == '1': # NOTE(dtantsur): '1' means 'the latest v1 API version'. Since we don't # have other major versions, it's identical to 'latest'. requested_api_version = LATEST_VERSION allow_api_version_downgrade = True else: allow_api_version_downgrade = OS_BAREMETAL_API_LATEST client = baremetal_client_class( os_ironic_api_version=requested_api_version, # NOTE(dtantsur): enable re-negotiation of the latest version, if CLI # latest is too high for the server we're talking to. allow_api_version_downgrade=allow_api_version_downgrade, session=instance.session, region_name=instance._region_name, # NOTE(vdrok): This will be set as endpoint_override, and the Client # class will be able to do the version stripping if needed endpoint=instance.get_endpoint_for_service_type( API_NAME, interface=instance.interface, region_name=instance._region_name ) ) return client def build_option_parser(parser): """Hook to add global options.""" parser.add_argument( '--os-baremetal-api-version', metavar='', default=_get_environment_version("latest"), choices=sorted( API_VERSIONS, key=lambda k: [int(x) for x in k.split('.')]) + ['latest'], action=ReplaceLatestVersion, help='Bare metal API version, default="latest" (the maximum version ' 'supported by both the client and the server). ' '(Env: OS_BAREMETAL_API_VERSION)', ) return parser def _get_environment_version(default): global OS_BAREMETAL_API_LATEST env_value = utils.env('OS_BAREMETAL_API_VERSION') if not env_value: env_value = default if env_value == 'latest': env_value = LATEST_VERSION else: OS_BAREMETAL_API_LATEST = False return env_value class ReplaceLatestVersion(argparse.Action): """Replaces `latest` keyword by last known version. OSC cannot accept the literal "latest" as a supported API version as it breaks the major version detection (OSC tries to load configuration options from setuptools entrypoint openstack.baremetal.vlatest). This action replaces "latest" with the latest known version, and sets the global OS_BAREMETAL_API_LATEST flag appropriately. """ def __call__(self, parser, namespace, values, option_string=None): global OS_BAREMETAL_API_LATEST if values == 'latest': values = LATEST_VERSION # The default value of "True" may have been overriden due to # non-empty OS_BAREMETAL_API_VERSION env variable. If a user # explicitly requests "latest", we need to correct it. OS_BAREMETAL_API_LATEST = True else: OS_BAREMETAL_API_LATEST = False setattr(namespace, self.dest, values) python-ironicclient-2.2.0/ironicclient/osc/__init__.py0000666000175100017510000000000013232474343023114 0ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/client.py0000666000175100017510000001574713232474343022077 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 keystoneauth1 import loading as kaloading from oslo_utils import importutils from ironicclient.common.i18n import _ from ironicclient import exc def get_client(api_version, os_auth_token=None, ironic_url=None, os_username=None, os_password=None, os_auth_url=None, os_project_id=None, os_project_name=None, os_tenant_id=None, os_tenant_name=None, os_region_name=None, os_user_domain_id=None, os_user_domain_name=None, os_project_domain_id=None, os_project_domain_name=None, os_service_type=None, os_endpoint_type=None, insecure=None, timeout=None, os_cacert=None, ca_file=None, os_cert=None, cert_file=None, os_key=None, key_file=None, os_ironic_api_version=None, max_retries=None, retry_interval=None, session=None, **ignored_kwargs): """Get an authenticated client, based on the credentials. :param api_version: the API version to use. Valid value: '1'. :param os_auth_token: pre-existing token to re-use :param ironic_url: ironic API endpoint :param os_username: name of a user :param os_password: user's password :param os_auth_url: endpoint to authenticate against :param os_tenant_name: name of a tenant (deprecated in favour of os_project_name) :param os_tenant_id: ID of a tenant (deprecated in favour of os_project_id) :param os_project_name: name of a project :param os_project_id: ID of a project :param os_region_name: name of a keystone region :param os_user_domain_name: name of a domain the user belongs to :param os_user_domain_id: ID of a domain the user belongs to :param os_project_domain_name: name of a domain the project belongs to :param os_project_domain_id: ID of a domain the project belongs to :param os_service_type: the type of service to lookup the endpoint for :param os_endpoint_type: the type (exposure) of the endpoint :param insecure: allow insecure SSL (no cert verification) :param timeout: allows customization of the timeout for client HTTP requests :param os_cacert: path to cacert file :param ca_file: path to cacert file, deprecated in favour of os_cacert :param os_cert: path to cert file :param cert_file: path to cert file, deprecated in favour of os_cert :param os_key: path to key file :param key_file: path to key file, deprecated in favour of os_key :param os_ironic_api_version: ironic API version to use or a list of available API versions to attempt to negotiate. :param max_retries: Maximum number of retries in case of conflict error :param retry_interval: Amount of time (in seconds) between retries in case of conflict error :param session: Keystone session to use :param ignored_kwargs: all the other params that are passed. Left for backwards compatibility. They are ignored. """ # TODO(TheJulia): At some point, we should consider possibly noting # the "latest" flag for os_ironic_api_version to cause the client to # auto-negotiate to the greatest available version, however we do not # have the ability yet for a caller to cap the version, and will hold # off doing so until then. os_service_type = os_service_type or 'baremetal' os_endpoint_type = os_endpoint_type or 'publicURL' project_id = (os_project_id or os_tenant_id) project_name = (os_project_name or os_tenant_name) kwargs = { 'os_ironic_api_version': os_ironic_api_version, 'max_retries': max_retries, 'retry_interval': retry_interval, } endpoint = ironic_url cacert = os_cacert or ca_file cert = os_cert or cert_file key = os_key or key_file if os_auth_token and endpoint: kwargs.update({ 'token': os_auth_token, 'insecure': insecure, 'ca_file': cacert, 'cert_file': cert, 'key_file': key, 'timeout': timeout, }) elif os_auth_url: auth_type = 'password' auth_kwargs = { 'auth_url': os_auth_url, 'project_id': project_id, 'project_name': project_name, 'user_domain_id': os_user_domain_id, 'user_domain_name': os_user_domain_name, 'project_domain_id': os_project_domain_id, 'project_domain_name': os_project_domain_name, } if os_username and os_password: auth_kwargs.update({ 'username': os_username, 'password': os_password, }) elif os_auth_token: auth_type = 'token' auth_kwargs.update({ 'token': os_auth_token, }) # Create new session only if it was not passed in if not session: loader = kaloading.get_plugin_loader(auth_type) auth_plugin = loader.load_from_options(**auth_kwargs) # Let keystoneauth do the necessary parameter conversions session = kaloading.session.Session().load_from_options( auth=auth_plugin, insecure=insecure, cacert=cacert, cert=cert, key=key, timeout=timeout, ) exception_msg = _('Must provide Keystone credentials or user-defined ' 'endpoint and token') if not endpoint: if session: try: # Pass the endpoint, it will be used to get hostname # and port that will be used for API version caching. It will # be also set as endpoint_override. endpoint = session.get_endpoint( service_type=os_service_type, interface=os_endpoint_type, region_name=os_region_name ) except Exception as e: raise exc.AmbiguousAuthSystem( _('%(message)s, error was: %(error)s') % {'message': exception_msg, 'error': e}) else: # Neither session, nor valid auth parameters provided raise exc.AmbiguousAuthSystem(exception_msg) # Always pass the session kwargs['session'] = session return Client(api_version, endpoint, **kwargs) def Client(version, *args, **kwargs): module = importutils.import_versioned_module('ironicclient', version, 'client') client_class = getattr(module, 'Client') return client_class(*args, **kwargs) python-ironicclient-2.2.0/ironicclient/exc.py0000666000175100017510000000503013232474343021360 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 ironicclient.common.apiclient import exceptions from ironicclient.common.apiclient.exceptions import * # noqa # NOTE(akurilin): This alias is left here since v.0.1.3 to support backwards # compatibility. InvalidEndpoint = EndpointException CommunicationError = ConnectionRefused HTTPBadRequest = BadRequest HTTPInternalServerError = InternalServerError HTTPNotFound = NotFound HTTPServiceUnavailable = ServiceUnavailable class AmbiguousAuthSystem(ClientException): """Could not obtain token and endpoint using provided credentials.""" pass # Alias for backwards compatibility AmbigiousAuthSystem = AmbiguousAuthSystem class InvalidAttribute(ClientException): pass class StateTransitionFailed(ClientException): """Failed to reach a requested provision state.""" class StateTransitionTimeout(ClientException): """Timed out while waiting for a requested provision state.""" def from_response(response, message=None, traceback=None, method=None, url=None): """Return an HttpError instance based on response from httplib/requests.""" error_body = {} if message: error_body['message'] = message if traceback: error_body['details'] = traceback if hasattr(response, 'status') and not hasattr(response, 'status_code'): # NOTE(akurilin): These modifications around response object give # ability to get all necessary information in method `from_response` # from common code, which expecting response object from `requests` # library instead of object from `httplib/httplib2` library. response.status_code = response.status response.headers = { 'Content-Type': response.getheader('content-type', "")} if hasattr(response, 'status_code'): # NOTE(jiangfei): These modifications allow SessionClient # to handle faultstring. response.json = lambda: {'error': error_body} return exceptions.from_response(response, method=method, url=url) python-ironicclient-2.2.0/ironicclient/shell.py0000666000175100017510000004541413232474343021722 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. """ Command-line interface to the OpenStack Bare Metal Provisioning API. """ from __future__ import print_function import argparse import getpass import logging import os import pkgutil import re import sys from keystoneauth1.loading import session as kasession from oslo_utils import encodeutils from oslo_utils import importutils import six import ironicclient from ironicclient.common.apiclient import exceptions from ironicclient.common import cliutils from ironicclient.common import http from ironicclient.common.i18n import _ from ironicclient.common import utils from ironicclient import exc LAST_KNOWN_API_VERSION = 34 LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION) class IronicShell(object): def get_base_parser(self): parser = argparse.ArgumentParser( prog='ironic', description=__doc__.strip(), epilog=_('See "ironic help COMMAND" ' 'for help on a specific command.'), add_help=False, formatter_class=HelpFormatter, ) # Register global Keystone args first so their defaults are respected. # See https://bugs.launchpad.net/python-ironicclient/+bug/1463581 kasession.register_argparse_arguments(parser) # Global arguments parser.add_argument('-h', '--help', action='store_true', help=argparse.SUPPRESS, ) parser.add_argument('--version', action='version', version=ironicclient.__version__) parser.add_argument('--debug', default=bool(cliutils.env('IRONICCLIENT_DEBUG')), action='store_true', help=_('Defaults to env[IRONICCLIENT_DEBUG]')) parser.add_argument('--json', default=False, action='store_true', help=_('Print JSON response without formatting.')) parser.add_argument('-v', '--verbose', default=False, action="store_true", help=_('Print more verbose output')) # for backward compatibility only parser.add_argument('--cert-file', dest='os_cert', help=_('DEPRECATED! Use --os-cert.')) # for backward compatibility only parser.add_argument('--key-file', dest='os_key', help=_('DEPRECATED! Use --os-key.')) # for backward compatibility only parser.add_argument('--ca-file', dest='os_cacert', help=_('DEPRECATED! Use --os-cacert.')) parser.add_argument('--os-username', default=cliutils.env('OS_USERNAME'), help=_('Defaults to env[OS_USERNAME]')) parser.add_argument('--os_username', help=argparse.SUPPRESS) parser.add_argument('--os-password', default=cliutils.env('OS_PASSWORD'), help=_('Defaults to env[OS_PASSWORD]')) parser.add_argument('--os_password', help=argparse.SUPPRESS) parser.add_argument('--os-tenant-id', default=cliutils.env('OS_TENANT_ID'), help=_('Defaults to env[OS_TENANT_ID]')) parser.add_argument('--os_tenant_id', help=argparse.SUPPRESS) parser.add_argument('--os-tenant-name', default=cliutils.env('OS_TENANT_NAME'), help=_('Defaults to env[OS_TENANT_NAME]')) parser.add_argument('--os_tenant_name', help=argparse.SUPPRESS) parser.add_argument('--os-auth-url', default=cliutils.env('OS_AUTH_URL'), help=_('Defaults to env[OS_AUTH_URL]')) parser.add_argument('--os_auth_url', help=argparse.SUPPRESS) parser.add_argument('--os-region-name', default=cliutils.env('OS_REGION_NAME'), help=_('Defaults to env[OS_REGION_NAME]')) parser.add_argument('--os_region_name', help=argparse.SUPPRESS) parser.add_argument('--os-auth-token', default=cliutils.env('OS_AUTH_TOKEN'), help=_('Defaults to env[OS_AUTH_TOKEN]')) parser.add_argument('--os_auth_token', help=argparse.SUPPRESS) parser.add_argument('--ironic-url', default=cliutils.env('IRONIC_URL'), help=_('Defaults to env[IRONIC_URL]')) parser.add_argument('--ironic_url', help=argparse.SUPPRESS) parser.add_argument('--ironic-api-version', default=cliutils.env('IRONIC_API_VERSION', default="latest"), help=_('Accepts 1.x (where "x" is microversion), ' '1 or "latest". Defaults to ' 'env[IRONIC_API_VERSION] or "latest".')) parser.add_argument('--ironic_api_version', help=argparse.SUPPRESS) parser.add_argument('--os-service-type', default=cliutils.env('OS_SERVICE_TYPE'), help=_('Defaults to env[OS_SERVICE_TYPE] or ' '"baremetal"')) parser.add_argument('--os_service_type', help=argparse.SUPPRESS) parser.add_argument('--os-endpoint', dest='ironic_url', default=cliutils.env('OS_SERVICE_ENDPOINT'), help=_('Specify an endpoint to use instead of ' 'retrieving one from the service catalog ' '(via authentication). ' 'Defaults to env[OS_SERVICE_ENDPOINT].')) parser.add_argument('--os_endpoint', dest='ironic_url', help=argparse.SUPPRESS) parser.add_argument('--os-endpoint-type', default=cliutils.env('OS_ENDPOINT_TYPE'), help=_('Defaults to env[OS_ENDPOINT_TYPE] or ' '"publicURL"')) parser.add_argument('--os_endpoint_type', help=argparse.SUPPRESS) parser.add_argument('--os-user-domain-id', default=cliutils.env('OS_USER_DOMAIN_ID'), help=_('Defaults to env[OS_USER_DOMAIN_ID].')) parser.add_argument('--os-user-domain-name', default=cliutils.env('OS_USER_DOMAIN_NAME'), help=_('Defaults to env[OS_USER_DOMAIN_NAME].')) parser.add_argument('--os-project-id', default=cliutils.env('OS_PROJECT_ID'), help=_('Another way to specify tenant ID. ' 'This option is mutually exclusive with ' ' --os-tenant-id. ' 'Defaults to env[OS_PROJECT_ID].')) parser.add_argument('--os-project-name', default=cliutils.env('OS_PROJECT_NAME'), help=_('Another way to specify tenant name. ' 'This option is mutually exclusive with ' ' --os-tenant-name. ' 'Defaults to env[OS_PROJECT_NAME].')) parser.add_argument('--os-project-domain-id', default=cliutils.env('OS_PROJECT_DOMAIN_ID'), help=_('Defaults to env[OS_PROJECT_DOMAIN_ID].')) parser.add_argument('--os-project-domain-name', default=cliutils.env('OS_PROJECT_DOMAIN_NAME'), help=_('Defaults to env[OS_PROJECT_DOMAIN_NAME].')) msg = _('Maximum number of retries in case of conflict error ' '(HTTP 409). Defaults to env[IRONIC_MAX_RETRIES] or %d. ' 'Use 0 to disable retrying.') % http.DEFAULT_MAX_RETRIES parser.add_argument('--max-retries', type=int, help=msg, default=cliutils.env( 'IRONIC_MAX_RETRIES', default=str(http.DEFAULT_MAX_RETRIES))) msg = _('Amount of time (in seconds) between retries ' 'in case of conflict error (HTTP 409). ' 'Defaults to env[IRONIC_RETRY_INTERVAL] ' 'or %d.') % http.DEFAULT_RETRY_INTERVAL parser.add_argument('--retry-interval', type=int, help=msg, default=cliutils.env( 'IRONIC_RETRY_INTERVAL', default=str(http.DEFAULT_RETRY_INTERVAL))) return parser def get_available_major_versions(self): matcher = re.compile(r"^v[0-9]+$") submodules = pkgutil.iter_modules([os.path.dirname(__file__)]) available_versions = [name[1:] for loader, name, ispkg in submodules if matcher.search(name)] return available_versions def get_subcommand_parser(self, version): parser = self.get_base_parser() self.subcommands = {} subparsers = parser.add_subparsers(metavar='', dest='subparser_name') try: submodule = importutils.import_versioned_module('ironicclient', version, 'shell') except ImportError as e: msg = _("Invalid client version '%(version)s'. " "Major part must be one of: '%(major)s'") % { "version": version, "major": ", ".join(self.get_available_major_versions())} raise exceptions.UnsupportedVersion( _('%(message)s, error was: %(error)s') % {'message': msg, 'error': e}) submodule.enhance_parser(parser, subparsers, self.subcommands) utils.define_commands_from_module(subparsers, self, self.subcommands) return parser def _setup_debugging(self, debug): if debug: logging.basicConfig( format="%(levelname)s (%(module)s:%(lineno)d) %(message)s", level=logging.DEBUG) else: logging.basicConfig( format="%(levelname)s %(message)s", level=logging.CRITICAL) def do_bash_completion(self): """Prints all of the commands and options for bash-completion.""" commands = set() options = set() for sc_str, sc in self.subcommands.items(): commands.add(sc_str) for option in sc._optionals._option_string_actions.keys(): options.add(option) commands.remove('bash-completion') print(' '.join(commands | options)) def _check_version(self, api_version): """Validate the supplied API (micro)version. :param api_version: API version as a string ("1", "1.x" or "latest") :returns: tuple (major version, version string) """ if api_version in ('1', 'latest'): return (1, LATEST_VERSION) else: try: versions = tuple(int(i) for i in api_version.split('.')) except ValueError: versions = () if not versions or len(versions) > 2: msg = _("The requested API version %(ver)s is an unexpected " "format. Acceptable formats are 'X', 'X.Y', or the " "literal string 'latest'." ) % {'ver': api_version} raise exc.CommandError(msg) if versions == (1, 0): os_ironic_api_version = None else: os_ironic_api_version = api_version api_major_version = versions[0] return (api_major_version, os_ironic_api_version) def main(self, argv): # TODO(rloo): delete the ironic CLI in the S* cycle. print('The "ironic" CLI is deprecated and will be removed in the ' 'S* release. Please use the "openstack baremetal" CLI instead.', file=sys.stderr) # Parse args once to find version parser = self.get_base_parser() (options, args) = parser.parse_known_args(argv) self._setup_debugging(options.debug) # build available subcommands based on version (api_major_version, os_ironic_api_version) = ( self._check_version(options.ironic_api_version)) subcommand_parser = self.get_subcommand_parser(api_major_version) self.parser = subcommand_parser # Handle top-level --help/-h before attempting to parse # a command off the command line if options.help or not argv: self.do_help(options) return 0 # Parse args again and call whatever callback was selected args = subcommand_parser.parse_args(argv) # Short-circuit and deal with these commands right away. if args.func == self.do_help: self.do_help(args) return 0 elif args.func == self.do_bash_completion: self.do_bash_completion() return 0 if not (args.os_auth_token and (args.ironic_url or args.os_auth_url)): if not args.os_username: raise exc.CommandError(_("You must provide a username via " "either --os-username or via " "env[OS_USERNAME]")) if not args.os_password: # No password, If we've got a tty, try prompting for it if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty(): # Check for Ctl-D try: args.os_password = getpass.getpass( 'OpenStack Password: ') except EOFError: pass # No password because we didn't have a tty or the # user Ctl-D when prompted. if not args.os_password: raise exc.CommandError(_("You must provide a password via " "either --os-password, " "env[OS_PASSWORD], " "or prompted response")) if not (args.os_tenant_id or args.os_tenant_name or args.os_project_id or args.os_project_name): raise exc.CommandError( _("You must provide a project name or" " project id via --os-project-name, --os-project-id," " env[OS_PROJECT_ID] or env[OS_PROJECT_NAME]. You may" " use os-project and os-tenant interchangeably.")) if not args.os_auth_url: raise exc.CommandError(_("You must provide an auth url via " "either --os-auth-url or via " "env[OS_AUTH_URL]")) if args.max_retries < 0: raise exc.CommandError(_("You must provide value >= 0 for " "--max-retries")) if args.retry_interval < 1: raise exc.CommandError(_("You must provide value >= 1 for " "--retry-interval")) client_args = ( 'os_auth_token', 'ironic_url', 'os_username', 'os_password', 'os_auth_url', 'os_project_id', 'os_project_name', 'os_tenant_id', 'os_tenant_name', 'os_region_name', 'os_user_domain_id', 'os_user_domain_name', 'os_project_domain_id', 'os_project_domain_name', 'os_service_type', 'os_endpoint_type', 'os_cacert', 'os_cert', 'os_key', 'max_retries', 'retry_interval', 'timeout', 'insecure' ) kwargs = {} for key in client_args: kwargs[key] = getattr(args, key) kwargs['os_ironic_api_version'] = os_ironic_api_version client = ironicclient.client.get_client(api_major_version, **kwargs) if options.ironic_api_version in ('1', 'latest'): # Allow negotiating a lower version, if the latest version # supported by the client is higher than the latest version # supported by the server. client.http_client.api_version_select_state = 'default' try: args.func(client, args) except exc.Unauthorized: raise exc.CommandError(_("Invalid OpenStack Identity credentials")) except exc.CommandError as e: subcommand_parser = self.subcommands[args.subparser_name] subcommand_parser.error(e) @cliutils.arg('command', metavar='', nargs='?', help=_('Display help for ')) def do_help(self, args): """Display help about this program or one of its subcommands.""" if getattr(args, 'command', None): if args.command in self.subcommands: self.subcommands[args.command].print_help() else: raise exc.CommandError(_("'%s' is not a valid subcommand") % args.command) else: self.parser.print_help() class HelpFormatter(argparse.HelpFormatter): def start_section(self, heading): super(HelpFormatter, self).start_section(heading.capitalize()) def main(): try: IronicShell().main(sys.argv[1:]) except KeyboardInterrupt: print(_("... terminating ironic client"), file=sys.stderr) return 130 except Exception as e: print(encodeutils.safe_encode(six.text_type(e)), file=sys.stderr) return 1 if __name__ == "__main__": sys.exit(main()) python-ironicclient-2.2.0/ironicclient/tests/0000775000175100017510000000000013232474761021375 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/tests/functional/0000775000175100017510000000000013232474761023537 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/tests/functional/utils.py0000666000175100017510000000216113232474343025247 0ustar zuulzuul00000000000000# Copyright (c) 2015 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import six def get_dict_from_output(output): """Parse list of dictionaries, return a dictionary. :param output: list of dictionaries """ obj = {} for item in output: obj[item['Property']] = six.text_type(item['Value']) return obj def get_object(object_list, object_value): """Get Ironic object by value from list of Ironic objects. :param object_list: the output of the cmd :param object_value: value to get """ for obj in object_list: if object_value in obj.values(): return obj python-ironicclient-2.2.0/ironicclient/tests/functional/test_driver.py0000666000175100017510000000364413232474343026450 0ustar zuulzuul00000000000000# Copyright (c) 2015 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ironicclient.tests.functional import base class DriverSanityTestIronicClient(base.FunctionalTestBase): """Sanity tests for testing actions with driver. Smoke test for the Ironic CLI commands which checks basic actions with driver command like driver-show, driver-properties. """ def test_driver_show(self): """Test steps: 1) get drivers names 2) check that each driver exists in driver-show output """ drivers_names = self.get_drivers_names() for driver in drivers_names: driver_show = self.show_driver(driver) self.assertEqual(driver, driver_show['name']) def test_driver_properties(self): """Test steps: 1) get drivers names 2) check that each driver has some properties """ drivers_names = self.get_drivers_names() for driver in drivers_names: driver_properties = self.properties_driver(driver) self.assertNotEqual([], [x['Property'] for x in driver_properties]) def test_driver_list(self): """Test steps: 1) get list of drivers 2) check that list of drivers is not empty """ driver = 'fake' available_drivers = self.get_drivers_names() self.assertGreater(len(available_drivers), 0) self.assertIn(driver, available_drivers) python-ironicclient-2.2.0/ironicclient/tests/functional/test_chassis_create.py0000666000175100017510000001360313232474343030131 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import six from tempest.lib.common.utils import data_utils from tempest.lib import exceptions from ironicclient.tests.functional import base class NegativeChassisCreateTestsIronicClient(base.FunctionalTestBase): """Negative tests for testing chassis-create command. Negative tests for the Ironic CLI commands which check actions with chassis-create command like create chassis without arguments or with incorrect arguments and check that correct error message raised. """ error_msg = r'ironic chassis-create: error:' expected_msg = r'expected one argument' def test_description_no_value(self): """Test steps: 1) create chassis using -d argument without the value 2) create chassis using --description argument without the value 3) check that command using -d argument triggers an exception 4) check that command with --description arg triggers an exception """ ex_text = (r'{0} argument -d/--description: {1}' .format(self.error_msg, self.expected_msg)) six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.create_chassis, '-d') six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.create_chassis, '--description') def test_metadata_extra_no_value(self): """Test steps: 1) create chassis using -e argument without the value 2) create chassis using --extra argument without the value 3) check that command using -e argument triggers an exception 4) check that command with --extra argument triggers an exception """ ex_text = (r'{0} argument -e/--extra: {1}' .format(self.error_msg, self.expected_msg)) six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.create_chassis, '-e') six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.create_chassis, '--extra') def test_specific_uuid_no_value(self): """Test steps: 1) create chassis using -u argument without the value 2) create chassis using --uuid argument without the value 3) check that command using -u argument triggers an exception 4) check that command with --uuid argument triggers an exception """ ex_text = (r'{0} argument -u/--uuid: {1}' .format(self.error_msg, self.expected_msg)) six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.create_chassis, '-u') six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.create_chassis, '--uuid') def test_invalid_description(self): """Test steps: 1) create chassis with invalid description using -d argument 2) create chassis with invalid description using --description arg 3) check that command using -d argument triggers an exception 4) check that command using --uuid argument triggers an exception """ description = '--' ex_text = (r'{0} argument -d/--description: {1}' .format(self.error_msg, self.expected_msg)) six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.create_chassis, params='-d {0}'.format(description)) six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.create_chassis, params='--description {0}'.format(description)) def test_invalid_metadata_extra(self): """Test steps: 1) create chassis with invalid metadata using -e argument 2) create chassis with invalid metadata using --extra argument 3) check that command using -e argument triggers an exception 4) check that command using --extra argument triggers an exception """ extra = "HelloWorld" ex_text = (r'{0} Attributes must be a list of PATH=VALUE' .format(self.error_msg)) six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.create_chassis, params='-e {0}'.format(extra)) six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.create_chassis, params='--extra {0}'.format(extra)) def test_invalid_specific_uuid(self): """Test steps: 1) create chassis with invalid specific uuid using -u argument 2) create chassis with invalid specific uuid using --uuid argument 3) check that command using -u argument triggers an exception 4) check that command using --uuid argument triggers an exception """ invalid_uuid = data_utils.rand_uuid()[:-1] ex_text = r'Expected a UUID but received {0}'.format(invalid_uuid) six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.create_chassis, params='-u {0}'.format(invalid_uuid)) six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.create_chassis, params='--uuid {0}'.format(invalid_uuid)) python-ironicclient-2.2.0/ironicclient/tests/functional/osc/0000775000175100017510000000000013232474761024323 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/tests/functional/osc/v1/0000775000175100017510000000000013232474761024651 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/tests/functional/osc/v1/test_baremetal_node_negative.py0000666000175100017510000000651213232474343033107 0ustar zuulzuul00000000000000# Copyright (c) 2017 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import ddt import six from tempest.lib import exceptions from ironicclient.tests.functional.osc.v1 import base @ddt.ddt class BaremetalNodeNegativeTests(base.TestCase): """Negative tests for baremetal node commands.""" def setUp(self): super(BaremetalNodeNegativeTests, self).setUp() self.node = self.node_create() @ddt.data( ('', '', 'error: argument --driver is required'), ('--driver', 'wrongdriver', 'No valid host was found. Reason: No conductor service ' 'registered which supports driver wrongdriver.') ) @ddt.unpack def test_create_driver(self, argument, value, ex_text): """Negative test for baremetal node driver options.""" base_cmd = 'baremetal node create' command = self.construct_cmd(base_cmd, argument, value) six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.openstack, command) def test_delete_no_node(self): """Test for baremetal node delete without node specified.""" command = 'baremetal node delete' ex_text = 'error: too few arguments' six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.openstack, command) def test_list_wrong_argument(self): """Test for baremetal node list with wrong argument.""" command = 'baremetal node list --wrong_arg' ex_text = 'error: unrecognized arguments: --wrong_arg' six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.openstack, command) @ddt.data( ('--property', '', 'error: too few arguments'), ('--property', 'prop', 'Attributes must be a list of PATH=VALUE') ) @ddt.unpack def test_set_property(self, argument, value, ex_text): """Negative test for baremetal node set command options.""" base_cmd = 'baremetal node set' command = self.construct_cmd(base_cmd, argument, value, self.node['uuid']) six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.openstack, command) @ddt.data( ('--property', '', 'error: too few arguments'), ('--property', 'prop', "Reason: can't remove non-existent object") ) @ddt.unpack def test_unset_property(self, argument, value, ex_text): """Negative test for baremetal node unset command options.""" base_cmd = 'baremetal node unset' command = self.construct_cmd(base_cmd, argument, value, self.node['uuid']) six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.openstack, command) ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000python-ironicclient-2.2.0/ironicclient/tests/functional/osc/v1/test_baremetal_node_create_negative.pypython-ironicclient-2.2.0/ironicclient/tests/functional/osc/v1/test_baremetal_node_create_negative.p0000666000175100017510000000370113232474343034236 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import ddt import six from tempest.lib import exceptions from ironicclient.tests.functional.osc.v1 import base @ddt.ddt class BaremetalNodeCreateNegativeTests(base.TestCase): """Negative tests for node create command.""" def setUp(self): super(BaremetalNodeCreateNegativeTests, self).setUp() @ddt.data( ('--uuid', '', 'expected one argument'), ('--uuid', '!@#$^*&%^', 'Expected a UUID'), ('--uuid', '0000 0000', 'unrecognized arguments'), ('--driver-info', '', 'expected one argument'), ('--driver-info', 'some info', 'unrecognized arguments'), ('--property', '', 'expected one argument'), ('--property', 'some property', 'unrecognized arguments'), ('--extra', '', 'expected one argument'), ('--extra', 'some extra', 'unrecognized arguments'), ('--name', '', 'expected one argument'), ('--name', 'some name', 'unrecognized arguments'), ('--network-interface', '', 'expected one argument'), ('--resource-class', '', 'expected one argument')) @ddt.unpack def test_baremetal_node_create(self, argument, value, ex_text): base_cmd = 'baremetal node create --driver fake' command = self.construct_cmd(base_cmd, argument, value) six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.openstack, command) python-ironicclient-2.2.0/ironicclient/tests/functional/osc/v1/test_baremetal_chassis_basic.py0000666000175100017510000000635413232474343033102 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ironicclient.tests.functional.osc.v1 import base class BaremetalChassisTests(base.TestCase): """Functional tests for baremetal chassis commands.""" def setUp(self): super(BaremetalChassisTests, self).setUp() self.chassis = self.chassis_create() def test_list(self): """Check baremetal chassis list command. Test steps: 1) Create baremetal chassis in setUp. 2) List baremetal chassis. 3) Check chassis description and UUID in chassis list. """ chassis_list = self.chassis_list() self.assertIn(self.chassis['uuid'], [x['UUID'] for x in chassis_list]) self.assertIn(self.chassis['description'], [x['Description'] for x in chassis_list]) def test_show(self): """Check baremetal chassis show command. Test steps: 1) Create baremetal chassis in setUp. 2) Show baremetal chassis. 3) Check chassis in chassis show. """ chassis = self.chassis_show(self.chassis['uuid']) self.assertEqual(self.chassis['uuid'], chassis['uuid']) self.assertEqual(self.chassis['description'], chassis['description']) def test_delete(self): """Check baremetal chassis delete command. Test steps: 1) Create baremetal chassis in setUp. 2) Delete baremetal chassis by UUID. 3) Check that chassis deleted successfully. """ output = self.chassis_delete(self.chassis['uuid']) self.assertIn('Deleted chassis {0}'.format(self.chassis['uuid']), output) self.assertNotIn(self.chassis['uuid'], self.chassis_list(['UUID'])) def test_set_unset_extra(self): """Check baremetal chassis set and unset commands. Test steps: 1) Create baremetal chassis in setUp. 2) Set extra data for chassis. 3) Check that baremetal chassis extra data was set. 4) Unset extra data for chassis. 5) Check that baremetal chassis extra data was unset. """ extra_key = 'ext' extra_value = 'testdata' self.openstack('baremetal chassis set --extra {0}={1} {2}' .format(extra_key, extra_value, self.chassis['uuid'])) show_prop = self.chassis_show(self.chassis['uuid'], ['extra']) self.assertEqual(extra_value, show_prop['extra'][extra_key]) self.openstack('baremetal chassis unset --extra {0} {1}' .format(extra_key, self.chassis['uuid'])) show_prop = self.chassis_show(self.chassis['uuid'], ['extra']) self.assertNotIn(extra_key, show_prop['extra']) python-ironicclient-2.2.0/ironicclient/tests/functional/osc/v1/test_baremetal_driver_basic.py0000666000175100017510000000255613232474343032740 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ironicclient.tests.functional.osc.v1 import base class BaremetalDriverTests(base.TestCase): """Functional tests for baremetal driver commands.""" driver_name = 'fake' def test_show(self): """Show specified driver. Test step: 1) Check output of baremetal driver show command. """ driver = self.driver_show(self.driver_name) self.assertEqual(self.driver_name, driver['name']) def test_list(self): """List available drivers. Test steps: 1) Get list of drivers. 2) Check that list of drivers is not empty. """ drivers = [ driver['Supported driver(s)'] for driver in self.driver_list() ] self.assertIn(self.driver_name, drivers) ././@LongLink0000000000000000000000000000014700000000000011217 Lustar 00000000000000python-ironicclient-2.2.0/ironicclient/tests/functional/osc/v1/test_baremetal_node_provision_states.pypython-ironicclient-2.2.0/ironicclient/tests/functional/osc/v1/test_baremetal_node_provision_states.0000666000175100017510000000744513232474343034355 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ironicclient.tests.functional.osc.v1 import base class ProvisionStateTests(base.TestCase): """Functional tests for baremetal node provision state commands.""" def setUp(self): super(ProvisionStateTests, self).setUp() self.node = self.node_create() def test_deploy_rebuild_undeploy_manage(self): """Deploy, rebuild and undeploy node. Test steps: 1) Create baremetal node in setUp. 2) Check initial "enroll" provision state. 3) Set baremetal node "manage" provision state. 4) Check baremetal node provision_state field value is "manageable". 5) Set baremetal node "provide" provision state. 6) Check baremetal node provision_state field value is "available". 7) Set baremetal node "deploy" provision state. 8) Check baremetal node provision_state field value is "active". 9) Set baremetal node "rebuild" provision state. 10) Check baremetal node provision_state field value is "active". 11) Set baremetal node "undeploy" provision state. 12) Check baremetal node provision_state field value is "available". 13) Set baremetal node "manage" provision state. 14) Check baremetal node provision_state field value is "manageable". 15) Set baremetal node "provide" provision state. 16) Check baremetal node provision_state field value is "available". """ show_prop = self.node_show(self.node['uuid'], ["provision_state"]) self.assertEqual("enroll", show_prop["provision_state"]) # manage self.openstack('baremetal node manage {0}'.format(self.node['uuid'])) show_prop = self.node_show(self.node['uuid'], ["provision_state"]) self.assertEqual("manageable", show_prop["provision_state"]) # provide self.openstack('baremetal node provide {0}'.format(self.node['uuid'])) show_prop = self.node_show(self.node['uuid'], ["provision_state"]) self.assertEqual("available", show_prop["provision_state"]) # deploy self.openstack('baremetal node deploy {0}'.format(self.node['uuid'])) show_prop = self.node_show(self.node['uuid'], ["provision_state"]) self.assertEqual("active", show_prop["provision_state"]) # rebuild self.openstack('baremetal node rebuild {0}'.format(self.node['uuid'])) show_prop = self.node_show(self.node['uuid'], ["provision_state"]) self.assertEqual("active", show_prop["provision_state"]) # undeploy self.openstack('baremetal node undeploy {0}'.format(self.node['uuid'])) show_prop = self.node_show(self.node['uuid'], ["provision_state"]) self.assertEqual("available", show_prop["provision_state"]) # manage self.openstack('baremetal node manage {0}'.format(self.node['uuid'])) show_prop = self.node_show(self.node['uuid'], ["provision_state"]) self.assertEqual("manageable", show_prop["provision_state"]) # provide back self.openstack('baremetal node provide {0}'.format(self.node['uuid'])) show_prop = self.node_show(self.node['uuid'], ["provision_state"]) self.assertEqual("available", show_prop["provision_state"]) python-ironicclient-2.2.0/ironicclient/tests/functional/osc/v1/test_baremetal_node_basic.py0000666000175100017510000002233413232474343032366 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import ddt import json from tempest.lib.common.utils import data_utils from ironicclient.tests.functional.osc.v1 import base @ddt.ddt class BaremetalNodeTests(base.TestCase): """Functional tests for baremetal node commands.""" def setUp(self): super(BaremetalNodeTests, self).setUp() self.node = self.node_create() def test_create_name_uuid(self): """Check baremetal node create command with name and UUID. Test steps: 1) Create baremetal node in setUp. 2) Create one more baremetal node explicitly with specified name and UUID. 3) Check that node successfully created. """ uuid = data_utils.rand_uuid() name = data_utils.rand_name('baremetal-node') node_info = self.node_create(name=name, params='--uuid {0}'.format(uuid)) self.assertEqual(node_info['uuid'], uuid) self.assertEqual(node_info['name'], name) self.assertEqual(node_info['driver'], 'fake') self.assertEqual(node_info['maintenance'], False) self.assertEqual(node_info['provision_state'], 'enroll') node_list = self.node_list() self.assertIn(uuid, [x['UUID'] for x in node_list]) self.assertIn(name, [x['Name'] for x in node_list]) def test_create_old_api_version(self): """Check baremetal node create command with name and UUID. Test steps: 1) Create baremetal node in setUp. 2) Create one more baremetal node explicitly with old API version 3) Check that node successfully created. """ node_info = self.node_create( params='--os-baremetal-api-version 1.5') self.assertEqual(node_info['driver'], 'fake') self.assertEqual(node_info['maintenance'], False) self.assertEqual(node_info['provision_state'], 'available') @ddt.data('name', 'uuid') def test_delete(self, key): """Check baremetal node delete command with name/UUID argument. Test steps: 1) Create baremetal node in setUp. 2) Delete baremetal node by name/UUID. 3) Check that node deleted successfully. """ output = self.node_delete(self.node[key]) self.assertIn('Deleted node {0}'.format(self.node[key]), output) node_list = self.node_list() self.assertNotIn(self.node['name'], [x['Name'] for x in node_list]) self.assertNotIn(self.node['uuid'], [x['UUID'] for x in node_list]) def test_list(self): """Check baremetal node list command. Test steps: 1) Create baremetal node in setUp. 2) List baremetal nodes. 3) Check node name in nodes list. """ node_list = self.node_list() self.assertIn(self.node['name'], [x['Name'] for x in node_list]) self.assertIn(self.node['uuid'], [x['UUID'] for x in node_list]) @ddt.data('name', 'uuid') def test_set(self, key): """Check baremetal node set command calling it by name/UUID. Test steps: 1) Create baremetal node in setUp. 2) Set another name for node calling it by name/UUID. 3) Check that baremetal node name was changed. """ new_name = data_utils.rand_name('newnodename') self.openstack('baremetal node set --name {0} {1}' .format(new_name, self.node[key])) show_prop = self.node_show(self.node['uuid'], ['name']) self.assertEqual(new_name, show_prop['name']) @ddt.data('name', 'uuid') def test_unset(self, key): """Check baremetal node unset command calling it by node name/UUID. Test steps: 1) Create baremetal node in setUp. 2) Unset name of baremetal node calling it by node name/UUID. 3) Check that node has no more name. """ self.openstack('baremetal node unset --name {0}' .format(self.node[key])) show_prop = self.node_show(self.node['uuid'], ['name']) self.assertIsNone(show_prop['name']) @ddt.data('name', 'uuid') def test_show(self, key): """Check baremetal node show command with name and UUID arguments. Test steps: 1) Create baremetal node in setUp. 2) Show baremetal node calling it with name and UUID arguments. 3) Check name, uuid and driver in node show output. """ node = self.node_show(self.node[key], ['name', 'uuid', 'driver']) self.assertEqual(self.node['name'], node['name']) self.assertEqual(self.node['uuid'], node['uuid']) self.assertEqual(self.node['driver'], node['driver']) def test_baremetal_node_maintenance_set_unset(self): """Check baremetal node maintenance set command. Test steps: 1) Create baremetal node in setUp. 2) Check maintenance status of fresh node is False. 3) Set maintenance status for node. 4) Check maintenance status of node is True. 5) Unset maintenance status for node. 6) Check maintenance status of node is False back. """ show_prop = self.node_show(self.node['name'], ['maintenance']) self.assertFalse(show_prop['maintenance']) self.openstack('baremetal node maintenance set {0}'. format(self.node['name'])) show_prop = self.node_show(self.node['name'], ['maintenance']) self.assertTrue(show_prop['maintenance']) self.openstack('baremetal node maintenance unset {0}'. format(self.node['name'])) show_prop = self.node_show(self.node['name'], ['maintenance']) self.assertFalse(show_prop['maintenance']) def test_baremetal_node_maintenance_set_unset_reason(self): """Check baremetal node maintenance set command. Test steps: 1) Create baremetal node in setUp. 2) Check initial maintenance reason is None. 3) Set maintenance status for node with reason. 4) Check maintenance reason of node equals to expected value. Also check maintenance status. 5) Unset maintenance status for node. Recheck maintenance status. 6) Check maintenance reason is None. Recheck maintenance status. """ reason = "Hardware maintenance." show_prop = self.node_show(self.node['name'], ['maintenance_reason', 'maintenance']) self.assertIsNone(show_prop['maintenance_reason']) self.assertFalse(show_prop['maintenance']) self.openstack("baremetal node maintenance set --reason '{0}' {1}". format(reason, self.node['name'])) show_prop = self.node_show(self.node['name'], ['maintenance_reason', 'maintenance']) self.assertEqual(reason, show_prop['maintenance_reason']) self.assertTrue(show_prop['maintenance']) self.openstack('baremetal node maintenance unset {0}'. format(self.node['name'])) show_prop = self.node_show(self.node['name'], ['maintenance_reason', 'maintenance']) self.assertIsNone(show_prop['maintenance_reason']) self.assertFalse(show_prop['maintenance']) @ddt.data( (50, '1'), ('MAX', 'JBOD'), (300, '6+0') ) @ddt.unpack def test_set_unset_target_raid_config(self, size, raid_level): """Set and unset node target RAID config data. Test steps: 1) Create baremetal node in setUp. 2) Set target RAID config data for the node 3) Check target_raid_config of node equals to expected value. 4) Unset target_raid_config data. 5) Check that target_raid_config data is empty. """ min_version = '--os-baremetal-api-version 1.12' argument_json = {"logical_disks": [{"size_gb": size, "raid_level": raid_level}]} argument_string = json.dumps(argument_json) self.openstack("baremetal node set --target-raid-config '{}' {} {}" .format(argument_string, self.node['uuid'], min_version)) show_prop = self.node_show(self.node['uuid'], ['target_raid_config'], min_version) self.assert_dict_is_subset(argument_json, show_prop['target_raid_config']) self.openstack("baremetal node unset --target-raid-config {} {}" .format(self.node['uuid'], min_version)) show_prop = self.node_show(self.node['uuid'], ['target_raid_config'], min_version) self.assertEqual({}, show_prop['target_raid_config']) python-ironicclient-2.2.0/ironicclient/tests/functional/osc/v1/test_baremetal_port_basic.py0000666000175100017510000001130013232474343032414 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ironicclient.tests.functional.osc.v1 import base class BaremetalPortTests(base.TestCase): """Functional tests for baremetal port commands.""" def setUp(self): super(BaremetalPortTests, self).setUp() self.node = self.node_create() self.port = self.port_create(self.node['uuid']) def test_list(self): """Check baremetal port list command. Test steps: 1) Create baremetal port in setUp. 2) List baremetal ports. 3) Check port address and UUID in ports list. """ port_list = self.port_list() self.assertIn(self.port['address'], [port['Address'] for port in port_list]) self.assertIn(self.port['uuid'], [port['UUID'] for port in port_list]) def test_show_uuid(self): """Check baremetal port show command with UUID. Test steps: 1) Create baremetal port in setUp. 2) Show baremetal port calling it by UUID. 3) Check port fields in output. """ port = self.port_show(self.port['uuid']) self.assertEqual(self.port['address'], port['address']) self.assertEqual(self.port['uuid'], port['uuid']) self.assertEqual(self.port['node_uuid'], self.node['uuid']) def test_show_addr(self): """Check baremetal port show command with address. Test steps: 1) Create baremetal port in setUp. 2) Show baremetal port calling it by address. 3) Check port fields in output. """ port = self.port_show( uuid='', params='--address {}'.format(self.port['address'])) self.assertEqual(self.port['address'], port['address']) self.assertEqual(self.port['uuid'], port['uuid']) self.assertEqual(self.port['node_uuid'], self.node['uuid']) def test_delete(self): """Check baremetal port delete command. Test steps: 1) Create baremetal port in setUp. 2) Delete baremetal port by UUID. 3) Check that port deleted successfully and not in list. """ output = self.port_delete(self.port['uuid']) self.assertIn('Deleted port {0}'.format(self.port['uuid']), output) port_list = self.port_list() self.assertNotIn(self.port['address'], [port['Address'] for port in port_list]) self.assertNotIn(self.port['uuid'], [port['UUID'] for port in port_list]) def test_set_unset_extra(self): """Check baremetal port set and unset commands. Test steps: 1) Create baremetal port in setUp. 2) Set extra data for port. 3) Check that baremetal port extra data was set. 4) Unset extra data for port. 5) Check that baremetal port extra data was unset. """ extra_key = 'ext' extra_value = 'testdata' self.openstack('baremetal port set --extra {0}={1} {2}' .format(extra_key, extra_value, self.port['uuid'])) show_prop = self.port_show(self.port['uuid'], ['extra']) self.assertEqual(extra_value, show_prop['extra'][extra_key]) self.openstack('baremetal port unset --extra {0} {1}' .format(extra_key, self.port['uuid'])) show_prop = self.port_show(self.port['uuid'], ['extra']) self.assertNotIn(extra_key, show_prop['extra']) def test_port_create_with_portgroup(self): """Create port with specific port group UUID. Test steps: 1) Create node in setUp(). 2) Create a port group. 3) Create a port with specified port group. 4) Check port properties for portgroup_uuid. """ api_version = ' --os-baremetal-api-version 1.24' port_group = self.port_group_create(self.node['uuid'], params=api_version) port = self.port_create( self.node['uuid'], params='--port-group {0} {1}'.format(port_group['uuid'], api_version)) self.assertEqual(port_group['uuid'], port['portgroup_uuid']) python-ironicclient-2.2.0/ironicclient/tests/functional/osc/v1/test_baremetal_node_power_states.py0000666000175100017510000000453113232474343034023 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ironicclient.tests.functional.osc.v1 import base class PowerStateTests(base.TestCase): """Functional tests for baremetal node power state commands.""" def setUp(self): super(PowerStateTests, self).setUp() self.node = self.node_create() def test_off_reboot_on(self): """Reboot node from Power OFF state. Test steps: 1) Create baremetal node in setUp. 2) Set node Power State OFF as precondition. 3) Call reboot command for baremetal node. 4) Check node Power State ON in node properties. """ self.openstack('baremetal node power off {0}' .format(self.node['uuid'])) show_prop = self.node_show(self.node['uuid'], ['power_state']) self.assertEqual('power off', show_prop['power_state']) self.openstack('baremetal node reboot {0}'.format(self.node['uuid'])) show_prop = self.node_show(self.node['uuid'], ['power_state']) self.assertEqual('power on', show_prop['power_state']) def test_on_reboot_on(self): """Reboot node from Power ON state. Test steps: 1) Create baremetal node in setUp. 2) Set node Power State ON as precondition. 3) Call reboot command for baremetal node. 4) Check node Power State ON in node properties. """ self.openstack('baremetal node power on {0}'.format(self.node['uuid'])) show_prop = self.node_show(self.node['uuid'], ['power_state']) self.assertEqual('power on', show_prop['power_state']) self.openstack('baremetal node reboot {0}'.format(self.node['uuid'])) show_prop = self.node_show(self.node['uuid'], ['power_state']) self.assertEqual('power on', show_prop['power_state']) python-ironicclient-2.2.0/ironicclient/tests/functional/osc/v1/__init__.py0000666000175100017510000000000013232474343026746 0ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/tests/functional/osc/v1/test_baremetal_node_fields.py0000666000175100017510000001155713232474343032560 0ustar zuulzuul00000000000000# Copyright (c) 2017 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import six from tempest.lib import exceptions from ironicclient.tests.functional.osc.v1 import base class TestNodeListFields(base.TestCase): """Functional tests for "baremetal node list" with --fields.""" def setUp(self): super(TestNodeListFields, self).setUp() self.node = self.node_create() def _get_table_headers(self, raw_output): table = self.parser.table(raw_output) return table['headers'] def test_list_default_fields(self): """Test presence of default list table headers.""" headers = ['UUID', 'Name', 'Instance UUID', 'Power State', 'Provisioning State', 'Maintenance'] nodes_list = self.openstack('baremetal node list') nodes_list_headers = self._get_table_headers(nodes_list) self.assertEqual(set(headers), set(nodes_list_headers)) def test_list_minimal_fields(self): headers = ['Instance UUID', 'Name', 'UUID'] fields = ['instance_uuid', 'name', 'uuid'] node_list = self.openstack( 'baremetal node list --fields {}' .format(' '.join(fields))) nodes_list_headers = self._get_table_headers(node_list) self.assertEqual(headers, nodes_list_headers) def test_list_no_fields(self): command = 'baremetal node list --fields' ex_text = 'expected at least one argument' six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.openstack, command) def test_list_wrong_field(self): command = 'baremetal node list --fields ABC' ex_text = 'invalid choice' six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.openstack, command) class TestNodeShowFields(base.TestCase): """Functional tests for "baremetal node show" with --fields.""" def setUp(self): super(TestNodeShowFields, self).setUp() self.node = self.node_create() self.api_version = '--os-baremetal-api-version 1.20' def _get_table_rows(self, raw_output): table = self.parser.table(raw_output) rows = [] for row in table['values']: rows.append(row[0]) return rows def test_show_default_fields(self): rows = ['console_enabled', 'clean_step', 'created_at', 'driver', 'driver_info', 'driver_internal_info', 'extra', 'inspection_finished_at', 'inspection_started_at', 'instance_info', 'instance_uuid', 'last_error', 'maintenance', 'maintenance_reason', 'name', 'power_state', 'properties', 'provision_state', 'provision_updated_at', 'reservation', 'target_power_state', 'target_provision_state', 'updated_at', 'uuid'] node_show = self.openstack('baremetal node show {}' .format(self.node['uuid'])) nodes_show_rows = self._get_table_rows(node_show) self.assertTrue(set(rows).issubset(set(nodes_show_rows))) def test_show_minimal_fields(self): rows = [ 'instance_uuid', 'name', 'uuid'] node_show = self.openstack( 'baremetal node show {} --fields {} {}' .format(self.node['uuid'], ' '.join(rows), self.api_version)) nodes_show_rows = self._get_table_rows(node_show) self.assertEqual(set(rows), set(nodes_show_rows)) def test_show_no_fields(self): command = 'baremetal node show {} --fields {}'.format( self.node['uuid'], self.api_version) ex_text = 'expected at least one argument' six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.openstack, command) def test_show_wrong_field(self): command = 'baremetal node show {} --fields ABC {}'.format( self.node['uuid'], self.api_version) ex_text = 'invalid choice' six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.openstack, command) python-ironicclient-2.2.0/ironicclient/tests/functional/osc/v1/test_baremetal_portgroup_basic.py0000666000175100017510000001177513232474343033511 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import ddt from tempest.lib.common.utils import data_utils from ironicclient.tests.functional.osc.v1 import base @ddt.ddt class BaremetalPortGroupTests(base.TestCase): """Functional tests for baremetal port group commands.""" def setUp(self): super(BaremetalPortGroupTests, self).setUp() self.node = self.node_create() self.api_version = ' --os-baremetal-api-version 1.25' self.port_group = self.port_group_create(self.node['uuid'], params=self.api_version) def test_create_with_address(self): """Check baremetal port group create command with address argument. Test steps: 1) Create baremetal port group in setUp. 2) Create baremetal port group with specific address argument. 3) Check address of created port group. """ mac_address = data_utils.rand_mac_address() port_group = self.port_group_create( self.node['uuid'], params='{0} --address {1}'.format(self.api_version, mac_address)) self.assertEqual(mac_address, port_group['address']) def test_list(self): """Check baremetal port group list command. Test steps: 1) Create baremetal port group in setUp. 2) List baremetal port groups. 3) Check port group address, UUID and name in port groups list. """ port_group_list = self.port_group_list(params=self.api_version) self.assertIn(self.port_group['uuid'], [x['UUID'] for x in port_group_list]) self.assertIn(self.port_group['name'], [x['Name'] for x in port_group_list]) @ddt.data('name', 'uuid') def test_delete(self, key): """Check baremetal port group delete command. Test steps: 1) Create baremetal port group in setUp. 2) Delete baremetal port group by UUID. 3) Check that port group deleted successfully and not in list. """ output = self.port_group_delete(self.port_group[key], params=self.api_version) self.assertEqual('Deleted port group {0}' .format(self.port_group[key]), output.strip()) port_group_list = self.port_group_list(params=self.api_version) self.assertNotIn(self.port_group['uuid'], [x['UUID'] for x in port_group_list]) self.assertNotIn(self.port_group['name'], [x['Name'] for x in port_group_list]) @ddt.data('name', 'uuid') def test_show(self, key): """Check baremetal port group show command. Test steps: 1) Create baremetal port group in setUp. 2) Show baremetal port group. 3) Check name, uuid and address in port group show output. """ port_group = self.port_group_show( self.port_group[key], ['name', 'uuid', 'address'], params=self.api_version) self.assertEqual(self.port_group['name'], port_group['name']) self.assertEqual(self.port_group['uuid'], port_group['uuid']) self.assertEqual(self.port_group['address'], port_group['address']) @ddt.data('name', 'uuid') def test_set_unset(self, key): """Check baremetal port group set and unset commands. Test steps: 1) Create baremetal port group in setUp. 2) Set extra data for port group. 3) Check that baremetal port group extra data was set. 4) Unset extra data for port group. 5) Check that baremetal port group extra data was unset. """ extra_key = 'ext' extra_value = 'testdata' self.openstack( 'baremetal port group set --extra {0}={1} {2} {3}' .format(extra_key, extra_value, self.port_group[key], self.api_version)) show_prop = self.port_group_show(self.port_group[key], ['extra'], params=self.api_version) self.assertEqual(extra_value, show_prop['extra'][extra_key]) self.openstack('baremetal port group unset --extra {0} {1} {2}' .format(extra_key, self.port_group[key], self.api_version)) show_prop = self.port_group_show(self.port_group[key], ['extra'], params=self.api_version) self.assertNotIn(extra_key, show_prop['extra']) python-ironicclient-2.2.0/ironicclient/tests/functional/osc/v1/base.py0000666000175100017510000003011313232474343026131 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 from tempest.lib.common.utils import data_utils from tempest.lib import exceptions from ironicclient.tests.functional import base class TestCase(base.FunctionalTestBase): def openstack(self, *args, **kwargs): return self._ironic(cmd='openstack', *args, **kwargs) def get_opts(self, fields=None, output_format='json'): """Get options for OSC output fields format. :param List fields: List of fields to get :param String output_format: Select output format :return: String of formatted options """ if not fields: return ' -f {0}'.format(output_format) return ' -f {0} {1}'.format(output_format, ' '.join(['-c ' + it for it in fields])) @staticmethod def construct_cmd(*parts): return ' '.join(str(x) for x in parts) def assert_dict_is_subset(self, expected, actual): """Check if expected keys/values exist in actual response body. Check if the expected keys and values are in the actual response body. :param expected: dict of key-value pairs that are expected to be in 'actual' dict. :param actual: dict of key-value pairs. """ for key, value in expected.items(): self.assertEqual(value, actual[key]) def node_create(self, driver='fake', name=None, params=''): """Create baremetal node and add cleanup. :param String driver: Driver for a new node :param String name: Name for a new node :param String params: Additional args and kwargs :return: JSON object of created node """ if not name: name = data_utils.rand_name('baremetal') opts = self.get_opts() output = self.openstack('baremetal node create {0} ' '--driver {1} --name {2} {3}' .format(opts, driver, name, params)) node = json.loads(output) self.addCleanup(self.node_delete, node['uuid'], True) if not output: self.fail('Baremetal node has not been created!') return node def node_list(self, fields=None, params=''): """List baremetal nodes. :param List fields: List of fields to show :param String params: Additional kwargs :return: list of JSON node objects """ opts = self.get_opts(fields=fields) output = self.openstack('baremetal node list {0} {1}' .format(opts, params)) return json.loads(output) def node_show(self, identifier, fields=None, params=''): """Show specified baremetal node. :param String identifier: Name or UUID of the node :param List fields: List of fields to show :param List params: Additional kwargs :return: JSON object of node """ opts = self.get_opts(fields) output = self.openstack('baremetal node show {0} {1} {2}' .format(opts, identifier, params)) return json.loads(output) def node_delete(self, identifier, ignore_exceptions=False): """Try to delete baremetal node by name or UUID. :param String identifier: Name or UUID of the node :param Bool ignore_exceptions: Ignore exception (needed for cleanUp) :return: raw values output :raise: CommandFailed exception when command fails to delete a node """ try: return self.openstack('baremetal node delete {0}' .format(identifier)) except exceptions.CommandFailed: if not ignore_exceptions: raise def port_create(self, node_id, mac_address=None, params=''): """Create baremetal port and add cleanup. :param String node_id: baremetal node UUID :param String mac_address: MAC address for port :param String params: Additional args and kwargs :return: JSON object of created port """ if not mac_address: mac_address = data_utils.rand_mac_address() opts = self.get_opts() port = self.openstack('baremetal port create {0} ' '--node {1} {2} {3}' .format(opts, node_id, mac_address, params)) port = json.loads(port) if not port: self.fail('Baremetal port has not been created!') self.addCleanup(self.port_delete, port['uuid'], True) return port def port_list(self, fields=None, params=''): """List baremetal ports. :param List fields: List of fields to show :param String params: Additional kwargs :return: list of JSON port objects """ opts = self.get_opts(fields=fields) output = self.openstack('baremetal port list {0} {1}' .format(opts, params)) return json.loads(output) def port_show(self, uuid, fields=None, params=''): """Show specified baremetal port. :param String uuid: UUID of the port :param List fields: List of fields to show :param List params: Additional kwargs :return: JSON object of port """ opts = self.get_opts(fields) output = self.openstack('baremetal port show {0} {1} {2}' .format(opts, uuid, params)) return json.loads(output) def port_delete(self, uuid, ignore_exceptions=False): """Try to delete baremetal port by UUID. :param String uuid: UUID of the port :param Bool ignore_exceptions: Ignore exception (needed for cleanUp) :return: raw values output :raise: CommandFailed exception when command fails to delete a port """ try: return self.openstack('baremetal port delete {0}' .format(uuid)) except exceptions.CommandFailed: if not ignore_exceptions: raise def port_group_list(self, fields=None, params=''): """List baremetal port groups. :param List fields: List of fields to show :param String params: Additional kwargs :return: JSON object of port group list """ opts = self.get_opts(fields=fields) output = self.openstack('baremetal port group list {0} {1}' .format(opts, params)) return json.loads(output) def port_group_create(self, node_id, name=None, params=''): """Create baremetal port group. :param String node_id: baremetal node UUID :param String name: port group name :param String params: Additional args and kwargs :return: JSON object of created port group """ if not name: name = data_utils.rand_name('port_group') opts = self.get_opts() output = self.openstack( 'baremetal port group create {0} --node {1} --name {2} {3}' .format(opts, node_id, name, params)) port_group = json.loads(output) if not port_group: self.fail('Baremetal port group has not been created!') self.addCleanup(self.port_group_delete, port_group['uuid'], params=params, ignore_exceptions=True) return port_group def port_group_delete(self, identifier, params='', ignore_exceptions=False): """Try to delete baremetal port group by Name or UUID. :param String identifier: Name or UUID of the port group :param String params: temporary arg to pass api version. :param Bool ignore_exceptions: Ignore exception (needed for cleanUp) :return: raw values output :raise: CommandFailed exception if not ignore_exceptions """ try: return self.openstack('baremetal port group delete {0} {1}' .format(identifier, params)) except exceptions.CommandFailed: if not ignore_exceptions: raise def port_group_show(self, identifier, fields=None, params=''): """Show specified baremetal port group. :param String identifier: Name or UUID of the port group :param List fields: List of fields to show :param List params: Additional kwargs :return: JSON object of port group """ opts = self.get_opts(fields) output = self.openstack('baremetal port group show {0} {1} {2}' .format(identifier, opts, params)) return json.loads(output) def chassis_create(self, params=''): """Create baremetal chassis and add cleanup. :param String params: Additional args and kwargs :return: JSON object of created chassis """ opts = self.get_opts() chassis = self.openstack('baremetal chassis create {0} {1}' .format(opts, params)) chassis = json.loads(chassis) if not chassis: self.fail('Baremetal chassis has not been created!') self.addCleanup(self.chassis_delete, chassis['uuid'], True) return chassis def chassis_delete(self, uuid, ignore_exceptions=False): """Try to delete baremetal chassis by UUID. :param String uuid: UUID of the chassis :param Bool ignore_exceptions: Ignore exception (needed for cleanUp) :return: raw values output :raise: CommandFailed exception when command fails to delete a chassis """ try: return self.openstack('baremetal chassis delete {0}' .format(uuid)) except exceptions.CommandFailed: if not ignore_exceptions: raise def chassis_list(self, fields=None, params=''): """List baremetal chassis. :param List fields: List of fields to show :param String params: Additional kwargs :return: list of JSON chassis objects """ opts = self.get_opts(fields=fields) output = self.openstack('baremetal chassis list {0} {1}' .format(opts, params)) return json.loads(output) def chassis_show(self, uuid, fields=None, params=''): """Show specified baremetal chassis. :param String uuid: UUID of the chassis :param List fields: List of fields to show :param List params: Additional kwargs :return: JSON object of chassis """ opts = self.get_opts(fields) chassis = self.openstack('baremetal chassis show {0} {1} {2}' .format(opts, uuid, params)) return json.loads(chassis) def driver_show(self, driver_name, fields=None, params=''): """Show specified baremetal driver. :param String driver_name: Name of the driver :param List fields: List of fields to show :param List params: Additional kwargs :return: JSON object of driver """ opts = self.get_opts(fields=fields) driver = self.openstack('baremetal driver show {0} {1} {2}' .format(opts, driver_name, params)) return json.loads(driver) def driver_list(self, fields=None, params=''): """List baremetal drivers. :param List fields: List of fields to show :param String params: Additional kwargs :return: list of JSON driver objects """ opts = self.get_opts(fields=fields) output = self.openstack('baremetal driver list {0} {1}' .format(opts, params)) return json.loads(output) python-ironicclient-2.2.0/ironicclient/tests/functional/osc/__init__.py0000666000175100017510000000000013232474343026420 0ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/tests/functional/hooks/0000775000175100017510000000000013232474761024662 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/tests/functional/hooks/post_test_hook.sh0000777000175100017510000000333513232474343030267 0ustar zuulzuul00000000000000#!/bin/bash -xe # 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 script is executed inside post_test_hook function in devstack gate. function generate_testr_results { if [ -f .testrepository/0 ]; then sudo .tox/functional/bin/testr last --subunit > $WORKSPACE/testrepository.subunit sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit sudo /usr/os-testr-env/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html sudo gzip -9 $BASE/logs/testrepository.subunit sudo gzip -9 $BASE/logs/testr_results.html sudo chown $USER:$USER $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz fi } export IRONICCLIENT_DIR="$BASE/new/python-ironicclient" sudo chown -R $USER:stack $IRONICCLIENT_DIR cd $IRONICCLIENT_DIR # Run tests echo "Running ironicclient functional test suite" set +e # Only admin credentials needed for ironic api source $BASE/new/devstack/openrc admin admin # Preserve env for OS_ credentials sudo -E -H -u $USER ./tools/run_functional.sh EXIT_CODE=$? set -e # Collect and parse result generate_testr_results exit $EXIT_CODE python-ironicclient-2.2.0/ironicclient/tests/functional/test_portgroup.py0000666000175100017510000001062013232474343027206 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ironicclient.tests.functional import base class PortGroupSanityTest(base.FunctionalTestBase): """Sanity tests for testing actions with port groups. Smoke test for the Ironic CLI port group subcommands: create, show, update, delete, list, port-list. """ def setUp(self): super(PortGroupSanityTest, self).setUp() self.node = self.create_node() self.port_group = self.create_portgroup(self.node['uuid']) def test_portgroup_create(self): """Test steps: 1) Create node and port group in setUp(). 2) Check that port group has been successfully created. """ portgroup_list_uuid = self.get_portgroup_uuids_from_portgroup_list() self.assertIn(self.port_group['uuid'], portgroup_list_uuid) def test_portgroup_delete(self): """Test steps: 1) Create node and port group in setUp(). 2) Delete port group. 3) Check that port group has been successfully deleted. """ self.delete_portgroup(self.port_group['uuid']) portgroup_list_uuid = self.get_portgroup_uuids_from_portgroup_list() self.assertNotIn(self.port_group['uuid'], portgroup_list_uuid) def test_portgroup_show(self): """Test steps: 1) Create node and port group in setUp(). 2) Check that portgroup-show returns the same UUID as portgroup-create. """ portgroup_show = self.show_portgroup(self.port_group['uuid']) self.assertEqual(self.port_group['uuid'], portgroup_show['uuid']) self.assertEqual(self.port_group['name'], portgroup_show['name']) def test_portgroup_list(self): """Test steps: 1) Create node and port group in setUp(). 2) Create one more node and port group. 3) Check that portgroup-list contains UUIDs of all created port groups. """ other_node = self.create_node() other_portgroup = self.create_portgroup(other_node['uuid']) uuids = {x['UUID'] for x in self.list_portgroups()} self.assertTrue({self.port_group['uuid'], other_portgroup['uuid']}.issubset(uuids)) def test_portgroup_update(self): """Test steps: 1) Create node and port group in setUp(). 2) Create node to replace. 3) Set new node to maintenance. 4) Update port group by replacing node. 5) Check that port group has been successfully updated. """ node_to_replace = self.create_node() self.set_node_maintenance(node_to_replace['uuid'], True) updated_portgroup = self.update_portgroup( self.port_group['uuid'], 'replace', params='node_uuid={0}' .format(node_to_replace['uuid']) ) self.assertEqual(node_to_replace['uuid'], updated_portgroup['node_uuid']) self.assertNotEqual(self.port_group['node_uuid'], updated_portgroup['node_uuid']) def test_portgroup_port_list(self): """Test steps: 1) Create node and port group in setUp(). 2) Create a port. 3) Set node to maintenance. 4) Attach port to the port group. 5) List the ports associated with a port group. 6) Check port UUID in list. 7) Check port address in list. """ port = self.create_port(self.node['uuid']) self.set_node_maintenance(self.node['uuid'], True) self.update_port(port['uuid'], 'replace', flags='--ironic-api-version 1.25', params='portgroup_uuid={0}' .format(self.port_group['uuid'])) pg_port_list = self.portgroup_port_list(self.port_group['uuid']) self.assertIn(port['uuid'], [x['UUID'] for x in pg_port_list]) self.assertIn(port['address'], [x['Address'] for x in pg_port_list]) python-ironicclient-2.2.0/ironicclient/tests/functional/test_node_set_power_state.py0000666000175100017510000000634613232474343031373 0ustar zuulzuul00000000000000# Copyright (c) 2015 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ironicclient.tests.functional import base class NodeSetPowerStateTestIronicClient(base.FunctionalTestBase): """Tests for testing node-set-power-state command. Tests for the Ironic CLI node-set-power-state command that checks that node can be set to 'on', 'off' or 'reboot' power states """ def setUp(self): super(NodeSetPowerStateTestIronicClient, self).setUp() self.node = self.create_node() node_power_state = self.show_node_states(self.node['uuid']) self.assertEqual('None', node_power_state['power_state']) def test_node_set_power_state_on(self): """Test steps: 1) create node 2) set node power state to 'on' 3) check node power state has been set to 'on' """ self.set_node_power_state(self.node['uuid'], 'on') node_state = self.show_node_states(self.node['uuid']) self.assertEqual('power on', node_state['power_state']) def test_node_set_power_state_off(self): """Test steps: 1) create node 2) set node power state to 'off' 3) check node power state has been set to 'off' """ self.set_node_power_state(self.node['uuid'], 'off') node_state = self.show_node_states(self.node['uuid']) self.assertEqual('power off', node_state['power_state']) def test_node_set_power_state_reboot_node_off(self): """Test steps: 1) create node 2) set node power state to 'off' 3) check node power state has been set to 'off' 4) set node power state to 'reboot' 5) check node power state has been set to 'on' """ self.set_node_power_state(self.node['uuid'], 'off') node_state = self.show_node_states(self.node['uuid']) self.assertEqual('power off', node_state['power_state']) self.set_node_power_state(self.node['uuid'], 'reboot') node_state = self.show_node_states(self.node['uuid']) self.assertEqual('power on', node_state['power_state']) def test_node_set_power_state_reboot_node_on(self): """Test steps: 1) create node 2) set node power state to 'on' 3) check node power state has been set to 'on' 4) set node power state to 'reboot' 5) check node power state has been set to 'on' """ self.set_node_power_state(self.node['uuid'], 'on') node_state = self.show_node_states(self.node['uuid']) self.assertEqual('power on', node_state['power_state']) self.set_node_power_state(self.node['uuid'], 'reboot') node_state = self.show_node_states(self.node['uuid']) self.assertEqual('power on', node_state['power_state']) python-ironicclient-2.2.0/ironicclient/tests/functional/test_table_structure.py0000666000175100017510000000431613232474343030361 0ustar zuulzuul00000000000000# Copyright (c) 2015 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ironicclient.tests.functional import base class TableStructureIronicCLITests(base.FunctionalTestBase): """Basic, read-only table structure tests for Ironic CLI commands. Basic smoke tests for the Ironic CLI commands to check table structure which do not require creating or modifying Ironic objects. """ def test_chassis_list_table_structure(self): """Test steps: 1) get chassis-list 2) check table structure """ chassis_list_header = self.get_table_headers('chassis-list') self.assertTableHeaders(['Description', 'UUID'], chassis_list_header) def test_node_list_table_structure(self): """Test steps: 1) get node-list 2) check table structure """ node_list_header = self.get_table_headers('node-list') self.assertTableHeaders(['UUID', 'Name', 'Instance UUID', 'Power State', 'Provisioning State', 'Maintenance'], node_list_header) def test_port_list_table_structure(self): """Test steps: 1) get port-list 2) check table structure """ port_list_header = self.get_table_headers('port-list') self.assertTableHeaders(['UUID', 'Address'], port_list_header) def test_driver_list_table_structure(self): """Test steps: 1) get driver-list 2) check table structure """ driver_list_header = self.get_table_headers('driver-list') self.assertTableHeaders(['Supported driver(s)', 'Active host(s)'], driver_list_header) python-ironicclient-2.2.0/ironicclient/tests/functional/test_chassis.py0000666000175100017510000001666313232474343026617 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import six from tempest.lib.common.utils import data_utils from tempest.lib import exceptions from ironicclient.tests.functional import base class ChassisSanityTestIronicClient(base.FunctionalTestBase): """Sanity tests for testing actions with Chassis. Smoke test for the Ironic CLI commands which checks basic actions with chassis command like create, show, update, delete etc. """ def setUp(self): super(ChassisSanityTestIronicClient, self).setUp() self.chassis = self.create_chassis() def test_chassis_create(self): """Test steps: 1) create chassis 2) check that chassis has been successfully created """ chassis_list_uuid = self.get_chassis_uuids_from_chassis_list() self.assertIn(self.chassis['uuid'], chassis_list_uuid) def test_chassis_delete(self): """Test steps: 1) create chassis 2) check that chassis has been successfully created 3) delete chassis 4) check that chassis has been successfully deleted """ self.delete_chassis(self.chassis['uuid']) chassis_list_uuid = self.get_chassis_uuids_from_chassis_list() self.assertNotIn(self.chassis['uuid'], chassis_list_uuid) def test_chassis_show(self): """Test steps: 1) create chassis 2) check that chassis-show returns the same chassis UUID 3) chassis-create """ chassis_show = self.show_chassis(self.chassis['uuid']) self.assertEqual(self.chassis['uuid'], chassis_show['uuid']) def test_chassis_show_field(self): """Test steps: 1) create chassis 2) show chassis with fields uuid 3) check that fields is exist """ fields = ['uuid'] chassis_show = self.show_chassis(self.chassis['uuid'], params='--fields {0}' .format(*fields)) self.assertTableHeaders(fields, chassis_show.keys()) def test_chassis_update(self): """Test steps: 1) create chassis 2) update chassis 3) check that chassis has been successfully updated """ updated_chassis = self.update_chassis( self.chassis['uuid'], 'add', 'description=test-chassis') self.assertEqual('test-chassis', updated_chassis['description']) self.assertNotEqual(self.chassis['description'], updated_chassis['description']) def test_chassis_node_list(self): """Test steps: 1) create chassis in setUp() 2) create 3 nodes 3) update 2 nodes to be included in chassis 4) check if 2 nodes are added to chassis 5) check if 1 nodes isn't added to chassis """ node1 = self.create_node() node2 = self.create_node() # This node is created to show that it won't be present # in the chassis-node-list output node3 = self.create_node() updated_node1 = self.update_node(node1['uuid'], 'add chassis_uuid={0}' .format(self.chassis['uuid'])) updated_node2 = self.update_node(node2['uuid'], 'add chassis_uuid={0}' .format(self.chassis['uuid'])) nodes = [updated_node1['uuid'], updated_node2['uuid']] nodes.sort() nodes_uuids = self.get_nodes_uuids_from_chassis_node_list( self.chassis['uuid']) nodes_uuids.sort() self.assertEqual(nodes, nodes_uuids) self.assertNotIn(node3['uuid'], nodes_uuids) class ChassisNegativeTestsIronicClient(base.FunctionalTestBase): """Negative tests for testing actions with Chassis. Negative tests for the Ironic CLI commands which checks actions with chassis command like show, update, delete either using with arguments or without arguments. """ def test_chassis_delete_without_arguments(self): """Test step: 1) check that chassis-delete command without arguments triggers an exception """ ex_text = r'chassis-delete: error: too few arguments' six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.delete_chassis, '') def test_chassis_delete_with_incorrect_chassis_uuid(self): """Test step: 1) check that deleting non-exist chassis triggers an exception triggers an exception """ uuid = data_utils.rand_uuid() ex_text = (r"Chassis {0} " r"could not be found. \(HTTP 404\)".format(uuid)) six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.delete_chassis, '{0}'.format(uuid)) def test_chassis_show_without_arguments(self): """Test step: 1) check that chassis-show command without arguments triggers an exception """ ex_text = r'chassis-show: error: too few arguments' six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.show_chassis, '') def test_chassis_show_with_incorrect_chassis_uuid(self): """Test step: 1) check that chassis-show command with incorrect chassis uuid triggers an exception """ uuid = data_utils.rand_uuid() ex_text = (r"Chassis {0} " r"could not be found. \(HTTP 404\)".format(uuid)) six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.show_chassis, '{0}'.format(uuid)) def test_chassis_update_without_arguments(self): """Test steps: 1) create chassis 2) check that chassis-update command without arguments triggers an exception """ ex_text = r'chassis-update: error: too few arguments' six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.update_chassis, chassis_id='', operation='') def test_chassis_update_with_incorrect_chassis_uuid(self): """Test steps: 1) create chassis 2) check that chassis-update command with incorrect arguments triggers an exception """ uuid = data_utils.rand_uuid() ex_text = r'chassis-update: error: too few arguments' six.assertRaisesRegex(self, exceptions.CommandFailed, ex_text, self.update_chassis, chassis_id='{0}'.format(uuid), operation='') python-ironicclient-2.2.0/ironicclient/tests/functional/test_port.py0000666000175100017510000001142313232474343026133 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ironicclient.tests.functional import base class PortSanityTestIronicClient(base.FunctionalTestBase): """Sanity tests for testing actions with port. Smoke test for the Ironic CLI commands which checks basic actions with port command like create, show, update, delete etc. """ def setUp(self): super(PortSanityTestIronicClient, self).setUp() self.node = self.create_node() self.port = self.create_port(self.node['uuid']) def test_port_create(self): """Test steps: 1) create node in setUp() 2) create port in setUp() 3) check that port has been successfully created """ port_list_uuid = self.get_uuids_from_port_list() self.assertIn(self.port['uuid'], port_list_uuid) def test_port_delete(self): """Test steps: 1) create node in setUp() 2) create port in setUp() 3) check that port has been successfully created 4) delete port 5) check that port has been successfully deleted """ port_list_uuid = self.get_uuids_from_port_list() self.assertIn(self.port['uuid'], port_list_uuid) self.delete_port(self.port['uuid']) port_list_uuid = self.get_uuids_from_port_list() self.assertNotIn(self.port['uuid'], port_list_uuid) def test_port_show(self): """Test steps: 1) create node in setUp() 2) create port in setUp() 3) check that port-show returns the same port UUID as port-create """ port_show = self.show_port(self.port['uuid']) self.assertEqual(self.port['uuid'], port_show['uuid']) def test_port_show_field(self): """Test steps: 1) create node in setUp() 2) create port in setUp() 3) show port with fields uuid, address, node_uuid 4) check that only fields uuid, address, node_uuid are the output fields """ fields = ['uuid', 'address', 'node_uuid'] port_show = self.show_port(self.port['uuid'], params='--fields {0} {1} {2}' .format(*fields)) self.assertTableHeaders(fields, port_show.keys()) def test_port_update(self): """Test steps: 1) create node in setUp() 2) create port in setUp() 3) create node to replace 4) update port replacing node 5) check that port has been successfully updated """ node_to_replace = self.create_node() updated_port = self.update_port(self.port['uuid'], 'replace', params='node_uuid={0}' .format(node_to_replace['uuid'])) self.assertEqual(node_to_replace['uuid'], updated_port['node_uuid']) self.assertNotEqual(self.port['node_uuid'], updated_port['node_uuid']) def test_port_list(self): """Test steps: 1) create node and port in setUp() 2) create one more node and port explicitly 3) check that port-list contains UUIDs of created ports 4) check that port-list contains Addresses of created ports """ other_node = self.create_node() other_port = self.create_port(other_node['uuid']) port_list = self.list_ports() uuids = {x['UUID'] for x in port_list} self.assertTrue({self.port['uuid'], other_port['uuid']}.issubset(uuids)) addresses = {x['Address'] for x in port_list} self.assertTrue({self.port['address'], other_port['address']}.issubset(addresses)) def test_port_create_with_portgroup_uuid(self): """Test steps: 1) Create node in setUp(). 2) Create a port group. 3) Create a port with specified port group UUID. 4) Check port properties for portgroup_uuid. """ flag = '--ironic-api-version 1.25' port_group = self.create_portgroup(self.node['uuid']) port = self.create_port( self.node['uuid'], flags=flag, params='--portgroup {0}'.format(port_group['uuid'])) self.assertEqual(port_group['uuid'], port['portgroup_uuid']) python-ironicclient-2.2.0/ironicclient/tests/functional/test_node.py0000666000175100017510000001640013232474343026074 0ustar zuulzuul00000000000000# Copyright (c) 2015 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from tempest.lib.common.utils import data_utils from ironicclient.tests.functional import base import ironicclient.tests.functional.utils as utils class NodeSanityTestIronicClient(base.FunctionalTestBase): """Sanity tests for testing actions with Node. Smoke test for the Ironic CLI commands which checks basic actions with node command like create, delete etc. """ def setUp(self): super(NodeSanityTestIronicClient, self).setUp() self.node = self.create_node() def test_node_create(self): """Test steps: 1) create node 2) check that node has been successfully created """ self.assertIn(self.node['uuid'], self.get_nodes_uuids_from_node_list()) def test_node_show(self): """Test steps: 1) create node 2) check that created node UUID equals to the one present in node-show output """ node_show = self.show_node(self.node['uuid']) self.assertEqual(self.node['uuid'], node_show['uuid']) def test_node_show_field(self): """Test steps: 1) create node 2) show node with fields instance_uuid, driver, name, uuid 3) check that only fields instance_uuid, driver, name, uuid are the output fields """ fields = ['instance_uuid', 'driver', 'name', 'uuid'] node_show = self.show_node(self.node['uuid'], params='--fields %s' % ' '.join(fields)) self.assertTableHeaders(fields, node_show.keys()) def test_node_delete(self): """Test steps: 1) create node 2) check that it was created 3) delete node 4) check that node has been successfully deleted """ self.assertIn(self.node['uuid'], self.get_nodes_uuids_from_node_list()) self.delete_node(self.node['uuid']) self.assertNotIn(self.node['uuid'], self.get_nodes_uuids_from_node_list()) def test_node_update(self): """Test steps: 1) create node 2) update node name 3) check that node name has been successfully updated """ node_name = data_utils.rand_name(prefix='test') updated_node = self.update_node(self.node['uuid'], 'add name={0}'.format(node_name)) self.assertEqual(node_name, updated_node['name']) def test_node_set_console_mode(self): """Test steps: 1) create node 2) check that console_enabled is False 3) set node console mode to True 4) check that node console mode has been successfully updated """ node_show = self.show_node(self.node['uuid']) self.assertEqual('False', node_show['console_enabled']) self.ironic('node-set-console-mode', params='{0} true'.format(self.node['uuid'])) node_show = self.show_node(self.node['uuid']) self.assertEqual('True', node_show['console_enabled']) def test_node_get_console(self): """Test steps: 1) create node 2) check console mode using node-show 3) get console mode using node-get-console 4) check that node-get-console value equals node-show value """ node_show = self.show_node(self.node['uuid']) node_get = self.ironic('node-get-console', params=self.node['uuid']) node_get = utils.get_dict_from_output(node_get) self.assertEqual(node_show['console_enabled'], node_get['console_enabled']) def test_node_set_maintenance(self): """Test steps: 1) create node 2) check that maintenance is False 3) put node to maintenance 4) check that node is in maintenance 5) check that maintenance reason has been successfully updated """ node_show = self.show_node(self.node['uuid']) self.assertEqual('False', node_show['maintenance']) self.set_node_maintenance( self.node['uuid'], "true --reason 'Testing node-set power state command'") node_show = self.show_node(self.node['uuid']) self.assertEqual('True', node_show['maintenance']) self.assertEqual('Testing node-set power state command', node_show['maintenance_reason']) def test_node_set_power_state(self): """Test steps: 1) create node 2) check that power state is None 3) set power state to 'off' 4) check that power state has been changed successfully """ node_show = self.show_node(self.node['uuid']) self.assertEqual('None', node_show['power_state']) self.set_node_power_state(self.node['uuid'], "off") node_show = self.show_node(self.node['uuid']) self.assertEqual('power off', node_show['power_state']) def test_node_set_provision_state(self): """Test steps: 1) create node 2) check that provision state is 'enroll' 3) set new provision state to the node 4) check that provision state has been updated successfully """ node_show = self.show_node(self.node['uuid']) self.assertEqual('enroll', node_show['provision_state']) for verb, target in [('manage', 'manageable'), ('provide', 'available'), ('active', 'active'), ('deleted', 'available')]: self.set_node_provision_state(self.node['uuid'], verb) node_show = self.show_node(self.node['uuid']) self.assertEqual(target, node_show['provision_state']) def test_node_validate(self): """Test steps: 1) create node 2) validate node """ node_validate = self.validate_node(self.node['uuid']) self.assertNodeValidate(node_validate) def test_show_node_states(self): """Test steps: 1) create node 2) check that states returned by node-show and node-show-states are the same """ node_show = self.show_node(self.node['uuid']) show_node_states = self.show_node_states(self.node['uuid']) self.assertNodeStates(node_show, show_node_states) def test_node_list(self): """Test steps: 1) create node in setup and one more node explicitly 2) check that both nodes are in list """ other_node = self.create_node() node_list = self.list_nodes() uuids = [x['UUID'] for x in node_list] names = [x['Name'] for x in node_list] self.assertIn(self.node['uuid'], uuids) self.assertIn(other_node['uuid'], uuids) self.assertIn(self.node['name'], names) self.assertIn(other_node['name'], names) python-ironicclient-2.2.0/ironicclient/tests/functional/test_json_response.py0000666000175100017510000002615013232474343030041 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import jsonschema from tempest.lib.common.utils import data_utils from ironicclient.tests.functional import base def _validate_json(json_response, schema): """Verify JSON is valid. :param json_response: JSON response from CLI :type json_response: string :param schema: expected schema of response :type json_response: dictionary """ json_response = json.loads(json_response) jsonschema.validate(json_response, schema) class TestNodeJsonResponse(base.FunctionalTestBase): """Test JSON responses for node commands.""" node_schema = { "type": "object", "properties": { "target_power_state": {"type": ["string", "null"]}, "extra": {"type": "object"}, "last_error": {"type": ["string", "null"]}, "updated_at": {"type": ["string", "null"]}, "maintenance_reason": {"type": ["string", "null"]}, "provision_state": {"type": "string"}, "clean_step": {"type": "object"}, "uuid": {"type": "string"}, "console_enabled": {"type": "boolean"}, "target_provision_state": {"type": ["string", "null"]}, "raid_config": {"type": "object"}, "provision_updated_at": {"type": ["string", "null"]}, "maintenance": {"type": "boolean"}, "target_raid_config": {"type": "object"}, "inspection_started_at": {"type": ["string", "null"]}, "inspection_finished_at": {"type": ["string", "null"]}, "power_state": {"type": ["string", "null"]}, "driver": {"type": "string"}, "reservation": {"type": ["string", "null"]}, "properties": {"type": "object"}, "instance_uuid": {"type": ["string", "null"]}, "name": {"type": ["string", "null"]}, "driver_info": {"type": "object"}, "created_at": {"type": "string"}, "driver_internal_info": {"type": "object"}, "chassis_uuid": {"type": ["string", "null"]}, "instance_info": {"type": "object"} }, "patternProperties": { ".*_interface$": {"type": ["string", "null"]} }, "additionalProperties": True } def setUp(self): super(TestNodeJsonResponse, self).setUp() self.node = self.create_node() def test_node_list_json(self): """Test JSON response for nodes list.""" schema = { "type": "array", "items": { "type": "object", "properties": { "instance_uuid": {"type": ["string", "null"]}, "maintenance": {"type": "boolean"}, "name": {"type": ["string", "null"]}, "power_state": {"type": ["string", "null"]}, "provision_state": {"type": "string"}, "uuid": {"type": "string"}}} } response = self.ironic('node-list', flags='--json', params='', parse=False) _validate_json(response, schema) def test_node_show_json(self): """Test JSON response for node show.""" response = self.ironic('node-show', flags='--json', params='{0}' .format(self.node['uuid']), parse=False) _validate_json(response, self.node_schema) def test_node_validate_json(self): """Test JSON response for node validation.""" schema = { "type": "array", "items": { "type": "object", "properties": { "interface": {"type": ["string", "null"]}, "result": {"type": ["boolean", "null"]}, "reason": {"type": ["string", "null"]}}} } response = self.ironic('node-validate', flags='--json', params='{0}'.format(self.node['uuid']), parse=False) _validate_json(response, schema) def test_node_show_states_json(self): """Test JSON response for node show states.""" schema = { "type": "object", "properties": { "target_power_state": {"type": ["string", "null"]}, "target_provision_state": {"type": ["string", "null"]}, "last_error": {"type": ["string", "null"]}, "console_enabled": {"type": "boolean"}, "provision_updated_at": {"type": ["string", "null"]}, "power_state": {"type": ["string", "null"]}, "provision_state": {"type": "string"} } } response = self.ironic('node-show-states', flags='--json', params='{0}'.format(self.node['uuid']), parse=False) _validate_json(response, schema) def test_node_create_json(self): """Test JSON response for node creation.""" schema = { "type": "object", "properties": { "uuid": {"type": "string"}, "driver_info": {"type": "object"}, "extra": {"type": "object"}, "driver": {"type": "string"}, "chassis_uuid": {"type": ["string", "null"]}, "properties": {"type": "object"}, "name": {"type": ["string", "null"]}, } } node_name = 'nodejson' response = self.ironic('node-create', flags='--json', params='-d fake -n {0}'.format(node_name), parse=False) self.addCleanup(self.delete_node, node_name) _validate_json(response, schema) def test_node_update_json(self): """Test JSON response for node update.""" node_name = data_utils.rand_name('test') response = self.ironic('node-update', flags='--json', params='{0} add name={1}' .format(self.node['uuid'], node_name), parse=False) _validate_json(response, self.node_schema) class TestDriverJsonResponse(base.FunctionalTestBase): """Test JSON responses for driver commands.""" def test_driver_list_json(self): """Test JSON response for drivers list.""" schema = { "type": "array", "items": { "type": "object", "properties": { "name": {"type": "string"}, "hosts": {"type": "string"}, }} } response = self.ironic('driver-list', flags='--json', parse=False) _validate_json(response, schema) def test_driver_show_json(self): """Test JSON response for driver show.""" schema = { "type": "object", "properties": { "name": {"type": "string"}, "hosts": { "type": "array", "items": {"type": "string"}} } } drivers_names = self.get_drivers_names() for driver in drivers_names: response = self.ironic('driver-show', flags='--json', params='{0}'.format(driver), parse=False) _validate_json(response, schema) def test_driver_properties_json(self): """Test JSON response for driver properties.""" schema = { "type": "object", "additionalProperties": {"type": "string"} } drivers_names = self.get_drivers_names() for driver in drivers_names: response = self.ironic('driver-properties', flags='--json', params='{0}'.format(driver), parse=False) _validate_json(response, schema) class TestChassisJsonResponse(base.FunctionalTestBase): """Test JSON responses for chassis commands.""" chassis_schema = { "type": "object", "properties": { "uuid": {"type": "string"}, "updated_at": {"type": ["string", "null"]}, "created_at": {"type": "string"}, "description": {"type": ["string", "null"]}, "extra": {"type": "object"}} } def setUp(self): super(TestChassisJsonResponse, self).setUp() self.chassis = self.create_chassis() def test_chassis_list_json(self): """Test JSON response for chassis list.""" schema = { "type": "array", "items": { "type": "object", "properties": { "uuid": {"type": "string"}, "description": {"type": ["string", "null"]}} } } response = self.ironic('chassis-list', flags='--json', parse=False) _validate_json(response, schema) def test_chassis_show_json(self): """Test JSON response for chassis show.""" response = self.ironic('chassis-show', flags='--json', params='{0}'.format(self.chassis['uuid']), parse=False) _validate_json(response, self.chassis_schema) def test_chassis_create_json(self): """Test JSON response for chassis create.""" response = self.ironic('chassis-create', flags='--json', parse=False) _validate_json(response, self.chassis_schema) def test_chassis_update_json(self): """Test JSON response for chassis update.""" response = self.ironic( 'chassis-update', flags='--json', params='{0} {1} {2}'.format( self.chassis['uuid'], 'add', 'description=test-chassis'), parse=False) _validate_json(response, self.chassis_schema) def test_chassis_node_list_json(self): """Test JSON response for chassis-node-list command.""" schema = { "type": "array", "items": { "type": "object", "properties": { "instance_uuid": {"type": ["string", "null"]}, "maintenance": {"type": "boolean"}, "name": {"type": ["string", "null"]}, "power_state": {"type": ["string", "null"]}, "provision_state": {"type": "string"}, "uuid": {"type": "string"}}} } self.node = self.create_node() self.update_node(self.node['uuid'], 'add chassis_uuid={0}' .format(self.chassis['uuid'])) response = self.ironic('chassis-node-list', flags='--json', params='{0}'.format(self.chassis['uuid']), parse=False) _validate_json(response, schema) python-ironicclient-2.2.0/ironicclient/tests/functional/test_help_msg.py0000666000175100017510000000435413232474343026752 0ustar zuulzuul00000000000000# Copyright (c) 2015 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ironicclient.tests.functional import base class IronicClientHelp(base.FunctionalTestBase): """Test for python-ironicclient help messages.""" def test_ironic_help(self): """Check Ironic client main help message contents.""" caption = ("Command-line interface to the " "OpenStack Bare Metal Provisioning API.") subcommands = { 'bash-completion', 'chassis-create', 'chassis-delete', 'chassis-list', 'chassis-node-list', 'chassis-show', 'chassis-update', 'driver-list', 'driver-properties', 'driver-show', 'driver-vendor-passthru', 'help', 'node-create', 'node-delete', 'node-get-boot-device', 'node-get-console', 'node-get-supported-boot-devices', 'node-list', 'node-port-list', 'node-set-boot-device', 'node-set-console-mode', 'node-set-maintenance', 'node-set-power-state', 'node-set-provision-state', 'node-show', 'node-show-states', 'node-update', 'node-validate', 'node-vendor-passthru', 'node-vif-attach', 'node-vif-detach', 'node-vif-list', 'port-create', 'port-delete', 'port-list', 'port-show', 'port-update' } output = self._ironic('help', flags='', params='') self.assertIn(caption, output) for string in subcommands: self.assertIn(string, output) python-ironicclient-2.2.0/ironicclient/tests/functional/__init__.py0000666000175100017510000000000013232474343025634 0ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/tests/functional/base.py0000666000175100017510000004336413232474343025033 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os import six import six.moves.configparser as config_parser from tempest.lib.cli import base from tempest.lib.common.utils import data_utils from tempest.lib import exceptions import ironicclient.tests.functional.utils as utils DEFAULT_CONFIG_FILE = os.path.join(os.path.dirname(__file__), 'test.conf') class FunctionalTestBase(base.ClientTestBase): """Ironic base class, calls to ironicclient.""" def setUp(self): super(FunctionalTestBase, self).setUp() self.client = self._get_clients() # NOTE(kromanenko) set ironic api version for portgroups self.pg_api_ver = '--ironic-api-version 1.25' def _get_clients(self): # NOTE(aarefiev): {toxinidir} is a current working directory, so # the tox env path is {toxinidir}/.tox cli_dir = os.path.join(os.path.abspath('.'), '.tox/functional/bin') config = self._get_config() if config.get('os_auth_url'): client = base.CLIClient(cli_dir=cli_dir, username=config['os_username'], password=config['os_password'], tenant_name=config['os_project_name'], uri=config['os_auth_url']) for keystone_object in 'user', 'project': domain_attr = 'os_%s_domain_id' % keystone_object if config.get(domain_attr): setattr(self, domain_attr, config[domain_attr]) else: self.ironic_url = config['ironic_url'] self.os_auth_token = config['os_auth_token'] client = base.CLIClient(cli_dir=cli_dir, ironic_url=self.ironic_url, os_auth_token=self.os_auth_token) return client def _get_config(self): config_file = os.environ.get('IRONICCLIENT_TEST_CONFIG', DEFAULT_CONFIG_FILE) # SafeConfigParser was deprecated in Python 3.2 if six.PY3: config = config_parser.ConfigParser() else: config = config_parser.SafeConfigParser() if not config.read(config_file): self.skipTest('Skipping, no test config found @ %s' % config_file) try: auth_strategy = config.get('functional', 'auth_strategy') except config_parser.NoOptionError: auth_strategy = 'keystone' if auth_strategy not in ['keystone', 'noauth']: raise self.fail( 'Invalid auth type specified: %s in functional must be ' 'one of: [keystone, noauth]' % auth_strategy) conf_settings = [] keystone_v3_conf_settings = [] if auth_strategy == 'keystone': conf_settings += ['os_auth_url', 'os_username', 'os_password', 'os_project_name'] keystone_v3_conf_settings += ['os_user_domain_id', 'os_project_domain_id', 'os_identity_api_version'] else: conf_settings += ['os_auth_token', 'ironic_url'] cli_flags = {} missing = [] for c in conf_settings + keystone_v3_conf_settings: try: cli_flags[c] = config.get('functional', c) except config_parser.NoOptionError: # NOTE(vdrok): Here we ignore the absence of KS v3 options as # v2 may be used. Keystone client will do the actual check of # the parameters' correctness. if c not in keystone_v3_conf_settings: missing.append(c) if missing: self.fail('Missing required setting in test.conf (%(conf)s) for ' 'auth_strategy=%(auth)s: %(missing)s' % {'conf': config_file, 'auth': auth_strategy, 'missing': ','.join(missing)}) return cli_flags def _cmd_no_auth(self, cmd, action, flags='', params=''): """Execute given command with noauth attributes. :param cmd: command to be executed :type cmd: string :param action: command on cli to run :type action: string :param flags: optional cli flags to use :type flags: string :param params: optional positional args to use :type params: string """ flags = ('--os_auth_token %(token)s --ironic_url %(url)s %(flags)s' % {'token': self.os_auth_token, 'url': self.ironic_url, 'flags': flags}) return base.execute(cmd, action, flags, params, cli_dir=self.client.cli_dir) def _ironic(self, action, cmd='ironic', flags='', params='', merge_stderr=False): """Execute ironic command for the given action. :param action: the cli command to run using Ironic :type action: string :param cmd: the base of cli command to run :type action: string :param flags: any optional cli flags to use :type flags: string :param params: any optional positional args to use :type params: string :param merge_stderr: whether to merge stderr into the result :type merge_stderr: bool """ if cmd == 'openstack': config = self._get_config() id_api_version = config['os_identity_api_version'] flags += ' --os-identity-api-version {0}'.format(id_api_version) else: flags += ' --os-endpoint-type publicURL' if hasattr(self, 'os_auth_token'): return self._cmd_no_auth(cmd, action, flags, params) else: for keystone_object in 'user', 'project': domain_attr = 'os_%s_domain_id' % keystone_object if hasattr(self, domain_attr): flags += ' --os-%(ks_obj)s-domain-id %(value)s' % { 'ks_obj': keystone_object, 'value': getattr(self, domain_attr) } return self.client.cmd_with_auth( cmd, action, flags, params, merge_stderr=merge_stderr) def ironic(self, action, flags='', params='', parse=True): """Return parsed list of dicts with basic item info. :param action: the cli command to run using Ironic :type action: string :param flags: any optional cli flags to use :type flags: string :param params: any optional positional args to use :type params: string :param parse: return parsed list or raw output :type parse: bool """ output = self._ironic(action=action, flags=flags, params=params) return self.parser.listing(output) if parse else output def get_table_headers(self, action, flags='', params=''): output = self._ironic(action=action, flags=flags, params=params) table = self.parser.table(output) return table['headers'] def assertTableHeaders(self, field_names, table_headers): """Assert that field_names and table_headers are equal. :param field_names: field names from the output table of the cmd :param table_headers: table headers output from cmd """ self.assertEqual(sorted(field_names), sorted(table_headers)) def assertNodeStates(self, node_show, node_show_states): """Assert that node_show_states output corresponds to node_show output. :param node_show: output from node-show cmd :param node_show_states: output from node-show-states cmd """ for key in node_show_states.keys(): self.assertEqual(node_show_states[key], node_show[key]) def assertNodeValidate(self, node_validate): """Assert that all interfaces present are valid. :param node_validate: output from node-validate cmd """ self.assertNotIn('False', [x['Result'] for x in node_validate]) def delete_node(self, node_id): """Delete node method works only with fake driver. :param node_id: node uuid :raises: CommandFailed exception when command fails to delete a node """ node_list = self.list_nodes() if utils.get_object(node_list, node_id): node_show = self.show_node(node_id) if node_show['provision_state'] not in ('available', 'manageable', 'enroll'): self.ironic('node-set-provision-state', params='{0} deleted'.format(node_id)) if node_show['power_state'] not in ('None', 'off'): self.ironic('node-set-power-state', params='{0} off'.format(node_id)) self.ironic('node-delete', params=node_id) node_list_uuid = self.get_nodes_uuids_from_node_list() if node_id in node_list_uuid: self.fail('Ironic node {0} has not been deleted!' .format(node_id)) def create_node(self, driver='fake', params=''): node = self.ironic('node-create', params='--driver {0} {1}'.format(driver, params)) if not node: self.fail('Ironic node has not been created!') node = utils.get_dict_from_output(node) self.addCleanup(self.delete_node, node['uuid']) return node def show_node(self, node_id, params=''): node_show = self.ironic('node-show', params='{0} {1}'.format(node_id, params)) return utils.get_dict_from_output(node_show) def list_nodes(self, params=''): return self.ironic('node-list', params=params) def update_node(self, node_id, params): updated_node = self.ironic('node-update', params='{0} {1}'.format(node_id, params)) return utils.get_dict_from_output(updated_node) def get_nodes_uuids_from_node_list(self): node_list = self.list_nodes() return [x['UUID'] for x in node_list] def show_node_states(self, node_id): show_node_states = self.ironic('node-show-states', params=node_id) return utils.get_dict_from_output(show_node_states) def set_node_maintenance(self, node_id, maintenance_mode, params=''): self.ironic( 'node-set-maintenance', params='{0} {1} {2}'.format(node_id, maintenance_mode, params)) def set_node_power_state(self, node_id, power_state, params=''): self.ironic('node-set-power-state', params='{0} {1} {2}' .format(node_id, power_state, params)) def set_node_provision_state(self, node_id, provision_state, params=''): self.ironic('node-set-provision-state', params='{0} {1} {2}' .format(node_id, provision_state, params)) def validate_node(self, node_id): return self.ironic('node-validate', params=node_id) def list_node_chassis(self, chassis_uuid, params=''): return self.ironic('chassis-node-list', params='{0} {1}'.format(chassis_uuid, params)) def get_nodes_uuids_from_chassis_node_list(self, chassis_uuid): chassis_node_list = self.list_node_chassis(chassis_uuid) return [x['UUID'] for x in chassis_node_list] def list_driver(self, params=''): return self.ironic('driver-list', params=params) def show_driver(self, driver_name): driver_show = self.ironic('driver-show', params=driver_name) return utils.get_dict_from_output(driver_show) def properties_driver(self, driver_name): return self.ironic('driver-properties', params=driver_name) def get_drivers_names(self): driver_list = self.list_driver() return [x['Supported driver(s)'] for x in driver_list] def delete_chassis(self, chassis_id, ignore_exceptions=False): try: self.ironic('chassis-delete', params=chassis_id) except exceptions.CommandFailed: if not ignore_exceptions: raise def get_chassis_uuids_from_chassis_list(self): chassis_list = self.list_chassis() return [x['UUID'] for x in chassis_list] def create_chassis(self, params=''): chassis = self.ironic('chassis-create', params=params) if not chassis: self.fail('Ironic chassis has not been created!') chassis = utils.get_dict_from_output(chassis) self.addCleanup(self.delete_chassis, chassis['uuid'], ignore_exceptions=True) return chassis def list_chassis(self, params=''): return self.ironic('chassis-list', params=params) def show_chassis(self, chassis_id, params=''): chassis_show = self.ironic('chassis-show', params='{0} {1}'.format(chassis_id, params)) return utils.get_dict_from_output(chassis_show) def update_chassis(self, chassis_id, operation, params=''): updated_chassis = self.ironic( 'chassis-update', params='{0} {1} {2}'.format(chassis_id, operation, params)) return utils.get_dict_from_output(updated_chassis) def delete_port(self, port_id, ignore_exceptions=False): try: self.ironic('port-delete', params=port_id) except exceptions.CommandFailed: if not ignore_exceptions: raise def create_port(self, node_id, mac_address=None, flags='', params=''): if mac_address is None: mac_address = data_utils.rand_mac_address() port = self.ironic('port-create', flags=flags, params='--address {0} --node {1} {2}' .format(mac_address, node_id, params)) if not port: self.fail('Ironic port has not been created!') return utils.get_dict_from_output(port) def list_ports(self, params=''): return self.ironic('port-list', params=params) def show_port(self, port_id, params=''): port_show = self.ironic('port-show', params='{0} {1}' .format(port_id, params)) return utils.get_dict_from_output(port_show) def get_uuids_from_port_list(self): port_list = self.list_ports() return [x['UUID'] for x in port_list] def update_port(self, port_id, operation, flags='', params=''): updated_port = self.ironic('port-update', flags=flags, params='{0} {1} {2}' .format(port_id, operation, params)) return utils.get_dict_from_output(updated_port) def create_portgroup(self, node_id, params=''): """Create a new portgroup.""" portgroup = self.ironic('portgroup-create', flags=self.pg_api_ver, params='--node {0} {1}' .format(node_id, params)) if not portgroup: self.fail('Ironic portgroup failed to create!') portgroup = utils.get_dict_from_output(portgroup) self.addCleanup(self.delete_portgroup, portgroup['uuid'], ignore_exceptions=True) return portgroup def delete_portgroup(self, portgroup_id, ignore_exceptions=False): """Delete a port group.""" try: self.ironic('portgroup-delete', flags=self.pg_api_ver, params=portgroup_id) except exceptions.CommandFailed: if not ignore_exceptions: raise def list_portgroups(self, params=''): """List the port groups.""" return self.ironic('portgroup-list', flags=self.pg_api_ver, params=params) def show_portgroup(self, portgroup_id, params=''): """Show detailed information about a port group.""" portgroup_show = self.ironic('portgroup-show', flags=self.pg_api_ver, params='{0} {1}' .format(portgroup_id, params)) return utils.get_dict_from_output(portgroup_show) def update_portgroup(self, portgroup_id, op, params=''): """Update information about a port group.""" updated_portgroup = self.ironic('portgroup-update', flags=self.pg_api_ver, params='{0} {1} {2}' .format(portgroup_id, op, params)) return utils.get_dict_from_output(updated_portgroup) def get_portgroup_uuids_from_portgroup_list(self): """Get UUIDs from list of port groups.""" portgroup_list = self.list_portgroups() return [x['UUID'] for x in portgroup_list] def portgroup_port_list(self, portgroup_id, params=''): """List the ports associated with a port group.""" return self.ironic('portgroup-port-list', flags=self.pg_api_ver, params='{0} {1}'.format(portgroup_id, params)) python-ironicclient-2.2.0/ironicclient/tests/unit/0000775000175100017510000000000013232474761022354 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/tests/unit/utils.py0000666000175100017510000001036413232474343024070 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack LLC. # 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 copy import os import fixtures import mock from oslo_utils import strutils import requests import six import testtools class BaseTestCase(testtools.TestCase): def setUp(self): super(BaseTestCase, self).setUp() self.useFixture(fixtures.FakeLogger()) # If enabled, stdout and/or stderr is captured and will appear in # test results if that test fails. if strutils.bool_from_string(os.environ.get('OS_STDOUT_CAPTURE')): stdout = self.useFixture(fixtures.StringStream('stdout')).stream self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout)) if strutils.bool_from_string(os.environ.get('OS_STDERR_CAPTURE')): stderr = self.useFixture(fixtures.StringStream('stderr')).stream self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr)) class FakeAPI(object): def __init__(self, responses): self.responses = responses self.calls = [] def _request(self, method, url, headers=None, body=None): call = (method, url, headers or {}, body) self.calls.append(call) return self.responses[url][method] def raw_request(self, *args, **kwargs): response = self._request(*args, **kwargs) body_iter = iter(six.StringIO(response[1])) return FakeResponse(response[0]), body_iter def json_request(self, *args, **kwargs): response = self._request(*args, **kwargs) return FakeResponse(response[0]), response[1] class FakeConnection(object): def __init__(self, response=None): self._response = response self._last_request = None def request(self, method, conn_url, **kwargs): self._last_request = (method, conn_url, kwargs) def setresponse(self, response): self._response = response def getresponse(self): return self._response def __repr__(self): return ("FakeConnection(response=%s)" % (self._response)) class FakeResponse(object): def __init__(self, headers, body=None, version=None, status=None, reason=None): """Fake object to help testing. :param headers: dict representing HTTP response headers :param body: file-like object """ self.headers = headers self.body = body self.raw = mock.Mock() self.raw.version = version self.status_code = status self.reason = reason def getheaders(self): return copy.deepcopy(self.headers).items() def getheader(self, key, default): return self.headers.get(key, default) def read(self, amt): return self.body.read(amt) def __repr__(self): return ("FakeResponse(%s, body=%s, version=%s, status=%s, reason=%s)" % (self.headers, self.body, self.version, self.status, self.reason)) def mockSessionResponse(headers, content=None, status_code=None, version=None): raw = mock.Mock() raw.version = version response = mock.Mock(spec=requests.Response, headers=headers, content=content, status_code=status_code, raw=raw, reason='', encoding='UTF-8') response.text = content return response def mockSession(headers, content=None, status_code=None, version=None): session = mock.Mock(spec=requests.Session, verify=False, cert=('test_cert', 'test_key')) response = mockSessionResponse(headers, content, status_code, version) session.request = mock.Mock(return_value=response) return session python-ironicclient-2.2.0/ironicclient/tests/unit/v1/0000775000175100017510000000000013232474761022702 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/tests/unit/v1/test_driver_shell.py0000666000175100017510000001667013232474343027005 0ustar zuulzuul00000000000000# Copyright 2014 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import mock from ironicclient.common import cliutils from ironicclient.tests.unit import utils import ironicclient.v1.driver as v1_driver import ironicclient.v1.driver_shell as d_shell class DriverShellTest(utils.BaseTestCase): def setUp(self): super(DriverShellTest, self).setUp() client_mock = mock.MagicMock() driver_mock = mock.MagicMock(spec=v1_driver.DriverManager) client_mock.driver = driver_mock self.client_mock = client_mock def test_driver_show(self): actual = {} fake_print_dict = lambda data, *args, **kwargs: actual.update(data) with mock.patch.object(cliutils, 'print_dict', fake_print_dict): driver = object() d_shell._print_driver_show(driver) exp = ['hosts', 'name', 'type', 'default_boot_interface', 'default_console_interface', 'default_deploy_interface', 'default_inspect_interface', 'default_management_interface', 'default_network_interface', 'default_power_interface', 'default_raid_interface', 'default_storage_interface', 'default_vendor_interface', 'enabled_boot_interfaces', 'enabled_console_interfaces', 'enabled_deploy_interfaces', 'enabled_inspect_interfaces', 'enabled_management_interfaces', 'enabled_network_interfaces', 'enabled_power_interfaces', 'enabled_raid_interfaces', 'enabled_storage_interfaces', 'enabled_vendor_interfaces'] act = actual.keys() self.assertEqual(sorted(exp), sorted(act)) def test_do_driver_vendor_passthru_with_args(self): client_mock = self.client_mock args = mock.MagicMock() args.driver_name = 'driver_name' args.http_method = 'POST' args.method = 'method' args.arguments = [['arg1=val1', 'arg2=val2']] d_shell.do_driver_vendor_passthru(client_mock, args) client_mock.driver.vendor_passthru.assert_called_once_with( args.driver_name, args.method, http_method=args.http_method, args={'arg1': 'val1', 'arg2': 'val2'}) def test_do_driver_vendor_passthru_without_args(self): client_mock = self.client_mock args = mock.MagicMock() args.driver_name = 'driver_name' args.http_method = 'POST' args.method = 'method' args.arguments = [[]] d_shell.do_driver_vendor_passthru(client_mock, args) client_mock.driver.vendor_passthru.assert_called_once_with( args.driver_name, args.method, args={}, http_method=args.http_method) def test_do_driver_properties(self): client_mock = self.client_mock args = mock.MagicMock() args.driver_name = 'driver_name' args.json = False d_shell.do_driver_properties(client_mock, args) client_mock.driver.properties.assert_called_once_with("driver_name") @mock.patch('ironicclient.common.cliutils.print_dict', autospec=True) def test_do_driver_properties_with_wrap_default(self, mock_print_dict): client_mock = self.client_mock client_mock.driver.properties.return_value = { 'foo': 'bar', 'baz': 'qux'} args = mock.MagicMock() args.driver_name = 'driver_name' args.wrap = 0 args.json = False d_shell.do_driver_properties(client_mock, args) mock_print_dict.assert_called_with( {'foo': 'bar', 'baz': 'qux'}, dict_value='Description', json_flag=False, wrap=0) @mock.patch('ironicclient.common.cliutils.print_dict', autospec=True) def test_do_driver_properties_with_wrap(self, mock_print_dict): client_mock = self.client_mock client_mock.driver.properties.return_value = { 'foo': 'bar', 'baz': 'qux'} args = mock.MagicMock() args.driver_name = 'driver_name' args.wrap = 80 args.json = False d_shell.do_driver_properties(client_mock, args) mock_print_dict.assert_called_with( {'foo': 'bar', 'baz': 'qux'}, dict_value='Description', json_flag=False, wrap=80) @mock.patch('ironicclient.common.cliutils.print_dict', autospec=True) def _test_do_driver_raid_logical_disk(self, print_dict_mock, wrap=0): cli_mock = self.client_mock cli_mock.driver.raid_logical_disk_properties.return_value = { 'foo': 'bar'} args = mock.MagicMock() args.driver_name = 'driver_name' args.wrap = wrap d_shell.do_driver_raid_logical_disk_properties(cli_mock, args) cli_mock.driver.raid_logical_disk_properties.assert_called_once_with( "driver_name") print_dict_mock.assert_called_with( {'foo': 'bar'}, dict_value='Description', wrap=wrap) def test_do_driver_raid_logical_disk_default_wrap(self): self._test_do_driver_raid_logical_disk() def test_do_driver_raid_logical_disk_with_wrap(self): self._test_do_driver_raid_logical_disk(wrap=80) def test_do_driver_show(self): client_mock = self.client_mock args = mock.MagicMock() args.driver_name = 'fake' args.json = False d_shell.do_driver_show(client_mock, args) client_mock.driver.get.assert_called_once_with('fake') def test_do_driver_list(self): client_mock = self.client_mock args = mock.MagicMock() args.type = None args.detail = None args.json = False d_shell.do_driver_list(client_mock, args) client_mock.driver.list.assert_called_once_with(driver_type=None, detail=None) def test_do_driver_list_with_type_and_no_detail(self): client_mock = self.client_mock args = mock.MagicMock() args.type = 'classic' args.detail = False args.json = False d_shell.do_driver_list(client_mock, args) client_mock.driver.list.assert_called_once_with(driver_type='classic', detail=False) def test_do_driver_list_with_detail(self): client_mock = self.client_mock args = mock.MagicMock() args.type = None args.detail = True args.json = False d_shell.do_driver_list(client_mock, args) client_mock.driver.list.assert_called_once_with(driver_type=None, detail=True) def test_do_driver_get_vendor_passthru_methods(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.driver_name = 'fake' d_shell.do_driver_get_vendor_passthru_methods(client_mock, args) mock_method = client_mock.driver.get_vendor_passthru_methods mock_method.assert_called_once_with('fake') python-ironicclient-2.2.0/ironicclient/tests/unit/v1/test_volume_target.py0000666000175100017510000002536613232474343027202 0ustar zuulzuul00000000000000# Copyright 2016 Hitachi, Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import testtools from ironicclient import exc from ironicclient.tests.unit import utils import ironicclient.v1.port NODE_UUID = '55555555-4444-3333-2222-111111111111' TARGET1 = {'uuid': '11111111-2222-3333-4444-555555555555', 'node_uuid': NODE_UUID, 'volume_type': 'iscsi', 'properties': {'target_iqn': 'iqn.foo'}, 'boot_index': 0, 'volume_id': '12345678', 'extra': {}} TARGET2 = {'uuid': '66666666-7777-8888-9999-000000000000', 'node_uuid': NODE_UUID, 'volume_type': 'fibre_channel', 'properties': {'target_wwn': 'foobar'}, 'boot_index': 1, 'volume_id': '87654321', 'extra': {}} CREATE_TARGET = copy.deepcopy(TARGET1) del CREATE_TARGET['uuid'] CREATE_TARGET_WITH_UUID = copy.deepcopy(TARGET1) UPDATED_TARGET = copy.deepcopy(TARGET1) NEW_VALUE = '100' UPDATED_TARGET['boot_index'] = NEW_VALUE fake_responses = { '/v1/volume/targets': { 'GET': ( {}, {"targets": [TARGET1]}, ), 'POST': ( {}, TARGET1 ), }, '/v1/volume/targets/?detail=True': { 'GET': ( {}, {"targets": [TARGET1]}, ), }, '/v1/volume/targets/?fields=uuid,boot_index': { 'GET': ( {}, {"targets": [TARGET1]}, ), }, '/v1/volume/targets/%s' % TARGET1['uuid']: { 'GET': ( {}, TARGET1, ), 'DELETE': ( {}, None, ), 'PATCH': ( {}, UPDATED_TARGET, ), }, '/v1/volume/targets/%s?fields=uuid,boot_index' % TARGET1['uuid']: { 'GET': ( {}, TARGET1, ), }, '/v1/volume/targets/?detail=True&node=%s' % NODE_UUID: { 'GET': ( {}, {"targets": [TARGET1]}, ), }, '/v1/volume/targets/?node=%s' % NODE_UUID: { 'GET': ( {}, {"targets": [TARGET1]}, ), } } fake_responses_pagination = { '/v1/volume/targets': { 'GET': ( {}, {"targets": [TARGET1], "next": "http://127.0.0.1:6385/v1/volume/targets/?marker=%s" % TARGET1['uuid']} ), }, '/v1/volume/targets/?limit=1': { 'GET': ( {}, {"targets": [TARGET1], "next": "http://127.0.0.1:6385/v1/volume/targets/?limit=1" "&marker=%s" % TARGET1['uuid']} ), }, '/v1/volume/targets/?limit=1&marker=%s' % TARGET1['uuid']: { 'GET': ( {}, {"targets": [TARGET2]} ), }, '/v1/volume/targets/?marker=%s' % TARGET1['uuid']: { 'GET': ( {}, {"targets": [TARGET2]} ), }, } fake_responses_sorting = { '/v1/volume/targets/?sort_key=updated_at': { 'GET': ( {}, {"targets": [TARGET2, TARGET1]} ), }, '/v1/volume/targets/?sort_dir=desc': { 'GET': ( {}, {"targets": [TARGET2, TARGET1]} ), }, } class VolumeTargetManagerTestBase(testtools.TestCase): def _validate_obj(self, expect, obj): self.assertEqual(expect['uuid'], obj.uuid) self.assertEqual(expect['volume_type'], obj.volume_type) self.assertEqual(expect['boot_index'], obj.boot_index) self.assertEqual(expect['volume_id'], obj.volume_id) self.assertEqual(expect['node_uuid'], obj.node_uuid) def _validate_list(self, expect_request, expect_targets, actual_targets): self.assertEqual(expect_request, self.api.calls) self.assertEqual(len(expect_targets), len(actual_targets)) for expect, obj in zip(expect_targets, actual_targets): self._validate_obj(expect, obj) class VolumeTargetManagerTest(VolumeTargetManagerTestBase): def setUp(self): super(VolumeTargetManagerTest, self).setUp() self.api = utils.FakeAPI(fake_responses) self.mgr = ironicclient.v1.volume_target.VolumeTargetManager(self.api) def test_volume_targets_list(self): volume_targets = self.mgr.list() expect = [ ('GET', '/v1/volume/targets', {}, None), ] expect_targets = [TARGET1] self._validate_list(expect, expect_targets, volume_targets) def test_volume_targets_list_by_node(self): volume_targets = self.mgr.list(node=NODE_UUID) expect = [ ('GET', '/v1/volume/targets/?node=%s' % NODE_UUID, {}, None), ] expect_targets = [TARGET1] self._validate_list(expect, expect_targets, volume_targets) def test_volume_targets_list_by_node_detail(self): volume_targets = self.mgr.list(node=NODE_UUID, detail=True) expect = [ ('GET', '/v1/volume/targets/?detail=True&node=%s' % NODE_UUID, {}, None), ] expect_targets = [TARGET1] self._validate_list(expect, expect_targets, volume_targets) def test_volume_targets_list_detail(self): volume_targets = self.mgr.list(detail=True) expect = [ ('GET', '/v1/volume/targets/?detail=True', {}, None), ] expect_targets = [TARGET1] self._validate_list(expect, expect_targets, volume_targets) def test_volume_target_list_fields(self): volume_targets = self.mgr.list(fields=['uuid', 'boot_index']) expect = [ ('GET', '/v1/volume/targets/?fields=uuid,boot_index', {}, None), ] expect_targets = [TARGET1] self._validate_list(expect, expect_targets, volume_targets) def test_volume_target_list_detail_and_fields_fail(self): self.assertRaises(exc.InvalidAttribute, self.mgr.list, detail=True, fields=['uuid', 'boot_index']) def test_volume_targets_show(self): volume_target = self.mgr.get(TARGET1['uuid']) expect = [ ('GET', '/v1/volume/targets/%s' % TARGET1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self._validate_obj(TARGET1, volume_target) def test_volume_target_show_fields(self): volume_target = self.mgr.get(TARGET1['uuid'], fields=['uuid', 'boot_index']) expect = [ ('GET', '/v1/volume/targets/%s?fields=uuid,boot_index' % TARGET1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(TARGET1['uuid'], volume_target.uuid) self.assertEqual(TARGET1['boot_index'], volume_target.boot_index) def test_create(self): volume_target = self.mgr.create(**CREATE_TARGET) expect = [ ('POST', '/v1/volume/targets', {}, CREATE_TARGET), ] self.assertEqual(expect, self.api.calls) self._validate_obj(TARGET1, volume_target) def test_create_with_uuid(self): volume_target = self.mgr.create(**CREATE_TARGET_WITH_UUID) expect = [ ('POST', '/v1/volume/targets', {}, CREATE_TARGET_WITH_UUID), ] self.assertEqual(expect, self.api.calls) self._validate_obj(TARGET1, volume_target) def test_delete(self): volume_target = self.mgr.delete(TARGET1['uuid']) expect = [ ('DELETE', '/v1/volume/targets/%s' % TARGET1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(volume_target) def test_update(self): patch = {'op': 'replace', 'value': NEW_VALUE, 'path': '/boot_index'} volume_target = self.mgr.update( volume_target_id=TARGET1['uuid'], patch=patch) expect = [ ('PATCH', '/v1/volume/targets/%s' % TARGET1['uuid'], {}, patch), ] self.assertEqual(expect, self.api.calls) self._validate_obj(UPDATED_TARGET, volume_target) class VolumeTargetManagerPaginationTest(VolumeTargetManagerTestBase): def setUp(self): super(VolumeTargetManagerPaginationTest, self).setUp() self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = ironicclient.v1.volume_target.VolumeTargetManager(self.api) def test_volume_targets_list_limit(self): volume_targets = self.mgr.list(limit=1) expect = [ ('GET', '/v1/volume/targets/?limit=1', {}, None), ] expect_targets = [TARGET1] self._validate_list(expect, expect_targets, volume_targets) def test_volume_targets_list_marker(self): volume_targets = self.mgr.list(marker=TARGET1['uuid']) expect = [ ('GET', '/v1/volume/targets/?marker=%s' % TARGET1['uuid'], {}, None), ] expect_targets = [TARGET2] self._validate_list(expect, expect_targets, volume_targets) def test_volume_targets_list_pagination_no_limit(self): volume_targets = self.mgr.list(limit=0) expect = [ ('GET', '/v1/volume/targets', {}, None), ('GET', '/v1/volume/targets/?marker=%s' % TARGET1['uuid'], {}, None) ] expect_targets = [TARGET1, TARGET2] self._validate_list(expect, expect_targets, volume_targets) class VolumeTargetManagerSortingTest(VolumeTargetManagerTestBase): def setUp(self): super(VolumeTargetManagerSortingTest, self).setUp() self.api = utils.FakeAPI(fake_responses_sorting) self.mgr = ironicclient.v1.volume_target.VolumeTargetManager(self.api) def test_volume_targets_list_sort_key(self): volume_targets = self.mgr.list(sort_key='updated_at') expect = [ ('GET', '/v1/volume/targets/?sort_key=updated_at', {}, None) ] expect_targets = [TARGET2, TARGET1] self._validate_list(expect, expect_targets, volume_targets) def test_volume_targets_list_sort_dir(self): volume_targets = self.mgr.list(sort_dir='desc') expect = [ ('GET', '/v1/volume/targets/?sort_dir=desc', {}, None) ] expect_targets = [TARGET2, TARGET1] self._validate_list(expect, expect_targets, volume_targets) python-ironicclient-2.2.0/ironicclient/tests/unit/v1/test_node_shell.py0000666000175100017510000014463013232474373026440 0ustar zuulzuul00000000000000# Copyright 2013 IBM Corp # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import tempfile import mock from ironicclient.common.apiclient import exceptions from ironicclient.common import cliutils from ironicclient.common import utils as commonutils from ironicclient import exc from ironicclient.tests.unit import utils import ironicclient.v1.node_shell as n_shell import ironicclient.v1.utils as v1_utils class NodeShellTest(utils.BaseTestCase): def test_node_show(self): actual = {} fake_print_dict = lambda data, *args, **kwargs: actual.update(data) with mock.patch.object(cliutils, 'print_dict', fake_print_dict): node = object() n_shell._print_node_show(node) exp = ['chassis_uuid', 'clean_step', 'created_at', 'console_enabled', 'driver', 'driver_info', 'driver_internal_info', 'extra', 'instance_info', 'instance_uuid', 'last_error', 'maintenance', 'maintenance_reason', 'name', 'boot_interface', 'console_interface', 'deploy_interface', 'inspect_interface', 'management_interface', 'network_interface', 'power_interface', 'raid_interface', 'storage_interface', 'vendor_interface', 'power_state', 'properties', 'provision_state', 'provision_updated_at', 'reservation', 'resource_class', 'target_power_state', 'target_provision_state', 'traits', 'updated_at', 'inspection_finished_at', 'inspection_started_at', 'uuid', 'raid_config', 'target_raid_config'] act = actual.keys() self.assertEqual(sorted(exp), sorted(act)) def test_do_node_delete(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = ['node_uuid'] n_shell.do_node_delete(client_mock, args) client_mock.node.delete.assert_called_once_with('node_uuid') def test_do_node_delete_multiple(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = ['node_uuid1', 'node_uuid2'] n_shell.do_node_delete(client_mock, args) client_mock.node.delete.assert_has_calls( [mock.call('node_uuid1'), mock.call('node_uuid2')]) def test_do_node_delete_multiple_with_exception(self): client_mock = mock.MagicMock() client_mock.node.delete.side_effect = ( [exceptions.ClientException, None]) args = mock.MagicMock() args.node = ['node_uuid1', 'node_uuid2'] self.assertRaises(exceptions.ClientException, n_shell.do_node_delete, client_mock, args) client_mock.node.delete.assert_has_calls( [mock.call('node_uuid1'), mock.call('node_uuid2')]) def test_do_node_update(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.op = 'add' args.attributes = [['arg1=val1', 'arg2=val2']] args.json = False n_shell.do_node_update(client_mock, args) patch = commonutils.args_array_to_patch(args.op, args.attributes[0]) client_mock.node.update.assert_called_once_with('node_uuid', patch) def test_do_node_update_wrong_op(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.op = 'foo' args.attributes = [['arg1=val1', 'arg2=val2']] args.json = False self.assertRaises(exceptions.CommandError, n_shell.do_node_update, client_mock, args) self.assertFalse(client_mock.node.update.called) def test_do_node_create(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.json = False n_shell.do_node_create(client_mock, args) client_mock.node.create.assert_called_once_with() def test_do_node_create_with_driver(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.driver = 'driver' args.json = False n_shell.do_node_create(client_mock, args) client_mock.node.create.assert_called_once_with( driver='driver') def test_do_node_create_with_chassis_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.chassis_uuid = 'chassis_uuid' args.json = False n_shell.do_node_create(client_mock, args) client_mock.node.create.assert_called_once_with( chassis_uuid='chassis_uuid') def test_do_node_create_with_driver_info(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.driver_info = ['arg1=val1', 'arg2=val2'] args.json = False n_shell.do_node_create(client_mock, args) kwargs = {'driver_info': {'arg1': 'val1', 'arg2': 'val2'}} client_mock.node.create.assert_called_once_with(**kwargs) def test_do_node_create_with_properties(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.properties = ['arg1=val1', 'arg2=val2'] args.json = False n_shell.do_node_create(client_mock, args) kwargs = {'properties': {'arg1': 'val1', 'arg2': 'val2'}} client_mock.node.create.assert_called_once_with(**kwargs) def test_do_node_create_with_extra(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.driver = 'driver_name' args.extra = ['arg1=val1', 'arg2=val2'] args.json = False n_shell.do_node_create(client_mock, args) kwargs = { 'driver': 'driver_name', 'extra': {'arg1': 'val1', 'arg2': 'val2'}, } client_mock.node.create.assert_called_once_with(**kwargs) def test_do_node_create_with_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.uuid = 'fef99cb8-a0d1-43df-b084-17b3b42b3cbd' args.json = False n_shell.do_node_create(client_mock, args) client_mock.node.create.assert_called_once_with(uuid=args.uuid) def test_do_node_create_with_name(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.name = 'node_name' args.json = False n_shell.do_node_create(client_mock, args) client_mock.node.create.assert_called_once_with(name=args.name) def test_do_node_create_with_boot_interface(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.boot_interface = 'boot' args.json = False n_shell.do_node_create(client_mock, args) client_mock.node.create.assert_called_once_with( boot_interface='boot') def test_do_node_create_with_console_interface(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.console_interface = 'console' args.json = False n_shell.do_node_create(client_mock, args) client_mock.node.create.assert_called_once_with( console_interface='console') def test_do_node_create_with_deploy_interface(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.deploy_interface = 'deploy' args.json = False n_shell.do_node_create(client_mock, args) client_mock.node.create.assert_called_once_with( deploy_interface='deploy') def test_do_node_create_with_inspect_interface(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.inspect_interface = 'inspect' args.json = False n_shell.do_node_create(client_mock, args) client_mock.node.create.assert_called_once_with( inspect_interface='inspect') def test_do_node_create_with_management_interface(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.management_interface = 'management' args.json = False n_shell.do_node_create(client_mock, args) client_mock.node.create.assert_called_once_with( management_interface='management') def test_do_node_create_with_network_interface(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.network_interface = 'neutron' args.json = False n_shell.do_node_create(client_mock, args) client_mock.node.create.assert_called_once_with( network_interface='neutron') def test_do_node_create_with_power_interface(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.power_interface = 'power' args.json = False n_shell.do_node_create(client_mock, args) client_mock.node.create.assert_called_once_with( power_interface='power') def test_do_node_create_with_raid_interface(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.raid_interface = 'raid' args.json = False n_shell.do_node_create(client_mock, args) client_mock.node.create.assert_called_once_with( raid_interface='raid') def test_do_node_create_with_storage_interface(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.storage_interface = 'storage' args.json = False n_shell.do_node_create(client_mock, args) client_mock.node.create.assert_called_once_with( storage_interface='storage') def test_do_node_create_with_vendor_interface(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.vendor_interface = 'vendor' args.json = False n_shell.do_node_create(client_mock, args) client_mock.node.create.assert_called_once_with( vendor_interface='vendor') def test_do_node_show(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.instance_uuid = False args.fields = None args.json = False n_shell.do_node_show(client_mock, args) client_mock.node.get.assert_called_once_with('node_uuid', fields=None) # assert get_by_instance_uuid() wasn't called self.assertFalse(client_mock.node.get_by_instance_uuid.called) def test_do_node_show_by_instance_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'instance_uuid' args.instance_uuid = True args.fields = None args.json = False n_shell.do_node_show(client_mock, args) client_mock.node.get_by_instance_uuid.assert_called_once_with( 'instance_uuid', fields=None) # assert get() wasn't called self.assertFalse(client_mock.node.get.called) def test_do_node_show_by_space_node_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = ' ' args.instance_uuid = False args.json = False self.assertRaises(exceptions.CommandError, n_shell.do_node_show, client_mock, args) def test_do_node_show_by_space_instance_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = ' ' args.instance_uuid = True args.json = False self.assertRaises(exceptions.CommandError, n_shell.do_node_show, client_mock, args) def test_do_node_show_by_empty_node_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = '' args.instance_uuid = False args.json = False self.assertRaises(exceptions.CommandError, n_shell.do_node_show, client_mock, args) def test_do_node_show_by_empty_instance_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = '' args.instance_uuid = True args.json = False self.assertRaises(exceptions.CommandError, n_shell.do_node_show, client_mock, args) def test_do_node_show_fields(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.instance_uuid = False args.fields = [['uuid', 'power_state']] args.json = False n_shell.do_node_show(client_mock, args) client_mock.node.get.assert_called_once_with( 'node_uuid', fields=['uuid', 'power_state']) def test_do_node_show_invalid_fields(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.instance_uuid = False args.fields = [['foo', 'bar']] args.json = False self.assertRaises(exceptions.CommandError, n_shell.do_node_show, client_mock, args) def test_do_node_set_maintenance_true(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.maintenance_mode = 'true' args.reason = 'reason' n_shell.do_node_set_maintenance(client_mock, args) client_mock.node.set_maintenance.assert_called_once_with( 'node_uuid', True, maint_reason='reason') def test_do_node_set_maintenance_false(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.maintenance_mode = 'false' # NOTE(jroll) None is the default. <3 mock. args.reason = None n_shell.do_node_set_maintenance(client_mock, args) client_mock.node.set_maintenance.assert_called_once_with( 'node_uuid', False, maint_reason=None) def test_do_node_set_maintenance_bad(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.maintenance_mode = 'yuck' # NOTE(jroll) None is the default. <3 mock. args.reason = None self.assertRaises(exceptions.CommandError, n_shell.do_node_set_maintenance, client_mock, args) self.assertFalse(client_mock.node.set_maintenance.called) def test_do_node_set_maintenance_false_with_reason_fails(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.maintenance_mode = 'false' args.reason = 'reason' self.assertRaises(exceptions.CommandError, n_shell.do_node_set_maintenance, client_mock, args) def test_do_node_set_maintenance_on(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.maintenance_mode = 'on' args.reason = 'reason' n_shell.do_node_set_maintenance(client_mock, args) client_mock.node.set_maintenance.assert_called_once_with( 'node_uuid', True, maint_reason='reason') def test_do_node_set_maintenance_off(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.maintenance_mode = 'off' # NOTE(jroll) None is the default. <3 mock. args.reason = None n_shell.do_node_set_maintenance(client_mock, args) client_mock.node.set_maintenance.assert_called_once_with( 'node_uuid', False, maint_reason=None) def test_do_node_set_maintenance_off_with_reason_fails(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.maintenance_mode = 'off' args.reason = 'reason' self.assertRaises(exceptions.CommandError, n_shell.do_node_set_maintenance, client_mock, args) def _do_node_set_power_state_helper(self, power_state, soft=False, timeout=None, error=False): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.power_state = power_state args.soft = soft args.power_timeout = timeout if error: client_mock.node = mock.MagicMock() client_mock.node.set_power_state = mock.MagicMock() client_mock.node.set_power_state.side_effect = ValueError("fake") self.assertRaises(exc.CommandError, n_shell.do_node_set_power_state, client_mock, args) else: n_shell.do_node_set_power_state(client_mock, args) client_mock.node.set_power_state.assert_called_once_with( 'node_uuid', power_state, soft, timeout=timeout) def test_do_node_set_power_state_on(self): self._do_node_set_power_state_helper('on') def test_do_node_set_power_state_off(self): self._do_node_set_power_state_helper('off') def test_do_node_set_power_state_reboot(self): self._do_node_set_power_state_helper('reboot') def test_do_node_set_power_state_on_timeout(self): self._do_node_set_power_state_helper('on', timeout=10) def test_do_node_set_power_state_on_timeout_fail(self): self._do_node_set_power_state_helper('on', timeout=0, error=True) def test_do_node_set_power_state_off_timeout(self): self._do_node_set_power_state_helper('off', timeout=10) def test_do_node_set_power_state_reboot_timeout(self): self._do_node_set_power_state_helper('reboot', timeout=10) def test_do_node_set_power_state_soft_on_fail(self): self._do_node_set_power_state_helper('on', soft=True, error=True) def test_do_node_set_power_state_soft_off(self): self._do_node_set_power_state_helper('off', soft=True) def test_do_node_set_power_state_soft_reboot(self): self._do_node_set_power_state_helper('reboot', soft=True) def test_do_node_set_power_state_soft_on_timeout_fail(self): self._do_node_set_power_state_helper('on', soft=True, timeout=10, error=True) def test_do_node_set_power_state_soft_off_timeout(self): self._do_node_set_power_state_helper('off', soft=True, timeout=10) def test_do_node_set_power_state_soft_reboot_timeout(self): self._do_node_set_power_state_helper('reboot', soft=True, timeout=10) def test_do_node_set_target_raid_config_file(self): contents = '{"raid": "config"}' with tempfile.NamedTemporaryFile(mode='w') as f: f.write(contents) f.flush() node_manager_mock = mock.MagicMock(spec=['set_target_raid_config']) client_mock = mock.MagicMock(spec=['node'], node=node_manager_mock) args = mock.MagicMock() args.node = 'node_uuid' args.target_raid_config = f.name n_shell.do_node_set_target_raid_config(client_mock, args) node_manager_mock.set_target_raid_config.assert_called_once_with( 'node_uuid', json.loads(contents)) def test_do_node_set_target_raid_config_string(self): node_manager_mock = mock.MagicMock(spec=['set_target_raid_config']) client_mock = mock.MagicMock(spec=['node'], node=node_manager_mock) target_raid_config_string = ( '{"logical_disks": [{"size_gb": 100, "raid_level": "1"}]}') expected_target_raid_config_string = json.loads( target_raid_config_string) args = mock.MagicMock(node='node', target_raid_config=target_raid_config_string) n_shell.do_node_set_target_raid_config(client_mock, args) node_manager_mock.set_target_raid_config.assert_called_once_with( 'node', expected_target_raid_config_string) @mock.patch.object(commonutils, 'get_from_stdin', autospec=True) def test_set_target_raid_config_stdin(self, stdin_read_mock): node_manager_mock = mock.MagicMock(spec=['set_target_raid_config']) client_mock = mock.MagicMock(spec=['node'], node=node_manager_mock) target_raid_config_string = ( '{"logical_disks": [{"size_gb": 100, "raid_level": "1"}]}') stdin_read_mock.return_value = target_raid_config_string args_mock = mock.MagicMock(node='node', target_raid_config='-') expected_target_raid_config_string = json.loads( target_raid_config_string) n_shell.do_node_set_target_raid_config(client_mock, args_mock) stdin_read_mock.assert_called_once_with('target_raid_config') client_mock.node.set_target_raid_config.assert_called_once_with( 'node', expected_target_raid_config_string) @mock.patch.object(commonutils, 'get_from_stdin', autospec=True) def test_set_target_raid_config_stdin_exception(self, stdin_read_mock): client_mock = mock.MagicMock() stdin_read_mock.side_effect = exc.InvalidAttribute('bad') args_mock = mock.MagicMock(node='node', target_raid_config='-') self.assertRaises(exc.InvalidAttribute, n_shell.do_node_set_target_raid_config, client_mock, args_mock) stdin_read_mock.assert_called_once_with('target_raid_config') self.assertFalse(client_mock.set_target_raid_config.called) def test_do_node_validate(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' n_shell.do_node_validate(client_mock, args) client_mock.node.validate.assert_called_once_with('node_uuid') def test_do_node_vendor_passthru_with_args(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.http_method = 'POST' args.method = 'method' args.arguments = [['arg1=val1', 'arg2=val2']] n_shell.do_node_vendor_passthru(client_mock, args) client_mock.node.vendor_passthru.assert_called_once_with( args.node, args.method, args={'arg1': 'val1', 'arg2': 'val2'}, http_method=args.http_method) def test_do_node_vendor_passthru_without_args(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.http_method = 'POST' args.method = 'method' args.arguments = [[]] n_shell.do_node_vendor_passthru(client_mock, args) client_mock.node.vendor_passthru.assert_called_once_with( args.node, args.method, args={}, http_method=args.http_method) def test_do_node_set_provision_state_active(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.provision_state = 'active' args.config_drive = 'foo' args.clean_steps = None args.wait_timeout = None n_shell.do_node_set_provision_state(client_mock, args) client_mock.node.set_provision_state.assert_called_once_with( 'node_uuid', 'active', configdrive='foo', cleansteps=None) self.assertFalse(client_mock.node.wait_for_provision_state.called) def test_do_node_set_provision_state_active_wait(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.provision_state = 'active' args.config_drive = 'foo' args.clean_steps = None args.wait_timeout = 0 n_shell.do_node_set_provision_state(client_mock, args) client_mock.node.set_provision_state.assert_called_once_with( 'node_uuid', 'active', configdrive='foo', cleansteps=None) client_mock.node.wait_for_provision_state.assert_called_once_with( 'node_uuid', expected_state='active', timeout=0, poll_interval=v1_utils._LONG_ACTION_POLL_INTERVAL) def test_do_node_set_provision_state_deleted(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.provision_state = 'deleted' args.config_drive = None args.clean_steps = None args.wait_timeout = None n_shell.do_node_set_provision_state(client_mock, args) client_mock.node.set_provision_state.assert_called_once_with( 'node_uuid', 'deleted', configdrive=None, cleansteps=None) def test_do_node_set_provision_state_rebuild(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.provision_state = 'rebuild' args.config_drive = None args.clean_steps = None args.wait_timeout = None n_shell.do_node_set_provision_state(client_mock, args) client_mock.node.set_provision_state.assert_called_once_with( 'node_uuid', 'rebuild', configdrive=None, cleansteps=None) def test_do_node_set_provision_state_not_active_fails(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.provision_state = 'deleted' args.config_drive = 'foo' args.clean_steps = None args.wait_timeout = None self.assertRaises(exceptions.CommandError, n_shell.do_node_set_provision_state, client_mock, args) self.assertFalse(client_mock.node.set_provision_state.called) def test_do_node_set_provision_state_inspect(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.provision_state = 'inspect' args.config_drive = None args.clean_steps = None args.wait_timeout = None n_shell.do_node_set_provision_state(client_mock, args) client_mock.node.set_provision_state.assert_called_once_with( 'node_uuid', 'inspect', configdrive=None, cleansteps=None) def test_do_node_set_provision_state_manage(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.provision_state = 'manage' args.config_drive = None args.clean_steps = None args.wait_timeout = None n_shell.do_node_set_provision_state(client_mock, args) client_mock.node.set_provision_state.assert_called_once_with( 'node_uuid', 'manage', configdrive=None, cleansteps=None) def test_do_node_set_provision_state_provide(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.provision_state = 'provide' args.config_drive = None args.clean_steps = None args.wait_timeout = None n_shell.do_node_set_provision_state(client_mock, args) client_mock.node.set_provision_state.assert_called_once_with( 'node_uuid', 'provide', configdrive=None, cleansteps=None) def test_do_node_set_provision_state_clean(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.provision_state = 'clean' args.config_drive = None clean_steps = '[{"step": "upgrade", "interface": "deploy"}]' args.clean_steps = clean_steps args.wait_timeout = None n_shell.do_node_set_provision_state(client_mock, args) client_mock.node.set_provision_state.assert_called_once_with( 'node_uuid', 'clean', configdrive=None, cleansteps=json.loads(clean_steps)) @mock.patch.object(commonutils, 'get_from_stdin', autospec=True) def test_do_node_set_provision_state_clean_stdin(self, mock_stdin): clean_steps = '[{"step": "upgrade", "interface": "deploy"}]' mock_stdin.return_value = clean_steps client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.provision_state = 'clean' args.config_drive = None args.clean_steps = '-' args.wait_timeout = None n_shell.do_node_set_provision_state(client_mock, args) mock_stdin.assert_called_once_with('clean steps') client_mock.node.set_provision_state.assert_called_once_with( 'node_uuid', 'clean', configdrive=None, cleansteps=json.loads(clean_steps)) @mock.patch.object(commonutils, 'get_from_stdin', autospec=True) def test_do_node_set_provision_state_clean_stdin_fails(self, mock_stdin): mock_stdin.side_effect = exc.InvalidAttribute('bad') client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.provision_state = 'clean' args.config_drive = None args.clean_steps = '-' args.wait_timeout = None self.assertRaises(exc.InvalidAttribute, n_shell.do_node_set_provision_state, client_mock, args) mock_stdin.assert_called_once_with('clean steps') self.assertFalse(client_mock.node.set_provision_state.called) def test_do_node_set_provision_state_clean_file(self): contents = '[{"step": "upgrade", "interface": "deploy"}]' with tempfile.NamedTemporaryFile(mode='w') as f: f.write(contents) f.flush() client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.provision_state = 'clean' args.config_drive = None args.clean_steps = f.name args.wait_timeout = None n_shell.do_node_set_provision_state(client_mock, args) client_mock.node.set_provision_state.assert_called_once_with( 'node_uuid', 'clean', configdrive=None, cleansteps=json.loads(contents)) def test_do_node_set_provision_state_clean_fails(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.provision_state = 'clean' args.config_drive = None args.clean_steps = None args.wait_timeout = None # clean_steps isn't specified self.assertRaisesRegex(exceptions.CommandError, 'clean-steps.*must be specified', n_shell.do_node_set_provision_state, client_mock, args) self.assertFalse(client_mock.node.set_provision_state.called) def test_do_node_set_provision_state_not_clean_fails(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.provision_state = 'deleted' args.config_drive = None clean_steps = '[{"step": "upgrade", "interface": "deploy"}]' args.clean_steps = clean_steps args.wait_timeout = None # clean_steps specified but not cleaning self.assertRaisesRegex(exceptions.CommandError, 'clean-steps.*only valid', n_shell.do_node_set_provision_state, client_mock, args) self.assertFalse(client_mock.node.set_provision_state.called) def test_do_node_set_provision_state_abort(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.provision_state = 'abort' args.config_drive = None args.clean_steps = None args.wait_timeout = None n_shell.do_node_set_provision_state(client_mock, args) client_mock.node.set_provision_state.assert_called_once_with( 'node_uuid', 'abort', configdrive=None, cleansteps=None) def test_do_node_set_provision_state_adopt(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.provision_state = 'adopt' args.config_drive = None args.clean_steps = None args.wait_timeout = 0 n_shell.do_node_set_provision_state(client_mock, args) client_mock.node.set_provision_state.assert_called_once_with( 'node_uuid', 'adopt', cleansteps=None, configdrive=None) def test_do_node_set_provision_state_abort_no_wait(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.provision_state = 'abort' args.config_drive = None args.clean_steps = None args.wait_timeout = 0 self.assertRaisesRegex(exceptions.CommandError, "not supported for provision state 'abort'", n_shell.do_node_set_provision_state, client_mock, args) self.assertFalse(client_mock.node.set_provision_state.called) self.assertFalse(client_mock.node.wait_for_provision_state.called) def test_do_node_set_console_mode(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.enabled = 'true' n_shell.do_node_set_console_mode(client_mock, args) client_mock.node.set_console_mode.assert_called_once_with( 'node_uuid', True) def test_do_node_set_console_mode_bad(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.enabled = 'yuck' self.assertRaises(exceptions.CommandError, n_shell.do_node_set_console_mode, client_mock, args) self.assertFalse(client_mock.node.set_console_mode.called) def test_do_node_set_boot_device(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.persistent = False args.device = 'pxe' n_shell.do_node_set_boot_device(client_mock, args) client_mock.node.set_boot_device.assert_called_once_with( 'node_uuid', 'pxe', False) def test_do_node_set_boot_device_persistent(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.persistent = True args.device = 'disk' n_shell.do_node_set_boot_device(client_mock, args) client_mock.node.set_boot_device.assert_called_once_with( 'node_uuid', 'disk', True) def test_do_node_get_boot_device(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.json = False n_shell.do_node_get_boot_device(client_mock, args) client_mock.node.get_boot_device.assert_called_once_with('node_uuid') def test_do_node_inject_nmi(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' n_shell.do_node_inject_nmi(client_mock, args) client_mock.node.inject_nmi.assert_called_once_with('node_uuid') def test_do_node_get_supported_boot_devices(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.json = False n_shell.do_node_get_supported_boot_devices(client_mock, args) client_mock.node.get_supported_boot_devices.assert_called_once_with( 'node_uuid') def _get_client_mock_args(self, node=None, associated=None, maintenance=None, marker=None, limit=None, sort_dir=None, sort_key=None, detail=False, fields=None, provision_state=None, driver=None, json=False, resource_class=None): args = mock.MagicMock() args.node = node args.associated = associated args.maintenance = maintenance args.provision_state = provision_state args.marker = marker args.limit = limit args.sort_dir = sort_dir args.sort_key = sort_key args.detail = detail args.fields = fields args.driver = driver args.json = json args.resource_class = resource_class return args def test_do_node_list(self): client_mock = mock.MagicMock() args = self._get_client_mock_args() n_shell.do_node_list(client_mock, args) client_mock.node.list.assert_called_once_with(detail=False) def test_do_node_list_detail(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(detail=True) n_shell.do_node_list(client_mock, args) client_mock.node.list.assert_called_once_with(detail=True) def test_do_node_list_provision_state(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(provision_state='wait call-back', detail=False) n_shell.do_node_list(client_mock, args) client_mock.node.list.assert_called_once_with( provision_state='wait call-back', detail=False) def test_do_node_list_detail_provision_state(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(provision_state='wait call-back', detail=True) n_shell.do_node_list(client_mock, args) client_mock.node.list.assert_called_once_with( provision_state='wait call-back', detail=True) def test_do_node_list_driver(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(driver='fake', detail=False) n_shell.do_node_list(client_mock, args) client_mock.node.list.assert_called_once_with(driver='fake', detail=False) def test_do_node_list_detail_driver(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(driver='fake', detail=True) n_shell.do_node_list(client_mock, args) client_mock.node.list.assert_called_once_with(driver='fake', detail=True) def test_do_node_list_resource_class(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(resource_class='foo', detail=False) n_shell.do_node_list(client_mock, args) client_mock.node.list.assert_called_once_with(resource_class='foo', detail=False) def test_do_node_list_detail_resource_class(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(resource_class='foo', detail=True) n_shell.do_node_list(client_mock, args) client_mock.node.list.assert_called_once_with(resource_class='foo', detail=True) def test_do_node_list_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='created_at', detail=False) n_shell.do_node_list(client_mock, args) client_mock.node.list.assert_called_once_with(sort_key='created_at', detail=False) def test_do_node_list_wrong_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='chassis_uuid', detail=False) self.assertRaises(exceptions.CommandError, n_shell.do_node_list, client_mock, args) self.assertFalse(client_mock.node.list.called) def test_do_node_list_detail_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='created_at', detail=True) n_shell.do_node_list(client_mock, args) client_mock.node.list.assert_called_once_with(sort_key='created_at', detail=True) def test_do_node_list_detail_wrong_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='chassis_uuid', detail=True) self.assertRaises(exceptions.CommandError, n_shell.do_node_list, client_mock, args) self.assertFalse(client_mock.node.list.called) def test_do_node_list_sort_dir(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_dir='desc', detail=False) n_shell.do_node_list(client_mock, args) client_mock.node.list.assert_called_once_with(sort_dir='desc', detail=False) def test_do_node_list_detail_sort_dir(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_dir='asc', detail=True) n_shell.do_node_list(client_mock, args) client_mock.node.list.assert_called_once_with(sort_dir='asc', detail=True) def test_do_node_list_wrong_sort_dir(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_dir='abc', detail=False) self.assertRaises(exceptions.CommandError, n_shell.do_node_list, client_mock, args) self.assertFalse(client_mock.node.list.called) def test_do_node_list_maintenance(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(maintenance=True, detail=False) n_shell.do_node_list(client_mock, args) client_mock.node.list.assert_called_once_with(maintenance=True, detail=False) def test_do_node_list_detail_maintenance(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(maintenance=True, detail=True) n_shell.do_node_list(client_mock, args) client_mock.node.list.assert_called_once_with(maintenance=True, detail=True) def test_do_node_list_associated(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(associated=True, detail=False) n_shell.do_node_list(client_mock, args) client_mock.node.list.assert_called_once_with(associated=True, detail=False) def test_do_node_list_detail_associated(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(associated=True, detail=True) n_shell.do_node_list(client_mock, args) client_mock.node.list.assert_called_once_with(associated=True, detail=True) def test_do_node_list_fields(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(fields=[['uuid', 'provision_state']]) n_shell.do_node_list(client_mock, args) client_mock.node.list.assert_called_once_with( fields=['uuid', 'provision_state'], detail=False) def test_do_node_list_invalid_fields(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(fields=[['foo', 'bar']]) self.assertRaises(exceptions.CommandError, n_shell.do_node_list, client_mock, args) def test_do_node_show_states(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.json = False n_shell.do_node_show_states(client_mock, args) client_mock.node.states.assert_called_once_with('node_uuid') def test_do_node_port_list(self): client_mock = mock.MagicMock() node_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(node=node_mock) n_shell.do_node_port_list(client_mock, args) client_mock.node.list_ports.assert_called_once_with( node_mock, detail=False) def test_do_node_port_list_detail(self): client_mock = mock.MagicMock() node_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(node=node_mock, detail=True) n_shell.do_node_port_list(client_mock, args) client_mock.node.list_ports.assert_called_once_with( node_mock, detail=True) def test_do_node_port_list_sort_key(self): client_mock = mock.MagicMock() node_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(node=node_mock, sort_key='created_at', detail=False) n_shell.do_node_port_list(client_mock, args) client_mock.node.list_ports.assert_called_once_with( node_mock, sort_key='created_at', detail=False) def test_do_node_port_list_wrong_sort_key(self): client_mock = mock.MagicMock() node_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(node=node_mock, sort_key='node_uuid', detail=False) self.assertRaises(exceptions.CommandError, n_shell.do_node_port_list, client_mock, args) self.assertFalse(client_mock.node.list_ports.called) def test_do_node_port_list_detail_sort_key(self): client_mock = mock.MagicMock() node_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(node=node_mock, sort_key='created_at', detail=True) n_shell.do_node_port_list(client_mock, args) client_mock.node.list_ports.assert_called_once_with( node_mock, sort_key='created_at', detail=True) def test_do_node_port_list_detail_wrong_sort_key(self): client_mock = mock.MagicMock() node_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(node=node_mock, sort_key='node_uuid', detail=True) self.assertRaises(exceptions.CommandError, n_shell.do_node_port_list, client_mock, args) self.assertFalse(client_mock.node.list_ports.called) def test_do_node_port_list_sort_dir(self): client_mock = mock.MagicMock() node_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(node=node_mock, sort_dir='desc', detail=False) n_shell.do_node_port_list(client_mock, args) client_mock.node.list_ports.assert_called_once_with( node_mock, sort_dir='desc', detail=False) def test_do_node_port_list_wrong_sort_dir(self): client_mock = mock.MagicMock() node_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(node=node_mock, sort_dir='abc', detail=False) self.assertRaises(exceptions.CommandError, n_shell.do_node_port_list, client_mock, args) self.assertFalse(client_mock.node.list_ports.called) def test_do_node_port_list_fields(self): client_mock = mock.MagicMock() node_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(node=node_mock, fields=[['uuid', 'address']]) n_shell.do_node_port_list(client_mock, args) client_mock.node.list_ports.assert_called_once_with( node_mock, fields=['uuid', 'address'], detail=False) def test_do_node_port_list_invalid_fields(self): client_mock = mock.MagicMock() node_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(node=node_mock, fields=[['foo', 'bar']]) self.assertRaises(exceptions.CommandError, n_shell.do_node_port_list, client_mock, args) def test_do_node_get_vendor_passthru_methods(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' n_shell.do_node_get_vendor_passthru_methods(client_mock, args) client_mock.node.get_vendor_passthru_methods.assert_called_once_with( 'node_uuid') def test_do_node_vif_list(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' n_shell.do_node_vif_list(client_mock, args) client_mock.node.vif_list.assert_called_once_with( 'node_uuid') def test_do_node_vif_attach(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.vif_id = 'aaa-aaa' n_shell.do_node_vif_attach(client_mock, args) client_mock.node.vif_attach.assert_called_once_with( 'node_uuid', 'aaa-aaa') def test_do_node_vif_attach_custom_fields(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.vif_id = 'aaa-aaa' args.vif_info = ['aaa=bbb', 'ccc=ddd'] n_shell.do_node_vif_attach(client_mock, args) client_mock.node.vif_attach.assert_called_once_with( 'node_uuid', 'aaa-aaa', aaa='bbb', ccc='ddd') def test_do_node_vif_detach(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node = 'node_uuid' args.vif_id = 'aaa-aaa' n_shell.do_node_vif_detach(client_mock, args) client_mock.node.vif_detach.assert_called_once_with( 'node_uuid', 'aaa-aaa') python-ironicclient-2.2.0/ironicclient/tests/unit/v1/test_driver.py0000666000175100017510000001700513232474343025607 0ustar zuulzuul00000000000000# Copyright 2013 Red Hat, Inc. # 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 mock import testtools from testtools import matchers from ironicclient import exc from ironicclient.tests.unit import utils from ironicclient.v1 import driver DRIVER1 = { 'name': 'fake', 'type': 'dynamic', 'hosts': ['fake-host1', 'fake-host2'], 'default_boot_interface': 'boot', 'default_console_interface': 'console', 'default_deploy_interface': 'deploy', 'default_inspect_interface': 'inspect', 'default_management_interface': 'management', 'default_network_interface': 'network', 'default_power_interface': 'power', 'default_raid_interface': 'raid', 'default_vendor_interface': 'vendor', 'enabled_boot_interfaces': ['boot', 'boot2'], 'enabled_console_interfaces': ['console', 'console2'], 'enabled_deploy_interfaces': ['deploy', 'deploy2'], 'enabled_inspect_interfaces': ['inspect', 'inspect2'], 'enabled_management_interfaces': ['management', 'management2'], 'enabled_network_interfaces': ['network', 'network2'], 'enabled_power_interfaces': ['power', 'power2'], 'enabled_raid_interfaces': ['raid', 'raid2'], 'enabled_vendor_interfaces': ['vendor', 'vendor2'], } DRIVER2 = { 'name': 'pxe_ipminative', 'type': 'classic', 'hosts': ['fake-host1', 'fake-host2'], } DRIVER2_PROPERTIES = { "username": "username. Required.", "password": "password. Optional.", "address": "IP of the node. Required.", } DRIVER_VENDOR_PASSTHRU_METHOD = {"lookup": {"attach": "false", "http_methods": ["POST"], "description": "", "async": "false"}} DRIVER2_RAID_LOGICAL_DISK_PROPERTIES = { "property1": "description1", "property2": "description2", } fake_responses = { '/v1/drivers': { 'GET': ( {}, {'drivers': [DRIVER1]}, ), }, '/v1/drivers/%s' % DRIVER1['name']: { 'GET': ( {}, DRIVER1 ), }, '/v1/drivers/%s/properties' % DRIVER2['name']: { 'GET': ( {}, DRIVER2_PROPERTIES, ), }, '/v1/drivers/%s/vendor_passthru/methods' % DRIVER1['name']: { 'GET': ( {}, DRIVER_VENDOR_PASSTHRU_METHOD, ), }, '/v1/drivers/%s/raid/logical_disk_properties' % DRIVER2['name']: { 'GET': ( {}, DRIVER2_RAID_LOGICAL_DISK_PROPERTIES, ), }, } class DriverManagerTest(testtools.TestCase): def setUp(self): super(DriverManagerTest, self).setUp() self.api = utils.FakeAPI(fake_responses) self.mgr = driver.DriverManager(self.api) def test_driver_list(self): drivers = self.mgr.list() expect = [ ('GET', '/v1/drivers', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(drivers, matchers.HasLength(1)) def test_driver_show(self): driver_ = self.mgr.get(DRIVER1['name']) expect = [ ('GET', '/v1/drivers/%s' % DRIVER1['name'], {}, None) ] self.assertEqual(expect, self.api.calls) driver_attr = {} for attr in DRIVER1.keys(): driver_attr[attr] = getattr(driver_, attr) self.assertEqual(DRIVER1, driver_attr) def test_driver_properties(self): properties = self.mgr.properties(DRIVER2['name']) expect = [ ('GET', '/v1/drivers/%s/properties' % DRIVER2['name'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(DRIVER2_PROPERTIES, properties) def test_driver_raid_logical_disk_properties(self): properties = self.mgr.raid_logical_disk_properties(DRIVER2['name']) expect = [ ('GET', '/v1/drivers/%s/raid/logical_disk_properties' % DRIVER2['name'], {}, None)] self.assertEqual(expect, self.api.calls) self.assertEqual(DRIVER2_RAID_LOGICAL_DISK_PROPERTIES, properties) @mock.patch.object(driver.DriverManager, '_list', autospec=True) def test_driver_raid_logical_disk_properties_indexerror(self, _list_mock): _list_mock.side_effect = IndexError properties = self.mgr.raid_logical_disk_properties(DRIVER2['name']) _list_mock.assert_called_once_with( self.mgr, '/v1/drivers/%s/raid/logical_disk_properties' % DRIVER2['name']) self.assertEqual({}, properties) @mock.patch.object(driver.DriverManager, 'update', autospec=True) def test_vendor_passthru_update(self, update_mock): # For now just mock the tests because vendor-passthru doesn't return # anything to verify. vendor_passthru_args = {'arg1': 'val1'} kwargs = { 'driver_name': 'driver_name', 'method': 'method', 'args': vendor_passthru_args } final_path = 'driver_name/vendor_passthru/method' for http_method in ('POST', 'PUT', 'PATCH'): kwargs['http_method'] = http_method self.mgr.vendor_passthru(**kwargs) update_mock.assert_called_once_with(mock.ANY, final_path, vendor_passthru_args, http_method=http_method) update_mock.reset_mock() @mock.patch.object(driver.DriverManager, 'get', autospec=True) def test_vendor_passthru_get(self, get_mock): kwargs = { 'driver_name': 'driver_name', 'method': 'method', 'http_method': 'GET', } final_path = 'driver_name/vendor_passthru/method' self.mgr.vendor_passthru(**kwargs) get_mock.assert_called_once_with(mock.ANY, final_path) @mock.patch.object(driver.DriverManager, 'delete', autospec=True) def test_vendor_passthru_delete(self, delete_mock): kwargs = { 'driver_name': 'driver_name', 'method': 'method', 'http_method': 'DELETE', } final_path = 'driver_name/vendor_passthru/method' self.mgr.vendor_passthru(**kwargs) delete_mock.assert_called_once_with(mock.ANY, final_path) @mock.patch.object(driver.DriverManager, 'delete', autospec=True) def test_vendor_passthru_unknown_http_method(self, delete_mock): kwargs = { 'driver_name': 'driver_name', 'method': 'method', 'http_method': 'UNKNOWN', } self.assertRaises(exc.InvalidAttribute, self.mgr.vendor_passthru, **kwargs) def test_vendor_passthru_methods(self): vendor_methods = self.mgr.get_vendor_passthru_methods(DRIVER1['name']) expect = [ ('GET', '/v1/drivers/%s/vendor_passthru/methods' % DRIVER1['name'], {}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(DRIVER_VENDOR_PASSTHRU_METHOD, vendor_methods) python-ironicclient-2.2.0/ironicclient/tests/unit/v1/test_chassis_shell.py0000666000175100017510000004064113232474343027142 0ustar zuulzuul00000000000000# Copyright 2013 IBM Corp # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import mock from oslo_utils import uuidutils from ironicclient.common.apiclient import exceptions from ironicclient.common import cliutils from ironicclient.common import utils as commonutils from ironicclient.tests.unit import utils import ironicclient.v1.chassis_shell as c_shell class ChassisShellTest(utils.BaseTestCase): def _get_client_mock_args(self, chassis=None, marker=None, limit=None, sort_dir=None, sort_key=None, detail=False, fields=None, associated=None, maintenance=None, provision_state=None, json=False): args = mock.MagicMock(spec=True) args.chassis = chassis args.marker = marker args.limit = limit args.sort_dir = sort_dir args.sort_key = sort_key args.detail = detail args.fields = fields args.associated = associated args.maintenance = maintenance args.provision_state = provision_state args.json = json return args def test_chassis_show(self): actual = {} fake_print_dict = lambda data, *args, **kwargs: actual.update(data) with mock.patch.object(cliutils, 'print_dict', fake_print_dict): chassis = object() c_shell._print_chassis_show(chassis) exp = ['created_at', 'description', 'extra', 'updated_at', 'uuid'] act = actual.keys() self.assertEqual(sorted(exp), sorted(act)) def test_do_chassis_show_space_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.chassis = ' ' self.assertRaises(exceptions.CommandError, c_shell.do_chassis_show, client_mock, args) def test_do_chassis_show_empty_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.chassis = '' self.assertRaises(exceptions.CommandError, c_shell.do_chassis_show, client_mock, args) def test_do_chassis_show_fields(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.chassis = 'chassis_uuid' args.fields = [['uuid', 'description']] args.json = False c_shell.do_chassis_show(client_mock, args) client_mock.chassis.get.assert_called_once_with( 'chassis_uuid', fields=['uuid', 'description']) def test_do_chassis_show_invalid_fields(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.chassis = 'chassis_uuid' args.fields = [['foo', 'bar']] args.json = False self.assertRaises(exceptions.CommandError, c_shell.do_chassis_show, client_mock, args) def test_do_chassis_list(self): client_mock = mock.MagicMock() args = self._get_client_mock_args() c_shell.do_chassis_list(client_mock, args) client_mock.chassis.list.assert_called_once_with(detail=False) def test_do_chassis_list_detail(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(detail=True) c_shell.do_chassis_list(client_mock, args) client_mock.chassis.list.assert_called_once_with(detail=True) def test_do_chassis_list_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='created_at', detail=False) c_shell.do_chassis_list(client_mock, args) client_mock.chassis.list.assert_called_once_with(sort_key='created_at', detail=False) def test_do_chassis_list_wrong_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='extra', detail=False) self.assertRaises(exceptions.CommandError, c_shell.do_chassis_list, client_mock, args) self.assertFalse(client_mock.chassis.list.called) def test_do_chassis_list_detail_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='created_at', detail=True) c_shell.do_chassis_list(client_mock, args) client_mock.chassis.list.assert_called_once_with(sort_key='created_at', detail=True) def test_do_chassis_list_detail_wrong_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='extra', detail=True) self.assertRaises(exceptions.CommandError, c_shell.do_chassis_list, client_mock, args) self.assertFalse(client_mock.chassis.list.called) def test_do_chassis_list_sort_dir(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_dir='desc', detail=False) c_shell.do_chassis_list(client_mock, args) client_mock.chassis.list.assert_called_once_with(sort_dir='desc', detail=False) def test_do_chassis_list_detail_sort_dir(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_dir='asc', detail=True) c_shell.do_chassis_list(client_mock, args) client_mock.chassis.list.assert_called_once_with(sort_dir='asc', detail=True) def test_do_chassis_list_wrong_sort_dir(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_dir='abc', detail=False) self.assertRaises(exceptions.CommandError, c_shell.do_chassis_list, client_mock, args) self.assertFalse(client_mock.chassis.list.called) def test_do_chassis_list_fields(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(fields=[['uuid', 'description']]) c_shell.do_chassis_list(client_mock, args) client_mock.chassis.list.assert_called_once_with( fields=['uuid', 'description'], detail=False) def test_do_chassis_list_invalid_fields(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(fields=[['foo', 'bar']]) self.assertRaises(exceptions.CommandError, c_shell.do_chassis_list, client_mock, args) def test_do_chassis_node_list(self): client_mock = mock.MagicMock() chassis_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(chassis=chassis_mock) c_shell.do_chassis_node_list(client_mock, args) client_mock.chassis.list_nodes.assert_called_once_with( chassis_mock, detail=False) def test_do_chassis_node_list_details(self): client_mock = mock.MagicMock() chassis_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(chassis=chassis_mock, detail=True) c_shell.do_chassis_node_list(client_mock, args) client_mock.chassis.list_nodes.assert_called_once_with( chassis_mock, detail=True) def test_do_chassis_node_list_sort_key(self): client_mock = mock.MagicMock() chassis_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(chassis_mock, sort_key='created_at', detail=False) c_shell.do_chassis_node_list(client_mock, args) client_mock.chassis.list_nodes.assert_called_once_with( chassis_mock, sort_key='created_at', detail=False) def test_do_chassis_node_list_wrong_sort_key(self): client_mock = mock.MagicMock() chassis_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(chassis_mock, sort_key='extra', detail=False) self.assertRaises(exceptions.CommandError, c_shell.do_chassis_node_list, client_mock, args) self.assertFalse(client_mock.chassis.list_nodes.called) def test_do_chassis_node_list_detail_sort_key(self): client_mock = mock.MagicMock() chassis_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(chassis_mock, sort_key='created_at', detail=True) c_shell.do_chassis_node_list(client_mock, args) client_mock.chassis.list_nodes.assert_called_once_with( chassis_mock, sort_key='created_at', detail=True) def test_do_chassis_node_list_detail_wrong_sort_key(self): client_mock = mock.MagicMock() chassis_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(chassis_mock, sort_key='extra', detail=True) self.assertRaises(exceptions.CommandError, c_shell.do_chassis_node_list, client_mock, args) self.assertFalse(client_mock.chassis.list_nodes.called) def test_do_chassis_node_list_sort_dir(self): client_mock = mock.MagicMock() chassis_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(chassis_mock, sort_dir='desc', detail=False) c_shell.do_chassis_node_list(client_mock, args) client_mock.chassis.list_nodes.assert_called_once_with( chassis_mock, sort_dir='desc', detail=False) def test_do_chassis_node_list_detail_sort_dir(self): client_mock = mock.MagicMock() chassis_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(chassis_mock, sort_dir='asc', detail=True) c_shell.do_chassis_node_list(client_mock, args) client_mock.chassis.list_nodes.assert_called_once_with( chassis_mock, sort_dir='asc', detail=True) def test_do_chassis_node_list_wrong_sort_dir(self): client_mock = mock.MagicMock() chassis_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(chassis_mock, sort_dir='abc', detail=False) self.assertRaises(exceptions.CommandError, c_shell.do_chassis_node_list, client_mock, args) self.assertFalse(client_mock.chassis.list_nodes.called) def test_do_chassis_node_list_fields(self): client_mock = mock.MagicMock() chassis_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(chassis=chassis_mock, fields=[['uuid', 'power_state']]) c_shell.do_chassis_node_list(client_mock, args) client_mock.chassis.list_nodes.assert_called_once_with( chassis_mock, fields=['uuid', 'power_state'], detail=False) def test_do_chassis_node_list_associated(self): client_mock = mock.MagicMock() chassis_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(chassis=chassis_mock, associated=True) c_shell.do_chassis_node_list(client_mock, args) client_mock.chassis.list_nodes.assert_called_once_with( chassis_mock, associated=True, detail=False) def test_do_chassis_node_list_maintenance(self): client_mock = mock.MagicMock() chassis_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(chassis=chassis_mock, maintenance=True) c_shell.do_chassis_node_list(client_mock, args) client_mock.chassis.list_nodes.assert_called_once_with( chassis_mock, maintenance=True, detail=False) def test_do_chassis_node_list_provision_state(self): client_mock = mock.MagicMock() chassis_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(chassis=chassis_mock, provision_state='wait call-back') c_shell.do_chassis_node_list(client_mock, args) client_mock.chassis.list_nodes.assert_called_once_with( chassis_mock, provision_state='wait call-back', detail=False) def test_do_chassis_node_list_invalid_fields(self): client_mock = mock.MagicMock() chassis_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(chassis=chassis_mock, fields=[['foo', 'bar']]) self.assertRaises(exceptions.CommandError, c_shell.do_chassis_node_list, client_mock, args) def test_do_chassis_create(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.json = False c_shell.do_chassis_create(client_mock, args) client_mock.chassis.create.assert_called_once_with() def test_do_chassis_create_with_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.uuid = uuidutils.generate_uuid() args.json = False c_shell.do_chassis_create(client_mock, args) client_mock.chassis.create.assert_called_once_with(uuid=args.uuid) def test_do_chassis_create_valid_field(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.extra = ["key1=val1", "key2=val2"] args.description = 'desc' args.json = False c_shell.do_chassis_create(client_mock, args) client_mock.chassis.create.assert_called_once_with(extra={ 'key1': 'val1', 'key2': 'val2'}, description='desc') def test_do_chassis_create_wrong_extra_field(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.extra = ["foo"] args.description = 'desc' args.json = False self.assertRaises(exceptions.CommandError, c_shell.do_chassis_create, client_mock, args) def test_do_chassis_delete(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.chassis = ['chassis_uuid'] c_shell.do_chassis_delete(client_mock, args) client_mock.chassis.delete.assert_called_once_with('chassis_uuid') def test_do_chassis_delete_multiple(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.chassis = ['chassis_uuid1', 'chassis_uuid2'] c_shell.do_chassis_delete(client_mock, args) client_mock.chassis.delete.assert_has_calls([ mock.call('chassis_uuid1'), mock.call('chassis_uuid2')]) def test_do_chassis_update(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.chassis = 'chassis_uuid' args.op = 'add' args.attributes = [['arg1=val1', 'arg2=val2']] args.json = False c_shell.do_chassis_update(client_mock, args) patch = commonutils.args_array_to_patch(args.op, args.attributes[0]) client_mock.chassis.update.assert_called_once_with('chassis_uuid', patch) def test_do_chassis_update_wrong_op(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.chassis = 'chassis_uuid' args.op = 'foo' args.attributes = [['arg1=val1', 'arg2=val2']] self.assertRaises(exceptions.CommandError, c_shell.do_chassis_update, client_mock, args) self.assertFalse(client_mock.chassis.update.called) python-ironicclient-2.2.0/ironicclient/tests/unit/v1/test_port_shell.py0000666000175100017510000003053513232474343026472 0ustar zuulzuul00000000000000# Copyright 2013 IBM Corp # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import mock from oslo_utils import uuidutils from ironicclient.common.apiclient import exceptions from ironicclient.common import cliutils from ironicclient.common import utils as commonutils from ironicclient.tests.unit import utils import ironicclient.v1.port_shell as p_shell class PortShellTest(utils.BaseTestCase): def test_port_show(self): actual = {} fake_print_dict = lambda data, *args, **kwargs: actual.update(data) with mock.patch.object(cliutils, 'print_dict', fake_print_dict): port = object() p_shell._print_port_show(port) exp = ['address', 'created_at', 'extra', 'node_uuid', 'physical_network', 'updated_at', 'uuid', 'pxe_enabled', 'local_link_connection', 'internal_info', 'portgroup_uuid'] act = actual.keys() self.assertEqual(sorted(exp), sorted(act)) def test_do_port_show(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.port = 'port_uuid' args.address = False args.fields = None args.json = False p_shell.do_port_show(client_mock, args) client_mock.port.get.assert_called_once_with('port_uuid', fields=None) # assert get_by_address() wasn't called self.assertFalse(client_mock.port.get_by_address.called) def test_do_port_show_space_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.port = ' ' args.address = False args.json = False self.assertRaises(exceptions.CommandError, p_shell.do_port_show, client_mock, args) def test_do_port_show_empty_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.port = '' args.address = False args.json = False self.assertRaises(exceptions.CommandError, p_shell.do_port_show, client_mock, args) def test_do_port_show_by_address(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.port = 'port_address' args.address = True args.fields = None args.json = False p_shell.do_port_show(client_mock, args) client_mock.port.get_by_address.assert_called_once_with('port_address', fields=None) # assert get() wasn't called self.assertFalse(client_mock.port.get.called) def test_do_port_show_fields(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.port = 'port_uuid' args.address = False args.fields = [['uuid', 'address']] args.json = False p_shell.do_port_show(client_mock, args) client_mock.port.get.assert_called_once_with( 'port_uuid', fields=['uuid', 'address']) def test_do_port_show_invalid_fields(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.port = 'port_uuid' args.address = False args.fields = [['foo', 'bar']] args.json = False self.assertRaises(exceptions.CommandError, p_shell.do_port_show, client_mock, args) def test_do_port_update(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.port = 'port_uuid' args.op = 'add' args.attributes = [['arg1=val1', 'arg2=val2']] args.json = False p_shell.do_port_update(client_mock, args) patch = commonutils.args_array_to_patch(args.op, args.attributes[0]) client_mock.port.update.assert_called_once_with('port_uuid', patch) def test_do_port_update_wrong_op(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.port = 'port_uuid' args.op = 'foo' args.attributes = [['arg1=val1', 'arg2=val2']] args.json = False self.assertRaises(exceptions.CommandError, p_shell.do_port_update, client_mock, args) self.assertFalse(client_mock.port.update.called) def _get_client_mock_args(self, address=None, marker=None, limit=None, sort_dir=None, sort_key=None, detail=False, fields=None, json=False): args = mock.MagicMock(spec=True) args.address = address args.marker = marker args.limit = limit args.sort_dir = sort_dir args.sort_key = sort_key args.detail = detail args.fields = fields args.json = json return args def test_do_port_list(self): client_mock = mock.MagicMock() args = self._get_client_mock_args() p_shell.do_port_list(client_mock, args) client_mock.port.list.assert_called_once_with(detail=False) def test_do_port_list_detail(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(detail=True) p_shell.do_port_list(client_mock, args) client_mock.port.list.assert_called_once_with(detail=True) def test_do_port_list_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='uuid', detail=False) p_shell.do_port_list(client_mock, args) client_mock.port.list.assert_called_once_with(sort_key='uuid', detail=False) def test_do_port_list_wrong_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='node_uuid', detail=False) self.assertRaises(exceptions.CommandError, p_shell.do_port_list, client_mock, args) self.assertFalse(client_mock.port.list.called) def test_do_port_list_detail_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='uuid', detail=True) p_shell.do_port_list(client_mock, args) client_mock.port.list.assert_called_once_with(sort_key='uuid', detail=True) def test_do_port_list_detail_wrong_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='node_uuid', detail=True) self.assertRaises(exceptions.CommandError, p_shell.do_port_list, client_mock, args) self.assertFalse(client_mock.port.list.called) def test_do_port_list_fields(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(fields=[['uuid', 'address']]) p_shell.do_port_list(client_mock, args) client_mock.port.list.assert_called_once_with( fields=['uuid', 'address'], detail=False) def test_do_port_list_invalid_fields(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(fields=[['foo', 'bar']]) self.assertRaises(exceptions.CommandError, p_shell.do_port_list, client_mock, args) def test_do_port_list_sort_dir(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_dir='desc', detail=False) p_shell.do_port_list(client_mock, args) client_mock.port.list.assert_called_once_with(sort_dir='desc', detail=False) def test_do_port_list_detail_sort_dir(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_dir='asc', detail=True) p_shell.do_port_list(client_mock, args) client_mock.port.list.assert_called_once_with(sort_dir='asc', detail=True) def test_do_port_list_wrong_sort_dir(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_dir='abc', detail=False) self.assertRaises(exceptions.CommandError, p_shell.do_port_list, client_mock, args) self.assertFalse(client_mock.port.list.called) def test_do_port_create(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.json = False p_shell.do_port_create(client_mock, args) client_mock.port.create.assert_called_once_with() def test_do_port_create_with_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.uuid = uuidutils.generate_uuid() args.json = False p_shell.do_port_create(client_mock, args) client_mock.port.create.assert_called_once_with(uuid=args.uuid) def test_do_port_create_valid_fields_values(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.address = 'address' args.node_uuid = 'uuid' args.extra = ["key1=val1", "key2=val2"] args.json = False p_shell.do_port_create(client_mock, args) client_mock.port.create.assert_called_once_with( address='address', node_uuid='uuid', extra={'key1': 'val1', 'key2': 'val2'}) def test_do_port_create_invalid_extra_fields_values(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.address = 'address' args.node_uuid = 'uuid' args.extra = ["foo"] args.json = False self.assertRaises(exceptions.CommandError, p_shell.do_port_create, client_mock, args) def test_do_port_create_portgroup_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.address = 'address' args.node_uuid = 'uuid' args.portgroup_uuid = 'portgroup-uuid' args.json = False p_shell.do_port_create(client_mock, args) client_mock.port.create.assert_called_once_with( address='address', node_uuid='uuid', portgroup_uuid='portgroup-uuid') def test_do_port_create_physical_network(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.address = 'address' args.node_uuid = 'uuid' args.physical_network = 'physnet1' args.json = False p_shell.do_port_create(client_mock, args) client_mock.port.create.assert_called_once_with( address='address', node_uuid='uuid', physical_network='physnet1') def test_do_port_delete(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.port = ['port_uuid'] p_shell.do_port_delete(client_mock, args) client_mock.port.delete.assert_called_once_with('port_uuid') def test_do_port_delete_multiple(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.port = ['port_uuid', 'port_uuid2'] p_shell.do_port_delete(client_mock, args) client_mock.port.delete.assert_has_calls( [mock.call('port_uuid'), mock.call('port_uuid2')]) def test_do_port_delete_multiple_with_exception(self): client_mock = mock.MagicMock() client_mock.port.delete.side_effect = ( [exceptions.ClientException, None]) args = mock.MagicMock() args.port = ['port_uuid', 'port_uuid2'] self.assertRaises(exceptions.ClientException, p_shell.do_port_delete, client_mock, args) client_mock.port.delete.assert_has_calls( [mock.call('port_uuid'), mock.call('port_uuid2')]) python-ironicclient-2.2.0/ironicclient/tests/unit/v1/test_create_resources.py0000666000175100017510000005446313232474343027662 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 jsonschema import mock import six import six.moves.builtins as __builtin__ from ironicclient import exc from ironicclient.tests.unit import utils from ironicclient.v1 import create_resources valid_json = { "chassis": [{ "description": "testing resources import", "nodes": [{ "driver": "agent_ssh", "extra": { "kv1": True, "vk1": None }, "properties": { "a": "c" }, "ports": [{ "address": "00:00:00:00:00:01", "extra": { "a": "b" } }], "portgroups": [{ "address": "00:00:00:00:00:02", "name": "portgroup1", "ports": [{ "address": "00:00:00:00:00:03" }] }] }] }], "nodes": [{ "driver": "fake", "driver_info": { "fake_key": "fake", "dict_prop": { "a": "b" }, "arr_prop": [ 1, 2, 3 ] }, "chassis_uuid": "10f99593-b8c2-4fcb-8858-494c1a47cee6" }] } ironic_pov_invalid_json = { "nodes": [{ "driver": "non_existent", "ports": [{ "address": "invalid_address" }] }] } schema_pov_invalid_json = {"meow": "woof!"} class CreateSchemaTest(utils.BaseTestCase): def test_schema(self): schema = create_resources._CREATE_SCHEMA jsonschema.validate(valid_json, schema) jsonschema.validate(ironic_pov_invalid_json, schema) self.assertRaises(jsonschema.ValidationError, jsonschema.validate, schema_pov_invalid_json, schema) class CreateResourcesTest(utils.BaseTestCase): def setUp(self): super(CreateResourcesTest, self).setUp() self.client = mock.MagicMock() @mock.patch.object(create_resources, 'create_nodes', autospec=True) @mock.patch.object(create_resources, 'create_chassis', autospec=True) @mock.patch.object(jsonschema, 'validate', autospec=True) @mock.patch.object(create_resources, 'load_from_file', side_effect=[valid_json], autospec=True) def test_create_resources( self, mock_load, mock_validate, mock_chassis, mock_nodes): resources_files = ['file.json'] create_resources.create_resources(self.client, resources_files) mock_load.assert_has_calls([ mock.call('file.json') ]) mock_validate.assert_called_once_with(valid_json, mock.ANY) mock_chassis.assert_called_once_with(self.client, valid_json['chassis']) mock_nodes.assert_called_once_with(self.client, valid_json['nodes']) @mock.patch.object(create_resources, 'create_nodes', autospec=True) @mock.patch.object(create_resources, 'create_chassis', autospec=True) @mock.patch.object(jsonschema, 'validate', autospec=True) @mock.patch.object(create_resources, 'load_from_file', side_effect=exc.ClientException, autospec=True) def test_create_resources_cannot_read_schema( self, mock_load, mock_validate, mock_chassis, mock_nodes): resources_files = ['file.json'] self.assertRaises(exc.ClientException, create_resources.create_resources, self.client, resources_files) mock_load.assert_called_once_with('file.json') self.assertFalse(mock_validate.called) self.assertFalse(mock_chassis.called) self.assertFalse(mock_nodes.called) @mock.patch.object(create_resources, 'create_nodes', autospec=True) @mock.patch.object(create_resources, 'create_chassis', autospec=True) @mock.patch.object(jsonschema, 'validate', side_effect=jsonschema.ValidationError(''), autospec=True) @mock.patch.object(create_resources, 'load_from_file', side_effect=[schema_pov_invalid_json], autospec=True) def test_create_resources_validation_fails( self, mock_load, mock_validate, mock_chassis, mock_nodes): resources_files = ['file.json'] self.assertRaises(exc.ClientException, create_resources.create_resources, self.client, resources_files) mock_load.assert_has_calls([ mock.call('file.json') ]) mock_validate.assert_called_once_with(schema_pov_invalid_json, mock.ANY) self.assertFalse(mock_chassis.called) self.assertFalse(mock_nodes.called) @mock.patch.object(create_resources, 'create_nodes', autospec=True) @mock.patch.object(create_resources, 'create_chassis', autospec=True) @mock.patch.object(jsonschema, 'validate', side_effect=[None, jsonschema.ValidationError('')], autospec=True) @mock.patch.object(create_resources, 'load_from_file', side_effect=[valid_json, schema_pov_invalid_json], autospec=True) def test_create_resources_validation_fails_multiple( self, mock_load, mock_validate, mock_chassis, mock_nodes): resources_files = ['file.json', 'file2.json'] self.assertRaises(exc.ClientException, create_resources.create_resources, self.client, resources_files) mock_load.assert_has_calls([ mock.call('file.json'), mock.call('file2.json') ]) mock_validate.assert_has_calls([ mock.call(valid_json, mock.ANY), mock.call(schema_pov_invalid_json, mock.ANY) ]) self.assertFalse(mock_chassis.called) self.assertFalse(mock_nodes.called) @mock.patch.object(create_resources, 'create_nodes', autospec=True) @mock.patch.object(create_resources, 'create_chassis', autospec=True) @mock.patch.object(jsonschema, 'validate', autospec=True) @mock.patch.object(create_resources, 'load_from_file', side_effect=[ironic_pov_invalid_json], autospec=True) def test_create_resources_ironic_fails_to_create( self, mock_load, mock_validate, mock_chassis, mock_nodes): mock_nodes.return_value = [exc.ClientException('cannot create that')] mock_chassis.return_value = [] resources_files = ['file.json'] self.assertRaises(exc.ClientException, create_resources.create_resources, self.client, resources_files) mock_load.assert_has_calls([ mock.call('file.json') ]) mock_validate.assert_called_once_with(ironic_pov_invalid_json, mock.ANY) mock_chassis.assert_called_once_with(self.client, []) mock_nodes.assert_called_once_with( self.client, ironic_pov_invalid_json['nodes']) class LoadFromFileTest(utils.BaseTestCase): @mock.patch.object(__builtin__, 'open', mock.mock_open(read_data='{"a": "b"}')) def test_load_json(self): fname = 'abc.json' res = create_resources.load_from_file(fname) self.assertEqual({'a': 'b'}, res) @mock.patch.object(__builtin__, 'open', mock.mock_open(read_data='{"a": "b"}')) def test_load_unknown_extension(self): fname = 'abc' self.assertRaisesRegex(exc.ClientException, 'must have .json or .yaml extension', create_resources.load_from_file, fname) @mock.patch.object(__builtin__, 'open', autospec=True) def test_load_ioerror(self, mock_open): mock_open.side_effect = IOError('file does not exist') fname = 'abc.json' self.assertRaisesRegex(exc.ClientException, 'Cannot read file', create_resources.load_from_file, fname) @mock.patch.object(__builtin__, 'open', mock.mock_open(read_data='{{bbb')) def test_load_incorrect_json(self): fname = 'abc.json' self.assertRaisesRegex( exc.ClientException, 'File "%s" is invalid' % fname, create_resources.load_from_file, fname) @mock.patch.object(__builtin__, 'open', mock.mock_open(read_data='---\na: b')) def test_load_yaml(self): fname = 'abc.yaml' res = create_resources.load_from_file(fname) self.assertEqual({'a': 'b'}, res) @mock.patch.object(__builtin__, 'open', mock.mock_open(read_data='---\na-: - b')) def test_load_incorrect_yaml(self): fname = 'abc.yaml' self.assertRaisesRegex( exc.ClientException, 'File "%s" is invalid' % fname, create_resources.load_from_file, fname) class CreateMethodsTest(utils.BaseTestCase): def setUp(self): super(CreateMethodsTest, self).setUp() self.client = mock.MagicMock() def test_create_single_node(self): params = {'driver': 'fake'} self.client.node.create.return_value = mock.Mock(uuid='uuid') self.assertEqual( ('uuid', None), create_resources.create_single_node(self.client, **params) ) self.client.node.create.assert_called_once_with(driver='fake') def test_create_single_node_with_ports(self): params = {'driver': 'fake', 'ports': ['some ports here']} self.client.node.create.return_value = mock.Mock(uuid='uuid') self.assertEqual( ('uuid', None), create_resources.create_single_node(self.client, **params) ) self.client.node.create.assert_called_once_with(driver='fake') def test_create_single_node_with_portgroups(self): params = {'driver': 'fake', 'portgroups': ['some portgroups']} self.client.node.create.return_value = mock.Mock(uuid='uuid') self.assertEqual( ('uuid', None), create_resources.create_single_node(self.client, **params) ) self.client.node.create.assert_called_once_with(driver='fake') def test_create_single_node_raises_client_exception(self): params = {'driver': 'fake'} e = exc.ClientException('foo') self.client.node.create.side_effect = e res, err = create_resources.create_single_node(self.client, **params) self.assertIsNone(res) self.assertIsInstance(err, exc.ClientException) self.assertIn('Unable to create the node', str(err)) self.client.node.create.assert_called_once_with(driver='fake') def test_create_single_node_raises_invalid_exception(self): params = {'driver': 'fake'} e = exc.InvalidAttribute('foo') self.client.node.create.side_effect = e res, err = create_resources.create_single_node(self.client, **params) self.assertIsNone(res) self.assertIsInstance(err, exc.InvalidAttribute) self.assertIn('Cannot create the node with attributes', str(err)) self.client.node.create.assert_called_once_with(driver='fake') def test_create_single_port(self): params = {'address': 'fake-address', 'node_uuid': 'fake-node-uuid'} self.client.port.create.return_value = mock.Mock(uuid='fake-port-uuid') self.assertEqual( ('fake-port-uuid', None), create_resources.create_single_port(self.client, **params) ) self.client.port.create.assert_called_once_with(**params) def test_create_single_portgroup(self): params = {'address': 'fake-address', 'node_uuid': 'fake-node-uuid'} self.client.portgroup.create.return_value = mock.Mock( uuid='fake-portgroup-uuid') self.assertEqual( ('fake-portgroup-uuid', None), create_resources.create_single_portgroup(self.client, **params) ) self.client.portgroup.create.assert_called_once_with(**params) def test_create_single_portgroup_with_ports(self): params = {'ports': ['some ports'], 'node_uuid': 'fake-node-uuid'} self.client.portgroup.create.return_value = mock.Mock( uuid='fake-portgroup-uuid') self.assertEqual( ('fake-portgroup-uuid', None), create_resources.create_single_portgroup( self.client, **params) ) self.client.portgroup.create.assert_called_once_with( node_uuid='fake-node-uuid') def test_create_single_chassis(self): self.client.chassis.create.return_value = mock.Mock(uuid='uuid') self.assertEqual( ('uuid', None), create_resources.create_single_chassis(self.client) ) self.client.chassis.create.assert_called_once_with() def test_create_single_chassis_with_nodes(self): params = {'nodes': ['some nodes here']} self.client.chassis.create.return_value = mock.Mock(uuid='uuid') self.assertEqual( ('uuid', None), create_resources.create_single_chassis(self.client, **params) ) self.client.chassis.create.assert_called_once_with() def test_create_ports(self): port = {'address': 'fake-address'} port_with_node_uuid = port.copy() port_with_node_uuid.update(node_uuid='fake-node-uuid') self.client.port.create.return_value = mock.Mock(uuid='uuid') self.assertEqual([], create_resources.create_ports(self.client, [port], 'fake-node-uuid')) self.client.port.create.assert_called_once_with(**port_with_node_uuid) def test_create_ports_two_node_uuids(self): port = {'address': 'fake-address', 'node_uuid': 'node-uuid-1'} errs = create_resources.create_ports(self.client, [port], 'node-uuid-2') self.assertIsInstance(errs[0], exc.ClientException) self.assertEqual(1, len(errs)) self.assertFalse(self.client.port.create.called) def test_create_ports_two_portgroup_uuids(self): port = {'address': 'fake-address', 'node_uuid': 'node-uuid-1', 'portgroup_uuid': 'pg-uuid-1'} errs = create_resources.create_ports(self.client, [port], 'node-uuid-1', 'pg-uuid-2') self.assertEqual(1, len(errs)) self.assertIsInstance(errs[0], exc.ClientException) self.assertIn('port group', six.text_type(errs[0])) self.assertFalse(self.client.port.create.called) @mock.patch.object(create_resources, 'create_portgroups', autospec=True) @mock.patch.object(create_resources, 'create_ports', autospec=True) def test_create_nodes(self, mock_create_ports, mock_create_portgroups): node = {'driver': 'fake', 'ports': ['list of ports'], 'portgroups': ['list of portgroups']} self.client.node.create.return_value = mock.Mock(uuid='uuid') self.assertEqual([], create_resources.create_nodes(self.client, [node])) self.client.node.create.assert_called_once_with(driver='fake') mock_create_ports.assert_called_once_with( self.client, ['list of ports'], node_uuid='uuid') mock_create_portgroups.assert_called_once_with( self.client, ['list of portgroups'], node_uuid='uuid') @mock.patch.object(create_resources, 'create_portgroups', autospec=True) @mock.patch.object(create_resources, 'create_ports', autospec=True) def test_create_nodes_exception(self, mock_create_ports, mock_create_portgroups): node = {'driver': 'fake', 'ports': ['list of ports'], 'portgroups': ['list of portgroups']} self.client.node.create.side_effect = exc.ClientException('bar') errs = create_resources.create_nodes(self.client, [node]) self.assertIsInstance(errs[0], exc.ClientException) self.assertEqual(1, len(errs)) self.client.node.create.assert_called_once_with(driver='fake') self.assertFalse(mock_create_ports.called) self.assertFalse(mock_create_portgroups.called) @mock.patch.object(create_resources, 'create_ports', autospec=True) def test_create_nodes_two_chassis_uuids(self, mock_create_ports): node = {'driver': 'fake', 'ports': ['list of ports'], 'chassis_uuid': 'chassis-uuid-1'} errs = create_resources.create_nodes(self.client, [node], chassis_uuid='chassis-uuid-2') self.assertFalse(self.client.node.create.called) self.assertFalse(mock_create_ports.called) self.assertEqual(1, len(errs)) self.assertIsInstance(errs[0], exc.ClientException) @mock.patch.object(create_resources, 'create_portgroups', autospec=True) @mock.patch.object(create_resources, 'create_ports', autospec=True) def test_create_nodes_no_ports_portgroups(self, mock_create_ports, mock_create_portgroups): node = {'driver': 'fake'} self.client.node.create.return_value = mock.Mock(uuid='uuid') self.assertEqual([], create_resources.create_nodes(self.client, [node])) self.client.node.create.assert_called_once_with(driver='fake') self.assertFalse(mock_create_ports.called) self.assertFalse(mock_create_portgroups.called) @mock.patch.object(create_resources, 'create_nodes', autospec=True) def test_create_chassis(self, mock_create_nodes): chassis = {'description': 'fake', 'nodes': ['list of nodes']} self.client.chassis.create.return_value = mock.Mock(uuid='uuid') self.assertEqual([], create_resources.create_chassis(self.client, [chassis])) self.client.chassis.create.assert_called_once_with(description='fake') mock_create_nodes.assert_called_once_with( self.client, ['list of nodes'], chassis_uuid='uuid') @mock.patch.object(create_resources, 'create_nodes', autospec=True) def test_create_chassis_exception(self, mock_create_nodes): chassis = {'description': 'fake', 'nodes': ['list of nodes']} self.client.chassis.create.side_effect = exc.ClientException('bar') errs = create_resources.create_chassis(self.client, [chassis]) self.client.chassis.create.assert_called_once_with(description='fake') self.assertFalse(mock_create_nodes.called) self.assertEqual(1, len(errs)) self.assertIsInstance(errs[0], exc.ClientException) @mock.patch.object(create_resources, 'create_nodes', autospec=True) def test_create_chassis_no_nodes(self, mock_create_nodes): chassis = {'description': 'fake'} self.client.chassis.create.return_value = mock.Mock(uuid='uuid') self.assertEqual([], create_resources.create_chassis(self.client, [chassis])) self.client.chassis.create.assert_called_once_with(description='fake') self.assertFalse(mock_create_nodes.called) @mock.patch.object(create_resources, 'create_ports', autospec=True) def test_create_portgroups(self, mock_create_ports): portgroup = {'name': 'fake', 'ports': ['list of ports']} portgroup_posted = {'name': 'fake', 'node_uuid': 'fake-node-uuid'} self.client.portgroup.create.return_value = mock.Mock(uuid='uuid') self.assertEqual([], create_resources.create_portgroups( self.client, [portgroup], node_uuid='fake-node-uuid')) self.client.portgroup.create.assert_called_once_with( **portgroup_posted) mock_create_ports.assert_called_once_with( self.client, ['list of ports'], node_uuid='fake-node-uuid', portgroup_uuid='uuid') @mock.patch.object(create_resources, 'create_ports', autospec=True) def test_create_portgroups_exception(self, mock_create_ports): portgroup = {'name': 'fake', 'ports': ['list of ports']} portgroup_posted = {'name': 'fake', 'node_uuid': 'fake-node-uuid'} self.client.portgroup.create.side_effect = exc.ClientException('bar') errs = create_resources.create_portgroups( self.client, [portgroup], node_uuid='fake-node-uuid') self.client.portgroup.create.assert_called_once_with( **portgroup_posted) self.assertFalse(mock_create_ports.called) self.assertEqual(1, len(errs)) self.assertIsInstance(errs[0], exc.ClientException) @mock.patch.object(create_resources, 'create_ports', autospec=True) def test_create_portgroups_two_node_uuids(self, mock_create_ports): portgroup = {'name': 'fake', 'node_uuid': 'fake-node-uuid-1', 'ports': ['list of ports']} self.client.portgroup.create.side_effect = exc.ClientException('bar') errs = create_resources.create_portgroups( self.client, [portgroup], node_uuid='fake-node-uuid-2') self.assertFalse(self.client.portgroup.create.called) self.assertFalse(mock_create_ports.called) self.assertEqual(1, len(errs)) self.assertIsInstance(errs[0], exc.ClientException) @mock.patch.object(create_resources, 'create_ports', autospec=True) def test_create_portgroups_no_ports(self, mock_create_ports): portgroup = {'name': 'fake'} portgroup_posted = {'name': 'fake', 'node_uuid': 'fake-node-uuid'} self.client.portgroup.create.return_value = mock.Mock(uuid='uuid') self.assertEqual([], create_resources.create_portgroups( self.client, [portgroup], node_uuid='fake-node-uuid')) self.client.portgroup.create.assert_called_once_with( **portgroup_posted) self.assertFalse(mock_create_ports.called) python-ironicclient-2.2.0/ironicclient/tests/unit/v1/test_client.py0000666000175100017510000001164613232474343025577 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 mock from ironicclient.common import filecache from ironicclient.common import http from ironicclient import exc from ironicclient.tests.unit import utils from ironicclient.v1 import client @mock.patch.object(http, '_construct_http_client', autospec=True) class ClientTest(utils.BaseTestCase): def test_client_user_api_version(self, http_client_mock): endpoint = 'http://ironic:6385' token = 'safe_token' os_ironic_api_version = '1.15' client.Client(endpoint, token=token, os_ironic_api_version=os_ironic_api_version) http_client_mock.assert_called_once_with( endpoint, token=token, os_ironic_api_version=os_ironic_api_version, api_version_select_state='user') def test_client_user_api_version_with_downgrade(self, http_client_mock): endpoint = 'http://ironic:6385' token = 'safe_token' os_ironic_api_version = '1.15' client.Client(endpoint, token=token, os_ironic_api_version=os_ironic_api_version, allow_api_version_downgrade=True) http_client_mock.assert_called_once_with( endpoint, token=token, os_ironic_api_version=os_ironic_api_version, api_version_select_state='default') def test_client_user_api_version_latest_with_downgrade(self, http_client_mock): endpoint = 'http://ironic:6385' token = 'safe_token' os_ironic_api_version = 'latest' self.assertRaises(ValueError, client.Client, endpoint, token=token, allow_api_version_downgrade=True, os_ironic_api_version=os_ironic_api_version) @mock.patch.object(filecache, 'retrieve_data', autospec=True) def test_client_cache_api_version(self, cache_mock, http_client_mock): endpoint = 'http://ironic:6385' token = 'safe_token' os_ironic_api_version = '1.15' cache_mock.return_value = os_ironic_api_version client.Client(endpoint, token=token) cache_mock.assert_called_once_with(host='ironic', port='6385') http_client_mock.assert_called_once_with( endpoint, token=token, os_ironic_api_version=os_ironic_api_version, api_version_select_state='cached') @mock.patch.object(filecache, 'retrieve_data', autospec=True) def test_client_default_api_version(self, cache_mock, http_client_mock): endpoint = 'http://ironic:6385' token = 'safe_token' cache_mock.return_value = None client.Client(endpoint, token=token) cache_mock.assert_called_once_with(host='ironic', port='6385') http_client_mock.assert_called_once_with( endpoint, token=token, os_ironic_api_version=client.DEFAULT_VER, api_version_select_state='default') def test_client_cache_version_no_endpoint_as_arg(self, http_client_mock): self.assertRaises(exc.EndpointException, client.Client, session='fake_session', insecure=True, endpoint_override='http://ironic:6385') def test_client_initialized_managers(self, http_client_mock): cl = client.Client('http://ironic:6385', token='safe_token', os_ironic_api_version='1.15') self.assertIsInstance(cl.node, client.node.NodeManager) self.assertIsInstance(cl.port, client.port.PortManager) self.assertIsInstance(cl.driver, client.driver.DriverManager) self.assertIsInstance(cl.chassis, client.chassis.ChassisManager) def test_negotiate_api_version(self, http_client_mock): endpoint = 'http://ironic:6385' token = 'safe_token' os_ironic_api_version = 'latest' cl = client.Client(endpoint, token=token, os_ironic_api_version=os_ironic_api_version) cl.negotiate_api_version() http_client_mock.assert_called_once_with( endpoint, api_version_select_state='user', os_ironic_api_version='latest', token=token) # TODO(TheJulia): We should verify that negotiate_version # is being called in the client and returns a version, # although mocking might need to be restrutured to # properly achieve that. python-ironicclient-2.2.0/ironicclient/tests/unit/v1/test_portgroup.py0000666000175100017510000003443113232474343026357 0ustar zuulzuul00000000000000# Copyright 2016 Mirantis, Inc. # 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 copy import mock import testtools from ironicclient.common.apiclient import exceptions from ironicclient.tests.unit import utils import ironicclient.v1.portgroup import ironicclient.v1.portgroup_shell as pg_shell PORTGROUP = {'uuid': '11111111-2222-3333-4444-555555555555', 'name': 'Portgroup-name', 'node_uuid': '66666666-7777-8888-9999-000000000000', 'address': 'AA:BB:CC:DD:EE:FF', 'extra': {}} PORTGROUP2 = {'uuid': '55555555-4444-3333-2222-111111111111', 'name': 'Portgroup2-name', 'node_uuid': '66666666-7777-8888-9999-000000000000', 'address': 'AA:AA:AA:BB:BB:BB', 'extra': {}} NODE1 = {'uuid': '66666666-7777-8888-9999-000000000000', 'chassis_uuid': 'aaaaaaaa-1111-bbbb-2222-cccccccccccc', 'maintenance': False, 'provision_state': 'available', 'driver': 'fake', 'driver_info': {'user': 'foo', 'password': 'bar'}, 'properties': {'num_cpu': 4}, 'name': 'fake-node-1', 'resource_class': 'foo', 'extra': {}} PORT = {'uuid': '11111111-2222-3333-4444-555555555555', 'portgroup_uuid': '11111111-2222-3333-4444-555555555555', 'node_uuid': '66666666-7777-8888-9999-000000000000', 'address': 'AA:AA:AA:AA:AA:AA', 'extra': {}} CREATE_PORTGROUP = copy.deepcopy(PORTGROUP) del CREATE_PORTGROUP['uuid'] UPDATED_PORTGROUP = copy.deepcopy(PORTGROUP) NEW_ADDR = 'AA:AA:AA:AA:AA:AA' UPDATED_PORTGROUP['address'] = NEW_ADDR fake_responses = { '/v1/portgroups': { 'GET': ( {}, {"portgroups": [PORTGROUP, PORTGROUP2]}, ), 'POST': ( {}, CREATE_PORTGROUP, ), }, '/v1/portgroups/detail': { 'GET': ( {}, {"portgroups": [PORTGROUP, PORTGROUP2]}, ), }, '/v1/portgroups/%s' % PORTGROUP['uuid']: { 'GET': ( {}, PORTGROUP, ), 'DELETE': ( {}, None, ), 'PATCH': ( {}, UPDATED_PORTGROUP, ), }, '/v1/portgroups/detail?address=%s' % PORTGROUP['address']: { 'GET': ( {}, {"portgroups": [PORTGROUP]}, ), }, '/v1/portgroups/?address=%s' % PORTGROUP['address']: { 'GET': ( {}, {"portgroups": [PORTGROUP]}, ), }, '/v1/portgroups/%s/ports' % PORTGROUP['name']: { 'GET': ( {}, {"ports": [PORT]}, ), }, '/v1/portgroups/%s/ports' % PORTGROUP['uuid']: { 'GET': ( {}, {"ports": [PORT]}, ), }, } fake_responses_pagination = { '/v1/portgroups': { 'GET': ( {}, {"portgroups": [PORTGROUP], "next": "http://127.0.0.1:6385/v1/portgroups/?limit=1"} ), }, '/v1/portgroups/?limit=1': { 'GET': ( {}, {"portgroups": [PORTGROUP2]} ), }, '/v1/portgroups/?marker=%s' % PORTGROUP['uuid']: { 'GET': ( {}, {"portgroups": [PORTGROUP2]} ), }, } fake_responses_sorting = { '/v1/portgroups/?sort_key=updated_at': { 'GET': ( {}, {"portgroups": [PORTGROUP2, PORTGROUP]} ), }, '/v1/portgroups/?sort_dir=desc': { 'GET': ( {}, {"portgroups": [PORTGROUP2, PORTGROUP]} ), }, } class PortgroupManagerTest(testtools.TestCase): def setUp(self): super(PortgroupManagerTest, self).setUp() self.api = utils.FakeAPI(fake_responses) self.mgr = ironicclient.v1.portgroup.PortgroupManager(self.api) def test_portgroups_list(self): portgroups = self.mgr.list() expect = [ ('GET', '/v1/portgroups', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(2, len(portgroups)) expected_resp = ({}, {"portgroups": [PORTGROUP, PORTGROUP2]},) self.assertEqual(expected_resp, self.api.responses['/v1/portgroups']['GET']) def test_portgroups_list_by_address(self): portgroups = self.mgr.list(address=PORTGROUP['address']) expect = [ ('GET', '/v1/portgroups/?address=%s' % PORTGROUP['address'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(portgroups)) expected_resp = ({}, {"portgroups": [PORTGROUP, PORTGROUP2]},) self.assertEqual(expected_resp, self.api.responses['/v1/portgroups']['GET']) def test_portgroups_list_by_address_detail(self): portgroups = self.mgr.list(address=PORTGROUP['address'], detail=True) expect = [ ('GET', '/v1/portgroups/detail?address=%s' % PORTGROUP['address'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(portgroups)) self.assertIn( PORTGROUP, self.api.responses['/v1/portgroups']['GET'][1]['portgroups']) def test_portgroups_list_detail(self): portgroups = self.mgr.list(detail=True) expect = [ ('GET', '/v1/portgroups/detail', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(2, len(portgroups)) expected_resp = ({}, {"portgroups": [PORTGROUP, PORTGROUP2]},) self.assertEqual(expected_resp, self.api.responses['/v1/portgroups']['GET']) def test_portgroups_show(self): portgroup = self.mgr.get(PORTGROUP['uuid']) expect = [ ('GET', '/v1/portgroups/%s' % PORTGROUP['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(PORTGROUP['uuid'], portgroup.uuid) self.assertEqual(PORTGROUP['name'], portgroup.name) self.assertEqual(PORTGROUP['node_uuid'], portgroup.node_uuid) self.assertEqual(PORTGROUP['address'], portgroup.address) expected_resp = ({}, PORTGROUP,) self.assertEqual( expected_resp, self.api.responses['/v1/portgroups/%s' % PORTGROUP['uuid']]['GET']) def test_portgroups_show_by_address(self): portgroup = self.mgr.get_by_address(PORTGROUP['address']) expect = [ ('GET', '/v1/portgroups/detail?address=%s' % PORTGROUP['address'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(PORTGROUP['uuid'], portgroup.uuid) self.assertEqual(PORTGROUP['name'], portgroup.name) self.assertEqual(PORTGROUP['node_uuid'], portgroup.node_uuid) self.assertEqual(PORTGROUP['address'], portgroup.address) expected_resp = ({}, {"portgroups": [PORTGROUP]},) self.assertEqual( expected_resp, self.api.responses['/v1/portgroups/detail?address=%s' % PORTGROUP['address']]['GET']) def test_create(self): portgroup = self.mgr.create(**CREATE_PORTGROUP) expect = [ ('POST', '/v1/portgroups', {}, CREATE_PORTGROUP), ] self.assertEqual(expect, self.api.calls) self.assertTrue(portgroup) self.assertIn( PORTGROUP, self.api.responses['/v1/portgroups']['GET'][1]['portgroups']) def test_delete(self): portgroup = self.mgr.delete(portgroup_id=PORTGROUP['uuid']) expect = [ ('DELETE', '/v1/portgroups/%s' % PORTGROUP['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(portgroup) expected_resp = ({}, PORTGROUP,) self.assertEqual( expected_resp, self.api.responses['/v1/portgroups/%s' % PORTGROUP['uuid']]['GET']) def test_do_portgroup_delete_multiple_with_exception(self): client_mock = mock.MagicMock() client_mock.portgroup.delete.side_effect = ( [exceptions.ClientException, None]) args = mock.MagicMock() args.portgroup = ['pg_uuid1', 'pg_uuid2'] self.assertRaises(exceptions.ClientException, pg_shell.do_portgroup_delete, client_mock, args) client_mock.portgroup.delete.assert_has_calls( [mock.call('pg_uuid1'), mock.call('pg_uuid2')]) expected_resp = ({}, PORTGROUP,) self.assertEqual( expected_resp, self.api.responses['/v1/portgroups/%s' % PORTGROUP['uuid']]['GET']) def test_update(self): patch = {'op': 'replace', 'value': NEW_ADDR, 'path': '/address'} portgroup = self.mgr.update(portgroup_id=PORTGROUP['uuid'], patch=patch) expect = [ ('PATCH', '/v1/portgroups/%s' % PORTGROUP['uuid'], {}, patch), ] self.assertEqual(expect, self.api.calls) self.assertEqual(NEW_ADDR, portgroup.address) expected_resp = ({}, PORTGROUP,) self.assertEqual( expected_resp, self.api.responses['/v1/portgroups/%s' % PORTGROUP['uuid']]['GET']) def test_portgroup_port_list_with_uuid(self): ports = self.mgr.list_ports(PORTGROUP['uuid']) expect = [ ('GET', '/v1/portgroups/%s/ports' % PORTGROUP['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(ports)) self.assertEqual(PORT['uuid'], ports[0].uuid) self.assertEqual(PORT['address'], ports[0].address) expected_resp = ({}, {"ports": [PORT]},) self.assertEqual( expected_resp, self.api.responses['/v1/portgroups/%s/ports' % PORTGROUP['uuid']]['GET']) def test_portgroup_port_list_with_name(self): ports = self.mgr.list_ports(PORTGROUP['name']) expect = [ ('GET', '/v1/portgroups/%s/ports' % PORTGROUP['name'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(ports)) self.assertEqual(PORT['uuid'], ports[0].uuid) self.assertEqual(PORT['address'], ports[0].address) expected_resp = ({}, {"ports": [PORT]},) self.assertEqual( expected_resp, self.api.responses['/v1/portgroups/%s/ports' % PORTGROUP['name']]['GET']) class PortgroupManagerPaginationTest(testtools.TestCase): def setUp(self): super(PortgroupManagerPaginationTest, self).setUp() self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = ironicclient.v1.portgroup.PortgroupManager(self.api) def test_portgroups_list_limit(self): portgroups = self.mgr.list(limit=1) expect = [ ('GET', '/v1/portgroups/?limit=1', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(portgroups)) expected_resp = ( {}, {"next": "http://127.0.0.1:6385/v1/portgroups/?limit=1", "portgroups": [PORTGROUP]},) self.assertEqual(expected_resp, self.api.responses['/v1/portgroups']['GET']) def test_portgroups_list_marker(self): portgroups = self.mgr.list(marker=PORTGROUP['uuid']) expect = [ ('GET', '/v1/portgroups/?marker=%s' % PORTGROUP['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(portgroups)) expected_resp = ( {}, {"next": "http://127.0.0.1:6385/v1/portgroups/?limit=1", "portgroups": [PORTGROUP]},) self.assertEqual(expected_resp, self.api.responses['/v1/portgroups']['GET']) def test_portgroups_list_pagination_no_limit(self): portgroups = self.mgr.list(limit=0) expect = [ ('GET', '/v1/portgroups', {}, None), ('GET', '/v1/portgroups/?limit=1', {}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(2, len(portgroups)) expected_resp = ( {}, {"next": "http://127.0.0.1:6385/v1/portgroups/?limit=1", "portgroups": [PORTGROUP]},) self.assertEqual(expected_resp, self.api.responses['/v1/portgroups']['GET']) class PortgroupManagerSortingTest(testtools.TestCase): def setUp(self): super(PortgroupManagerSortingTest, self).setUp() self.api = utils.FakeAPI(fake_responses_sorting) self.mgr = ironicclient.v1.portgroup.PortgroupManager(self.api) def test_portgroups_list_sort_key(self): portgroups = self.mgr.list(sort_key='updated_at') expect = [ ('GET', '/v1/portgroups/?sort_key=updated_at', {}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(2, len(portgroups)) expected_resp = ({}, {"portgroups": [PORTGROUP2, PORTGROUP]},) self.assertEqual( expected_resp, self.api.responses['/v1/portgroups/?sort_key=updated_at']['GET']) def test_portgroups_list_sort_dir(self): portgroups = self.mgr.list(sort_dir='desc') expect = [ ('GET', '/v1/portgroups/?sort_dir=desc', {}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(2, len(portgroups)) expected_resp = ({}, {"portgroups": [PORTGROUP2, PORTGROUP]},) self.assertEqual( expected_resp, self.api.responses['/v1/portgroups/?sort_dir=desc']['GET']) python-ironicclient-2.2.0/ironicclient/tests/unit/v1/test_volume_connector_shell.py0000666000175100017510000002707313232474343031072 0ustar zuulzuul00000000000000# Copyright 2017 Hitachi Data Systems # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import mock from oslo_utils import uuidutils from ironicclient.common.apiclient import exceptions from ironicclient.common import cliutils from ironicclient.common import utils as commonutils from ironicclient.tests.unit import utils import ironicclient.v1.volume_connector_shell as vc_shell class Volume_ConnectorShellTest(utils.BaseTestCase): def test_volume_connector_show(self): actual = {} fake_print_dict = lambda data, *args, **kwargs: actual.update(data) with mock.patch.object(cliutils, 'print_dict', fake_print_dict): volume_connector = object() vc_shell._print_volume_connector_show(volume_connector) exp = ['created_at', 'extra', 'node_uuid', 'type', 'updated_at', 'uuid', 'connector_id'] act = actual.keys() self.assertEqual(sorted(exp), sorted(act)) def test_do_volume_connector_show(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_connector = 'volume_connector_uuid' args.fields = None args.json = False vc_shell.do_volume_connector_show(client_mock, args) client_mock.volume_connector.get.assert_called_once_with( 'volume_connector_uuid', fields=None) def test_do_volume_connector_show_space_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_connector = ' ' self.assertRaises(exceptions.CommandError, vc_shell.do_volume_connector_show, client_mock, args) def test_do_volume_connector_show_empty_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_connector = '' self.assertRaises(exceptions.CommandError, vc_shell.do_volume_connector_show, client_mock, args) def test_do_volume_connector_show_fields(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_connector = 'volume_connector_uuid' args.fields = [['uuid', 'connector_id']] args.json = False vc_shell.do_volume_connector_show(client_mock, args) client_mock.volume_connector.get.assert_called_once_with( 'volume_connector_uuid', fields=['uuid', 'connector_id']) def test_do_volume_connector_show_invalid_fields(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_connector = 'volume_connector_uuid' args.fields = [['foo', 'bar']] args.json = False self.assertRaises(exceptions.CommandError, vc_shell.do_volume_connector_show, client_mock, args) def test_do_volume_connector_update(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_connector = 'volume_connector_uuid' args.op = 'add' args.attributes = [['arg1=val1', 'arg2=val2']] args.json = False vc_shell.do_volume_connector_update(client_mock, args) patch = commonutils.args_array_to_patch(args.op, args.attributes[0]) client_mock.volume_connector.update.assert_called_once_with( 'volume_connector_uuid', patch) def test_do_volume_connector_update_wrong_op(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_connector = 'volume_connector_uuid' args.op = 'foo' args.attributes = [['arg1=val1', 'arg2=val2']] self.assertRaises(exceptions.CommandError, vc_shell.do_volume_connector_update, client_mock, args) self.assertFalse(client_mock.volume_connector.update.called) def _get_client_mock_args(self, node=None, marker=None, limit=None, sort_dir=None, sort_key=None, detail=False, fields=None, json=False): args = mock.MagicMock(spec=True) args.node = node args.marker = marker args.limit = limit args.sort_dir = sort_dir args.sort_key = sort_key args.detail = detail args.fields = fields args.json = json return args def test_do_volume_connector_list(self): client_mock = mock.MagicMock() args = self._get_client_mock_args() vc_shell.do_volume_connector_list(client_mock, args) client_mock.volume_connector.list.assert_called_once_with(detail=False) def test_do_volume_connector_list_detail(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(detail=True) vc_shell.do_volume_connector_list(client_mock, args) client_mock.volume_connector.list.assert_called_once_with(detail=True) def test_do_volume_connector_list_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='uuid', detail=False) vc_shell.do_volume_connector_list(client_mock, args) client_mock.volume_connector.list.assert_called_once_with( sort_key='uuid', detail=False) def test_do_volume_connector_list_wrong_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='node_uuid', detail=False) self.assertRaises(exceptions.CommandError, vc_shell.do_volume_connector_list, client_mock, args) self.assertFalse(client_mock.volume_connector.list.called) def test_do_volume_connector_list_detail_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='uuid', detail=True) vc_shell.do_volume_connector_list(client_mock, args) client_mock.volume_connector.list.assert_called_once_with( sort_key='uuid', detail=True) def test_do_volume_connector_list_detail_wrong_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='node_uuid', detail=True) self.assertRaises(exceptions.CommandError, vc_shell.do_volume_connector_list, client_mock, args) self.assertFalse(client_mock.volume_connector.list.called) def test_do_volume_connector_list_fields(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(fields=[['uuid', 'connector_id']]) vc_shell.do_volume_connector_list(client_mock, args) client_mock.volume_connector.list.assert_called_once_with( fields=['uuid', 'connector_id'], detail=False) def test_do_volume_connector_list_invalid_fields(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(fields=[['foo', 'bar']]) self.assertRaises(exceptions.CommandError, vc_shell.do_volume_connector_list, client_mock, args) def test_do_volume_connector_list_sort_dir(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_dir='desc', detail=False) vc_shell.do_volume_connector_list(client_mock, args) client_mock.volume_connector.list.assert_called_once_with( sort_dir='desc', detail=False) def test_do_volume_connector_list_detail_sort_dir(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_dir='asc', detail=True) vc_shell.do_volume_connector_list(client_mock, args) client_mock.volume_connector.list.assert_called_once_with( sort_dir='asc', detail=True) def test_do_volume_connector_wrong_sort_dir(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_dir='abc', detail=False) self.assertRaises(exceptions.CommandError, vc_shell.do_volume_connector_list, client_mock, args) self.assertFalse(client_mock.volume_connector.list.called) def test_do_volume_connector_create(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.json = False vc_shell.do_volume_connector_create(client_mock, args) client_mock.volume_connector.create.assert_called_once_with() def test_do_volume_connector_create_with_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.uuid = uuidutils.generate_uuid() args.json = False vc_shell.do_volume_connector_create(client_mock, args) client_mock.volume_connector.create.assert_called_once_with( uuid=args.uuid) def test_do_volume_connector_create_valid_fields_values(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.type = 'type' args.connector_id = 'connector_id' args.node_uuid = 'uuid' args.extra = ["key1=val1", "key2=val2"] args.json = False vc_shell.do_volume_connector_create(client_mock, args) client_mock.volume_connector.create.assert_called_once_with( type='type', connector_id='connector_id', node_uuid='uuid', extra={'key1': 'val1', 'key2': 'val2'}) def test_do_volume_connector_create_invalid_extra_fields(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.type = 'type' args.connector_id = 'connector_id' args.node_uuid = 'uuid' args.extra = ["foo"] args.json = False self.assertRaises(exceptions.CommandError, vc_shell.do_volume_connector_create, client_mock, args) def test_do_volume_connector_delete(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_connector = ['volume_connector_uuid'] vc_shell.do_volume_connector_delete(client_mock, args) client_mock.volume_connector.delete.assert_called_once_with( 'volume_connector_uuid') def test_do_volume_connector_delete_multi(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_connector = ['uuid1', 'uuid2'] vc_shell.do_volume_connector_delete(client_mock, args) self.assertEqual([mock.call('uuid1'), mock.call('uuid2')], client_mock.volume_connector.delete.call_args_list) def test_do_volume_connector_delete_multi_error(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_connector = ['uuid1', 'uuid2'] client_mock.volume_connector.delete.side_effect = [ exceptions.ClientException('error'), None] self.assertRaises(exceptions.ClientException, vc_shell.do_volume_connector_delete, client_mock, args) self.assertEqual([mock.call('uuid1'), mock.call('uuid2')], client_mock.volume_connector.delete.call_args_list) python-ironicclient-2.2.0/ironicclient/tests/unit/v1/test_resource_fields.py0000666000175100017510000000752313232474343027475 0ustar zuulzuul00000000000000# Copyright (c) 2015 Intel Corporation # # 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 testtools from ironicclient.v1 import resource_fields class ResourceTest(testtools.TestCase): def setUp(self): super(ResourceTest, self).setUp() self._saved_ids = resource_fields.Resource.FIELDS resource_fields.Resource.FIELDS = { 'item1': 'ITEM1', '2nd_item': 'A second item', 'item_3': 'Third item', } def tearDown(self): super(ResourceTest, self).tearDown() resource_fields.Resource.FIELDS = self._saved_ids def test_fields_single_value(self): # Make sure single value is what we expect foo = resource_fields.Resource(['item1']) self.assertEqual(('item1',), foo.fields) self.assertEqual(('ITEM1',), foo.labels) self.assertEqual(('item1',), foo.sort_fields) self.assertEqual(('ITEM1',), foo.sort_labels) def test_fields_multiple_value_order(self): # Make sure order is maintained foo = resource_fields.Resource(['2nd_item', 'item1']) self.assertEqual(('2nd_item', 'item1'), foo.fields) self.assertEqual(('A second item', 'ITEM1'), foo.labels) self.assertEqual(('2nd_item', 'item1'), foo.sort_fields) self.assertEqual(('A second item', 'ITEM1'), foo.sort_labels) def test_sort_excluded(self): # Test excluding of fields for sort purposes foo = resource_fields.Resource(['item_3', 'item1', '2nd_item'], sort_excluded=['item1']) self.assertEqual(('item_3', '2nd_item'), foo.sort_fields) self.assertEqual(('Third item', 'A second item'), foo.sort_labels) def test_sort_excluded_unknown(self): # Test sort_excluded value not in the field_ids self.assertRaises( ValueError, resource_fields.Resource, ['item_3', 'item1', '2nd_item'], sort_excluded=['item1', 'foo']) def test_override_labels(self): # Test overriding labels foo = resource_fields.Resource(['item_3', 'item1', '2nd_item'], override_labels={'item1': 'One'}) self.assertEqual(('Third item', 'One', 'A second item'), foo.labels) self.assertEqual(('Third item', 'One', 'A second item'), foo.sort_labels) def test_override_labels_unknown(self): # Test overriding labels with key not in field_ids self.assertRaises( ValueError, resource_fields.Resource, ['item_3', 'item1', '2nd_item'], override_labels={'foo': 'One'}) def test_sort_excluded_override_labels(self): # Test excluding of fields for sort purposes and overriding labels foo = resource_fields.Resource(['item_3', 'item1', '2nd_item'], sort_excluded=['item1'], override_labels={'item_3': 'Three'}) self.assertEqual(('Three', 'ITEM1', 'A second item'), foo.labels) self.assertEqual(('item_3', '2nd_item'), foo.sort_fields) self.assertEqual(('Three', 'A second item'), foo.sort_labels) def test_unknown_field_id(self): self.assertRaises( KeyError, resource_fields.Resource, ['item1', 'unknown_id']) python-ironicclient-2.2.0/ironicclient/tests/unit/v1/test_chassis.py0000666000175100017510000003427013232474343025754 0ustar zuulzuul00000000000000# Copyright 2013 Red Hat, Inc. # 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 copy import testtools from testtools.matchers import HasLength from ironicclient import exc from ironicclient.tests.unit import utils import ironicclient.v1.chassis CHASSIS = {'uuid': 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', 'extra': {}, 'description': 'data-center-1-chassis'} CHASSIS2 = {'uuid': 'eeeeeeee-dddd-cccc-bbbb-aaaaaaaaaaaa', 'extra': {}, 'description': 'data-center-1-chassis'} NODE = {'uuid': '66666666-7777-8888-9999-000000000000', 'chassis_uuid': 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', 'provision_state': 'available', 'driver': 'fake', 'driver_info': {'user': 'foo', 'password': 'bar'}, 'properties': {'num_cpu': 4}, 'extra': {}} CREATE_CHASSIS = copy.deepcopy(CHASSIS) del CREATE_CHASSIS['uuid'] CREATE_WITH_UUID = copy.deepcopy(CHASSIS) UPDATED_CHASSIS = copy.deepcopy(CHASSIS) NEW_DESCR = 'new-description' UPDATED_CHASSIS['description'] = NEW_DESCR fake_responses = { '/v1/chassis': { 'GET': ( {}, {"chassis": [CHASSIS]}, ), 'POST': ( {}, CREATE_CHASSIS, ), }, '/v1/chassis/detail': { 'GET': ( {}, {"chassis": [CHASSIS]}, ), }, '/v1/chassis/?fields=uuid,extra': { 'GET': ( {}, {"chassis": [CHASSIS]}, ), }, '/v1/chassis/%s' % CHASSIS['uuid']: { 'GET': ( {}, CHASSIS, ), 'DELETE': ( {}, None, ), 'PATCH': ( {}, UPDATED_CHASSIS, ), }, '/v1/chassis/%s?fields=uuid,description' % CHASSIS['uuid']: { 'GET': ( {}, CHASSIS, ), }, '/v1/chassis/%s/nodes' % CHASSIS['uuid']: { 'GET': ( {}, {"nodes": [NODE]}, ), }, '/v1/chassis/%s/nodes/detail' % CHASSIS['uuid']: { 'GET': ( {}, {"nodes": [NODE]}, ), }, '/v1/chassis/%s/nodes?fields=uuid,extra' % CHASSIS['uuid']: { 'GET': ( {}, {"nodes": [NODE]}, ), }, '/v1/chassis/%s/nodes?associated=True' % CHASSIS['uuid']: { 'GET': ( {}, {"nodes": [NODE]}, ) }, '/v1/chassis/%s/nodes?maintenance=False' % CHASSIS['uuid']: { 'GET': ( {}, {"nodes": [NODE]}, ) }, '/v1/chassis/%s/nodes?provision_state=available' % CHASSIS['uuid']: { 'GET': ( {}, {"nodes": [NODE]}, ) }, } fake_responses_pagination = { '/v1/chassis': { 'GET': ( {}, {"chassis": [CHASSIS], "next": "http://127.0.0.1:6385/v1/chassis/?limit=1"} ), }, '/v1/chassis/?limit=1': { 'GET': ( {}, {"chassis": [CHASSIS2]} ), }, '/v1/chassis/?marker=%s' % CHASSIS['uuid']: { 'GET': ( {}, {"chassis": [CHASSIS2]} ), }, '/v1/chassis/%s/nodes?limit=1' % CHASSIS['uuid']: { 'GET': ( {}, {"nodes": [NODE]}, ), }, '/v1/chassis/%s/nodes?marker=%s' % (CHASSIS['uuid'], NODE['uuid']): { 'GET': ( {}, {"nodes": [NODE]}, ), }, } fake_responses_sorting = { '/v1/chassis/?sort_key=updated_at': { 'GET': ( {}, {"chassis": [CHASSIS2]} ), }, '/v1/chassis/?sort_dir=desc': { 'GET': ( {}, {"chassis": [CHASSIS2]} ), }, '/v1/chassis/%s/nodes?sort_key=updated_at' % CHASSIS['uuid']: { 'GET': ( {}, {"nodes": [NODE]}, ), }, '/v1/chassis/%s/nodes?sort_dir=desc' % CHASSIS['uuid']: { 'GET': ( {}, {"nodes": [NODE]}, ), }, } class ChassisManagerTest(testtools.TestCase): def setUp(self): super(ChassisManagerTest, self).setUp() self.api = utils.FakeAPI(fake_responses) self.mgr = ironicclient.v1.chassis.ChassisManager(self.api) def test_chassis_list(self): chassis = self.mgr.list() expect = [ ('GET', '/v1/chassis', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(chassis)) def test_chassis_list_limit(self): self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = ironicclient.v1.chassis.ChassisManager(self.api) chassis = self.mgr.list(limit=1) expect = [ ('GET', '/v1/chassis/?limit=1', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(chassis, HasLength(1)) def test_chassis_list_marker(self): self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = ironicclient.v1.chassis.ChassisManager(self.api) chassis = self.mgr.list(marker=CHASSIS['uuid']) expect = [ ('GET', '/v1/chassis/?marker=%s' % CHASSIS['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(chassis, HasLength(1)) def test_chassis_list_pagination_no_limit(self): self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = ironicclient.v1.chassis.ChassisManager(self.api) chassis = self.mgr.list(limit=0) expect = [ ('GET', '/v1/chassis', {}, None), ('GET', '/v1/chassis/?limit=1', {}, None) ] self.assertEqual(expect, self.api.calls) self.assertThat(chassis, HasLength(2)) def test_chassis_list_sort_key(self): self.api = utils.FakeAPI(fake_responses_sorting) self.mgr = ironicclient.v1.chassis.ChassisManager(self.api) chassis = self.mgr.list(sort_key='updated_at') expect = [ ('GET', '/v1/chassis/?sort_key=updated_at', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(chassis, HasLength(1)) def test_chassis_list_sort_dir(self): self.api = utils.FakeAPI(fake_responses_sorting) self.mgr = ironicclient.v1.chassis.ChassisManager(self.api) chassis = self.mgr.list(sort_dir='desc') expect = [ ('GET', '/v1/chassis/?sort_dir=desc', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(chassis, HasLength(1)) def test_chassis_list_detail(self): chassis = self.mgr.list(detail=True) expect = [ ('GET', '/v1/chassis/detail', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(chassis)) def test_chassis_list_fields(self): nodes = self.mgr.list(fields=['uuid', 'extra']) expect = [ ('GET', '/v1/chassis/?fields=uuid,extra', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(nodes)) def test_chassis_list_detail_and_fields_fail(self): self.assertRaises(exc.InvalidAttribute, self.mgr.list, detail=True, fields=['uuid', 'extra']) def test_chassis_show(self): chassis = self.mgr.get(CHASSIS['uuid']) expect = [ ('GET', '/v1/chassis/%s' % CHASSIS['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(CHASSIS['uuid'], chassis.uuid) self.assertEqual(CHASSIS['description'], chassis.description) def test_chassis_show_fields(self): chassis = self.mgr.get(CHASSIS['uuid'], fields=['uuid', 'description']) expect = [ ('GET', '/v1/chassis/%s?fields=uuid,description' % CHASSIS['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(CHASSIS['uuid'], chassis.uuid) self.assertEqual(CHASSIS['description'], chassis.description) def test_create(self): chassis = self.mgr.create(**CREATE_CHASSIS) expect = [ ('POST', '/v1/chassis', {}, CREATE_CHASSIS), ] self.assertEqual(expect, self.api.calls) self.assertTrue(chassis) def test_create_with_uuid(self): chassis = self.mgr.create(**CREATE_WITH_UUID) expect = [ ('POST', '/v1/chassis', {}, CREATE_WITH_UUID), ] self.assertEqual(expect, self.api.calls) self.assertTrue(chassis) def test_delete(self): chassis = self.mgr.delete(chassis_id=CHASSIS['uuid']) expect = [ ('DELETE', '/v1/chassis/%s' % CHASSIS['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(chassis) def test_update(self): patch = {'op': 'replace', 'value': NEW_DESCR, 'path': '/description'} chassis = self.mgr.update(chassis_id=CHASSIS['uuid'], patch=patch) expect = [ ('PATCH', '/v1/chassis/%s' % CHASSIS['uuid'], {}, patch), ] self.assertEqual(expect, self.api.calls) self.assertEqual(NEW_DESCR, chassis.description) def test_chassis_node_list(self): nodes = self.mgr.list_nodes(CHASSIS['uuid']) expect = [ ('GET', '/v1/chassis/%s/nodes' % CHASSIS['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(nodes)) self.assertEqual(NODE['uuid'], nodes[0].uuid) def test_chassis_node_list_detail(self): nodes = self.mgr.list_nodes(CHASSIS['uuid'], detail=True) expect = [ ('GET', '/v1/chassis/%s/nodes/detail' % CHASSIS['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(nodes)) self.assertEqual(NODE['uuid'], nodes[0].uuid) def test_chassis_node_list_fields(self): nodes = self.mgr.list_nodes(CHASSIS['uuid'], fields=['uuid', 'extra']) expect = [ ('GET', '/v1/chassis/%s/nodes?fields=uuid,extra' % CHASSIS['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(nodes)) def test_chassis_node_list_maintenance(self): nodes = self.mgr.list_nodes(CHASSIS['uuid'], maintenance=False) expect = [ ('GET', '/v1/chassis/%s/nodes?maintenance=False' % CHASSIS['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(nodes)) def test_chassis_node_list_associated(self): nodes = self.mgr.list_nodes(CHASSIS['uuid'], associated=True) expect = [ ('GET', '/v1/chassis/%s/nodes?associated=True' % CHASSIS['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(nodes)) def test_chassis_node_list_provision_state(self): nodes = self.mgr.list_nodes(CHASSIS['uuid'], provision_state="available") expect = [ ('GET', '/v1/chassis/%s/nodes?provision_state=available' % CHASSIS['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(nodes)) def test_chassis_node_list_detail_and_fields_fail(self): self.assertRaises(exc.InvalidAttribute, self.mgr.list_nodes, CHASSIS['uuid'], detail=True, fields=['uuid', 'extra']) def test_chassis_node_list_limit(self): self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = ironicclient.v1.chassis.ChassisManager(self.api) nodes = self.mgr.list_nodes(CHASSIS['uuid'], limit=1) expect = [ ('GET', '/v1/chassis/%s/nodes?limit=1' % CHASSIS['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(nodes, HasLength(1)) self.assertEqual(NODE['uuid'], nodes[0].uuid) def test_chassis_node_list_sort_key(self): self.api = utils.FakeAPI(fake_responses_sorting) self.mgr = ironicclient.v1.chassis.ChassisManager(self.api) nodes = self.mgr.list_nodes(CHASSIS['uuid'], sort_key='updated_at') expect = [ ('GET', '/v1/chassis/%s/nodes?sort_key=updated_at' % CHASSIS['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(nodes, HasLength(1)) self.assertEqual(NODE['uuid'], nodes[0].uuid) def test_chassis_node_list_sort_dir(self): self.api = utils.FakeAPI(fake_responses_sorting) self.mgr = ironicclient.v1.chassis.ChassisManager(self.api) nodes = self.mgr.list_nodes(CHASSIS['uuid'], sort_dir='desc') expect = [ ('GET', '/v1/chassis/%s/nodes?sort_dir=desc' % CHASSIS['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(nodes, HasLength(1)) self.assertEqual(NODE['uuid'], nodes[0].uuid) def test_chassis_node_list_marker(self): self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = ironicclient.v1.chassis.ChassisManager(self.api) nodes = self.mgr.list_nodes(CHASSIS['uuid'], marker=NODE['uuid']) expect = [ ('GET', '/v1/chassis/%s/nodes?marker=%s' % (CHASSIS['uuid'], NODE['uuid']), {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(nodes, HasLength(1)) self.assertEqual(NODE['uuid'], nodes[0].uuid) python-ironicclient-2.2.0/ironicclient/tests/unit/v1/test_port.py0000666000175100017510000002563713232474343025312 0ustar zuulzuul00000000000000# Copyright 2013 Red Hat, Inc. # 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 copy import testtools from testtools.matchers import HasLength from ironicclient import exc from ironicclient.tests.unit import utils import ironicclient.v1.port PORT = {'uuid': '11111111-2222-3333-4444-555555555555', 'node_uuid': '55555555-4444-3333-2222-111111111111', 'address': 'AA:BB:CC:DD:EE:FF', 'pxe_enabled': True, 'local_link_connection': {}, 'portgroup_uuid': '55555555-4444-3333-2222-111111111111', 'physical_network': 'physnet1', 'extra': {}} PORT2 = {'uuid': '55555555-4444-3333-2222-111111111111', 'node_uuid': '55555555-4444-3333-2222-111111111111', 'address': 'AA:AA:AA:BB:BB:BB', 'pxe_enabled': True, 'local_link_connection': {}, 'portgroup_uuid': '55555555-4444-3333-2222-111111111111', 'physical_network': 'physnet2', 'extra': {}} CREATE_PORT = copy.deepcopy(PORT) del CREATE_PORT['uuid'] CREATE_PORT_WITH_UUID = copy.deepcopy(PORT) UPDATED_PORT = copy.deepcopy(PORT) NEW_ADDR = 'AA:AA:AA:AA:AA:AA' UPDATED_PORT['address'] = NEW_ADDR fake_responses = { '/v1/ports': { 'GET': ( {}, {"ports": [PORT]}, ), 'POST': ( {}, CREATE_PORT, ), }, '/v1/ports/detail': { 'GET': ( {}, {"ports": [PORT]}, ), }, '/v1/ports/?fields=uuid,address': { 'GET': ( {}, {"ports": [PORT]}, ), }, '/v1/ports/%s' % PORT['uuid']: { 'GET': ( {}, PORT, ), 'DELETE': ( {}, None, ), 'PATCH': ( {}, UPDATED_PORT, ), }, '/v1/ports/%s?fields=uuid,address' % PORT['uuid']: { 'GET': ( {}, PORT, ), }, '/v1/ports/detail?address=%s' % PORT['address']: { 'GET': ( {}, {"ports": [PORT]}, ), }, '/v1/ports/?address=%s' % PORT['address']: { 'GET': ( {}, {"ports": [PORT]}, ), }, '/v1/ports/?node=%s' % PORT['node_uuid']: { 'GET': ( {}, {"ports": [PORT]}, ), }, '/v1/ports/?portgroup=%s' % PORT['portgroup_uuid']: { 'GET': ( {}, {"ports": [PORT]}, ), }, } fake_responses_pagination = { '/v1/ports': { 'GET': ( {}, {"ports": [PORT], "next": "http://127.0.0.1:6385/v1/ports/?limit=1"} ), }, '/v1/ports/?limit=1': { 'GET': ( {}, {"ports": [PORT2]} ), }, '/v1/ports/?marker=%s' % PORT['uuid']: { 'GET': ( {}, {"ports": [PORT2]} ), }, } fake_responses_sorting = { '/v1/ports/?sort_key=updated_at': { 'GET': ( {}, {"ports": [PORT2, PORT]} ), }, '/v1/ports/?sort_dir=desc': { 'GET': ( {}, {"ports": [PORT2, PORT]} ), }, } class PortManagerTest(testtools.TestCase): def setUp(self): super(PortManagerTest, self).setUp() self.api = utils.FakeAPI(fake_responses) self.mgr = ironicclient.v1.port.PortManager(self.api) def test_ports_list(self): ports = self.mgr.list() expect = [ ('GET', '/v1/ports', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(ports)) def test_ports_list_by_address(self): ports = self.mgr.list(address=PORT['address']) expect = [ ('GET', '/v1/ports/?address=%s' % PORT['address'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(ports)) def test_ports_list_by_address_detail(self): ports = self.mgr.list(address=PORT['address'], detail=True) expect = [ ('GET', '/v1/ports/detail?address=%s' % PORT['address'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(ports)) def test_ports_list_by_node(self): ports = self.mgr.list(node=PORT['node_uuid']) expect = [ ('GET', '/v1/ports/?node=%s' % PORT['node_uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(ports)) def test_ports_list_by_portgroup(self): ports = self.mgr.list(portgroup=PORT['portgroup_uuid']) expect = [ ('GET', '/v1/ports/?portgroup=%s' % PORT['portgroup_uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(ports)) def test_ports_list_detail(self): ports = self.mgr.list(detail=True) expect = [ ('GET', '/v1/ports/detail', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(ports)) def test_port_list_fields(self): ports = self.mgr.list(fields=['uuid', 'address']) expect = [ ('GET', '/v1/ports/?fields=uuid,address', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(ports)) def test_port_list_detail_and_fields_fail(self): self.assertRaises(exc.InvalidAttribute, self.mgr.list, detail=True, fields=['uuid', 'address']) def test_ports_list_limit(self): self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = ironicclient.v1.port.PortManager(self.api) ports = self.mgr.list(limit=1) expect = [ ('GET', '/v1/ports/?limit=1', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(ports, HasLength(1)) def test_ports_list_marker(self): self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = ironicclient.v1.port.PortManager(self.api) ports = self.mgr.list(marker=PORT['uuid']) expect = [ ('GET', '/v1/ports/?marker=%s' % PORT['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(ports, HasLength(1)) def test_ports_list_pagination_no_limit(self): self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = ironicclient.v1.port.PortManager(self.api) ports = self.mgr.list(limit=0) expect = [ ('GET', '/v1/ports', {}, None), ('GET', '/v1/ports/?limit=1', {}, None) ] self.assertEqual(expect, self.api.calls) self.assertThat(ports, HasLength(2)) def test_ports_list_sort_key(self): self.api = utils.FakeAPI(fake_responses_sorting) self.mgr = ironicclient.v1.port.PortManager(self.api) ports = self.mgr.list(sort_key='updated_at') expect = [ ('GET', '/v1/ports/?sort_key=updated_at', {}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(2, len(ports)) def test_ports_list_sort_dir(self): self.api = utils.FakeAPI(fake_responses_sorting) self.mgr = ironicclient.v1.port.PortManager(self.api) ports = self.mgr.list(sort_dir='desc') expect = [ ('GET', '/v1/ports/?sort_dir=desc', {}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(2, len(ports)) def test_ports_show(self): port = self.mgr.get(PORT['uuid']) expect = [ ('GET', '/v1/ports/%s' % PORT['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(PORT['uuid'], port.uuid) self.assertEqual(PORT['address'], port.address) self.assertEqual(PORT['node_uuid'], port.node_uuid) self.assertEqual(PORT['pxe_enabled'], port.pxe_enabled) self.assertEqual(PORT['local_link_connection'], port.local_link_connection) self.assertEqual(PORT['portgroup_uuid'], port.portgroup_uuid) self.assertEqual(PORT['physical_network'], port.physical_network) def test_ports_show_by_address(self): port = self.mgr.get_by_address(PORT['address']) expect = [ ('GET', '/v1/ports/detail?address=%s' % PORT['address'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(PORT['uuid'], port.uuid) self.assertEqual(PORT['address'], port.address) self.assertEqual(PORT['node_uuid'], port.node_uuid) self.assertEqual(PORT['pxe_enabled'], port.pxe_enabled) self.assertEqual(PORT['local_link_connection'], port.local_link_connection) self.assertEqual(PORT['portgroup_uuid'], port.portgroup_uuid) self.assertEqual(PORT['physical_network'], port.physical_network) def test_port_show_fields(self): port = self.mgr.get(PORT['uuid'], fields=['uuid', 'address']) expect = [ ('GET', '/v1/ports/%s?fields=uuid,address' % PORT['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(PORT['uuid'], port.uuid) self.assertEqual(PORT['address'], port.address) def test_create(self): port = self.mgr.create(**CREATE_PORT) expect = [ ('POST', '/v1/ports', {}, CREATE_PORT), ] self.assertEqual(expect, self.api.calls) self.assertTrue(port) def test_create_with_uuid(self): port = self.mgr.create(**CREATE_PORT_WITH_UUID) expect = [ ('POST', '/v1/ports', {}, CREATE_PORT_WITH_UUID), ] self.assertEqual(expect, self.api.calls) self.assertTrue(port) def test_delete(self): port = self.mgr.delete(port_id=PORT['uuid']) expect = [ ('DELETE', '/v1/ports/%s' % PORT['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(port) def test_update(self): patch = {'op': 'replace', 'value': NEW_ADDR, 'path': '/address'} port = self.mgr.update(port_id=PORT['uuid'], patch=patch) expect = [ ('PATCH', '/v1/ports/%s' % PORT['uuid'], {}, patch), ] self.assertEqual(expect, self.api.calls) self.assertEqual(NEW_ADDR, port.address) python-ironicclient-2.2.0/ironicclient/tests/unit/v1/test_node.py0000666000175100017510000016175113232474373025254 0ustar zuulzuul00000000000000# Copyright 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import tempfile import time import mock import six import testtools from testtools.matchers import HasLength from ironicclient.common import utils as common_utils from ironicclient import exc from ironicclient.tests.unit import utils from ironicclient.v1 import node from ironicclient.v1 import volume_connector from ironicclient.v1 import volume_target if six.PY3: import io file = io.BytesIO NODE1 = {'uuid': '66666666-7777-8888-9999-000000000000', 'chassis_uuid': 'aaaaaaaa-1111-bbbb-2222-cccccccccccc', 'maintenance': False, 'provision_state': 'available', 'driver': 'fake', 'driver_info': {'user': 'foo', 'password': 'bar'}, 'properties': {'num_cpu': 4}, 'name': 'fake-node-1', 'resource_class': 'foo', 'extra': {}} NODE2 = {'uuid': '66666666-7777-8888-9999-111111111111', 'instance_uuid': '66666666-7777-8888-9999-222222222222', 'chassis_uuid': 'aaaaaaaa-1111-bbbb-2222-cccccccccccc', 'maintenance': True, 'driver': 'fake too', 'driver_info': {'user': 'foo', 'password': 'bar'}, 'properties': {'num_cpu': 4}, 'resource_class': 'bar', 'extra': {}} PORT = {'uuid': '11111111-2222-3333-4444-555555555555', 'node_uuid': '66666666-7777-8888-9999-000000000000', 'address': 'AA:AA:AA:AA:AA:AA', 'extra': {}} PORTGROUP = {'uuid': '11111111-2222-3333-4444-555555555555', 'name': 'Portgroup', 'node_uuid': '66666666-7777-8888-9999-000000000000', 'address': 'AA:BB:CC:DD:EE:FF', 'extra': {}} CONNECTOR = {'uuid': 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', 'node_uuid': NODE1['uuid'], 'type': 'iqn', 'connector_id': 'iqn.2010-10.org.openstack:test', 'extra': {}} TARGET = {'uuid': 'cccccccc-dddd-eeee-ffff-000000000000', 'node_uuid': NODE1['uuid'], 'volume_type': 'iscsi', 'properties': {'target_iqn': 'iqn.foo'}, 'boot_index': 0, 'volume_id': '12345678', 'extra': {}} POWER_STATE = {'power_state': 'power on', 'target_power_state': 'power off'} DRIVER_IFACES = {'deploy': {'result': True}, 'power': {'result': False, 'reason': 'Invalid IPMI username'}, 'console': {'result': None, 'reason': 'not supported'}, 'rescue': {'result': None, 'reason': 'not supported'}} NODE_STATES = {"last_error": None, "power_state": "power on", "provision_state": "active", "target_power_state": None, "target_provision_state": None} CONSOLE_DATA_ENABLED = {'console_enabled': True, 'console_info': {'test-console': 'test-console-data'}} CONSOLE_DATA_DISABLED = {'console_enabled': False, 'console_info': None} CONSOLE_ENABLE = 'true' BOOT_DEVICE = {'boot_device': 'pxe', 'persistent': False} SUPPORTED_BOOT_DEVICE = {'supported_boot_devices': ['pxe']} NODE_VENDOR_PASSTHRU_METHOD = {"heartbeat": {"attach": "false", "http_methods": ["POST"], "description": "", "async": "true"}} VIFS = {'vifs': [{'id': 'aaa-aaa'}]} TRAITS = {'traits': ['CUSTOM_FOO', 'CUSTOM_BAR']} CREATE_NODE = copy.deepcopy(NODE1) del CREATE_NODE['uuid'] del CREATE_NODE['maintenance'] del CREATE_NODE['provision_state'] UPDATED_NODE = copy.deepcopy(NODE1) NEW_DRIVER = 'new-driver' UPDATED_NODE['driver'] = NEW_DRIVER CREATE_WITH_UUID = copy.deepcopy(NODE1) del CREATE_WITH_UUID['maintenance'] del CREATE_WITH_UUID['provision_state'] fake_responses = { '/v1/nodes': { 'GET': ( {}, {"nodes": [NODE1, NODE2]} ), 'POST': ( {}, CREATE_NODE, ), }, '/v1/nodes/detail': { 'GET': ( {}, {"nodes": [NODE1, NODE2]} ), }, '/v1/nodes/?fields=uuid,extra': { 'GET': ( {}, {"nodes": [NODE1]} ), }, '/v1/nodes/?associated=False': { 'GET': ( {}, {"nodes": [NODE1]}, ) }, '/v1/nodes/?associated=True': { 'GET': ( {}, {"nodes": [NODE2]}, ) }, '/v1/nodes/?maintenance=False': { 'GET': ( {}, {"nodes": [NODE1]}, ) }, '/v1/nodes/?maintenance=True': { 'GET': ( {}, {"nodes": [NODE2]}, ) }, '/v1/nodes/?associated=True&maintenance=True': { 'GET': ( {}, {"nodes": [NODE2]}, ) }, '/v1/nodes/?provision_state=available': { 'GET': ( {}, {"nodes": [NODE1]}, ) }, '/v1/nodes/?driver=fake': { 'GET': ( {}, {"nodes": [NODE1]}, ) }, '/v1/nodes/?resource_class=foo': { 'GET': ( {}, {"nodes": [NODE1]}, ) }, '/v1/nodes/?chassis_uuid=%s' % NODE2['chassis_uuid']: { 'GET': ( {}, {"nodes": [NODE2]}, ) }, '/v1/nodes/detail?instance_uuid=%s' % NODE2['instance_uuid']: { 'GET': ( {}, {"nodes": [NODE2]}, ) }, '/v1/nodes/%s' % NODE1['uuid']: { 'GET': ( {}, NODE1, ), 'DELETE': ( {}, None, ), 'PATCH': ( {}, UPDATED_NODE, ), }, '/v1/nodes/%s?fields=uuid,extra' % NODE1['uuid']: { 'GET': ( {}, NODE1, ), }, '/v1/nodes/%s' % NODE2['uuid']: { 'GET': ( {}, NODE2, ), }, '/v1/nodes/%s' % NODE1['name']: { 'GET': ( {}, NODE1, ), }, '/v1/nodes/%s/ports' % NODE1['uuid']: { 'GET': ( {}, {"ports": [PORT]}, ), }, '/v1/nodes/%s/ports' % NODE1['name']: { 'GET': ( {}, {"ports": [PORT]}, ), }, '/v1/nodes/%s/ports/detail' % NODE1['uuid']: { 'GET': ( {}, {"ports": [PORT]}, ), }, '/v1/nodes/%s/ports/detail' % NODE1['name']: { 'GET': ( {}, {"ports": [PORT]}, ), }, '/v1/nodes/%s/ports?fields=uuid,address' % NODE1['uuid']: { 'GET': ( {}, {"ports": [PORT]}, ), }, '/v1/nodes/%s/portgroups' % NODE1['uuid']: { 'GET': ( {}, {"portgroups": [PORTGROUP]}, ), }, '/v1/nodes/%s/portgroups/detail' % NODE1['uuid']: { 'GET': ( {}, {"portgroups": [PORTGROUP]}, ), }, '/v1/nodes/%s/portgroups?fields=uuid,address' % NODE1['uuid']: { 'GET': ( {}, {"portgroups": [PORTGROUP]}, ), }, '/v1/nodes/%s/volume/connectors' % NODE1['uuid']: { 'GET': ( {}, {"connectors": [CONNECTOR]}, ), }, '/v1/nodes/%s/volume/connectors?detail=True' % NODE1['uuid']: { 'GET': ( {}, {"connectors": [CONNECTOR]}, ), }, '/v1/nodes/%s/volume/connectors?fields=uuid,connector_id' % NODE1['uuid']: { 'GET': ( {}, {"connectors": [CONNECTOR]}, ), }, '/v1/nodes/%s/volume/targets' % NODE1['uuid']: { 'GET': ( {}, {"targets": [TARGET]}, ), }, '/v1/nodes/%s/volume/targets?detail=True' % NODE1['uuid']: { 'GET': ( {}, {"targets": [TARGET]}, ), }, '/v1/nodes/%s/volume/targets?fields=uuid,value' % NODE1['uuid']: { 'GET': ( {}, {"targets": [TARGET]}, ), }, '/v1/nodes/%s/maintenance' % NODE1['uuid']: { 'PUT': ( {}, None, ), 'DELETE': ( {}, None, ), }, '/v1/nodes/%s/states/power' % NODE1['uuid']: { 'PUT': ( {}, POWER_STATE, ), }, '/v1/nodes/%s/validate' % NODE1['uuid']: { 'GET': ( {}, DRIVER_IFACES, ), }, '/v1/nodes/%s/states/provision' % NODE1['uuid']: { 'PUT': ( {}, None, ), }, '/v1/nodes/%s/states' % NODE1['uuid']: { 'GET': ( {}, NODE_STATES, ), }, '/v1/nodes/%s/states/raid' % NODE1['uuid']: { 'PUT': ( {}, None, ), }, '/v1/nodes/%s/states/console' % NODE1['uuid']: { 'GET': ( {}, CONSOLE_DATA_ENABLED, ), 'PUT': ( {'enabled': CONSOLE_ENABLE}, None, ), }, '/v1/nodes/%s/states/console' % NODE2['uuid']: { 'GET': ( {}, CONSOLE_DATA_DISABLED, ), }, '/v1/nodes/%s/management/boot_device' % NODE1['uuid']: { 'GET': ( {}, BOOT_DEVICE, ), 'PUT': ( {}, None, ), }, '/v1/nodes/%s/management/inject_nmi' % NODE1['uuid']: { 'PUT': ( {}, None, ), }, '/v1/nodes/%s/management/boot_device/supported' % NODE1['uuid']: { 'GET': ( {}, SUPPORTED_BOOT_DEVICE, ), }, '/v1/nodes/%s/vendor_passthru/methods' % NODE1['uuid']: { 'GET': ( {}, NODE_VENDOR_PASSTHRU_METHOD, ), }, '/v1/nodes/%s/vifs' % NODE1['uuid']: { 'GET': ( {}, VIFS, ), }, '/v1/nodes/%s/traits' % NODE1['uuid']: { 'GET': ( {}, TRAITS, ), 'PUT': ( {}, None, ), 'DELETE': ( {}, None, ), }, '/v1/nodes/%s/traits/CUSTOM_FOO' % NODE1['uuid']: { 'PUT': ( {}, None, ), 'DELETE': ( {}, None, ), } } fake_responses_pagination = { '/v1/nodes': { 'GET': ( {}, {"nodes": [NODE1], "next": "http://127.0.0.1:6385/v1/nodes/?limit=1"} ), }, '/v1/nodes/?limit=1': { 'GET': ( {}, {"nodes": [NODE2]} ), }, '/v1/nodes/?marker=%s' % NODE1['uuid']: { 'GET': ( {}, {"nodes": [NODE2]} ), }, '/v1/nodes/%s/ports?limit=1' % NODE1['uuid']: { 'GET': ( {}, {"ports": [PORT]}, ), }, '/v1/nodes/%s/ports?marker=%s' % (NODE1['uuid'], PORT['uuid']): { 'GET': ( {}, {"ports": [PORT]}, ), }, '/v1/nodes/%s/portgroups?limit=1' % NODE1['uuid']: { 'GET': ( {}, {"portgroups": [PORTGROUP]}, ), }, '/v1/nodes/%s/portgroups?marker=%s' % (NODE1['uuid'], PORTGROUP['uuid']): { 'GET': ( {}, {"portgroups": [PORTGROUP]}, ), }, '/v1/nodes/%s/volume/connectors?limit=1' % NODE1['uuid']: { 'GET': ( {}, {"connectors": [CONNECTOR]}, ), }, '/v1/nodes/%s/volume/connectors?marker=%s' % (NODE1['uuid'], CONNECTOR['uuid']): { 'GET': ( {}, {"connectors": [CONNECTOR]}, ), }, '/v1/nodes/%s/volume/targets?limit=1' % NODE1['uuid']: { 'GET': ( {}, {"targets": [TARGET]}, ), }, '/v1/nodes/%s/volume/targets?marker=%s' % (NODE1['uuid'], TARGET['uuid']): { 'GET': ( {}, {"targets": [TARGET]}, ), }, } fake_responses_sorting = { '/v1/nodes/?sort_key=updated_at': { 'GET': ( {}, {"nodes": [NODE2, NODE1]} ), }, '/v1/nodes/?sort_dir=desc': { 'GET': ( {}, {"nodes": [NODE2, NODE1]} ), }, '/v1/nodes/%s/ports?sort_key=updated_at' % NODE1['uuid']: { 'GET': ( {}, {"ports": [PORT]}, ), }, '/v1/nodes/%s/ports?sort_dir=desc' % NODE1['uuid']: { 'GET': ( {}, {"ports": [PORT]}, ), }, '/v1/nodes/%s/portgroups?sort_key=updated_at' % NODE1['uuid']: { 'GET': ( {}, {"portgroups": [PORTGROUP]}, ), }, '/v1/nodes/%s/portgroups?sort_dir=desc' % NODE1['uuid']: { 'GET': ( {}, {"portgroups": [PORTGROUP]}, ), }, '/v1/nodes/%s/volume/connectors?sort_key=updated_at' % NODE1['uuid']: { 'GET': ( {}, {"connectors": [CONNECTOR]}, ), }, '/v1/nodes/%s/volume/connectors?sort_dir=desc' % NODE1['uuid']: { 'GET': ( {}, {"connectors": [CONNECTOR]}, ), }, '/v1/nodes/%s/volume/targets?sort_key=updated_at' % NODE1['uuid']: { 'GET': ( {}, {"targets": [TARGET]}, ), }, '/v1/nodes/%s/volume/targets?sort_dir=desc' % NODE1['uuid']: { 'GET': ( {}, {"targets": [TARGET]}, ), }, } class NodeManagerTest(testtools.TestCase): def setUp(self): super(NodeManagerTest, self).setUp() self.api = utils.FakeAPI(fake_responses) self.mgr = node.NodeManager(self.api) def test_node_list(self): nodes = self.mgr.list() expect = [ ('GET', '/v1/nodes', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(2, len(nodes)) def test_node_list_shows_name(self): nodes = self.mgr.list() self.assertIsNotNone(getattr(nodes[0], 'name')) def test_node_list_limit(self): self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = node.NodeManager(self.api) nodes = self.mgr.list(limit=1) expect = [ ('GET', '/v1/nodes/?limit=1', {}, None) ] self.assertEqual(expect, self.api.calls) self.assertThat(nodes, HasLength(1)) def test_node_list_marker(self): self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = node.NodeManager(self.api) nodes = self.mgr.list(marker=NODE1['uuid']) expect = [ ('GET', '/v1/nodes/?marker=%s' % NODE1['uuid'], {}, None) ] self.assertEqual(expect, self.api.calls) self.assertThat(nodes, HasLength(1)) def test_node_list_pagination_no_limit(self): self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = node.NodeManager(self.api) nodes = self.mgr.list(limit=0) expect = [ ('GET', '/v1/nodes', {}, None), ('GET', '/v1/nodes/?limit=1', {}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(2, len(nodes)) def test_node_list_sort_key(self): self.api = utils.FakeAPI(fake_responses_sorting) self.mgr = node.NodeManager(self.api) nodes = self.mgr.list(sort_key='updated_at') expect = [ ('GET', '/v1/nodes/?sort_key=updated_at', {}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(2, len(nodes)) def test_node_list_sort_dir(self): self.api = utils.FakeAPI(fake_responses_sorting) self.mgr = node.NodeManager(self.api) nodes = self.mgr.list(sort_dir='desc') expect = [ ('GET', '/v1/nodes/?sort_dir=desc', {}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(2, len(nodes)) def test_node_list_associated(self): nodes = self.mgr.list(associated=True) expect = [ ('GET', '/v1/nodes/?associated=True', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(nodes, HasLength(1)) self.assertEqual(NODE2['uuid'], getattr(nodes[0], 'uuid')) def test_node_list_unassociated(self): nodes = self.mgr.list(associated=False) expect = [ ('GET', '/v1/nodes/?associated=False', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(nodes, HasLength(1)) self.assertEqual(NODE1['uuid'], getattr(nodes[0], 'uuid')) def test_node_list_unassociated_string(self): nodes = self.mgr.list(associated="False") expect = [ ('GET', '/v1/nodes/?associated=False', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(nodes, HasLength(1)) self.assertEqual(NODE1['uuid'], getattr(nodes[0], 'uuid')) def test_node_list_maintenance(self): nodes = self.mgr.list(maintenance=True) expect = [ ('GET', '/v1/nodes/?maintenance=True', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(nodes, HasLength(1)) self.assertEqual(NODE2['uuid'], getattr(nodes[0], 'uuid')) def test_node_list_maintenance_string(self): nodes = self.mgr.list(maintenance="True") expect = [ ('GET', '/v1/nodes/?maintenance=True', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(nodes, HasLength(1)) self.assertEqual(NODE2['uuid'], getattr(nodes[0], 'uuid')) def test_node_list_provision_state(self): nodes = self.mgr.list(provision_state="available") expect = [ ('GET', '/v1/nodes/?provision_state=available', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(nodes, HasLength(1)) self.assertEqual(NODE1['uuid'], getattr(nodes[0], 'uuid')) def test_node_list_provision_state_fail(self): self.assertRaises(KeyError, self.mgr.list, provision_state="test") def test_node_list_driver(self): nodes = self.mgr.list(driver="fake") expect = [ ('GET', '/v1/nodes/?driver=fake', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(nodes, HasLength(1)) self.assertEqual(NODE1['uuid'], getattr(nodes[0], 'uuid')) def test_node_list_resource_class(self): nodes = self.mgr.list(resource_class="foo") expect = [ ('GET', '/v1/nodes/?resource_class=foo', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(nodes, HasLength(1)) self.assertEqual(NODE1['uuid'], getattr(nodes[0], 'uuid')) def test_node_list_chassis(self): ch2 = NODE2['chassis_uuid'] nodes = self.mgr.list(chassis=ch2) expect = [ ('GET', '/v1/nodes/?chassis_uuid=%s' % ch2, {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(nodes, HasLength(1)) self.assertEqual(NODE2['uuid'], getattr(nodes[0], 'uuid')) def test_node_list_no_maintenance(self): nodes = self.mgr.list(maintenance=False) expect = [ ('GET', '/v1/nodes/?maintenance=False', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(nodes, HasLength(1)) self.assertEqual(NODE1['uuid'], getattr(nodes[0], 'uuid')) def test_node_list_associated_and_maintenance(self): nodes = self.mgr.list(associated=True, maintenance=True) expect = [ ('GET', '/v1/nodes/?associated=True&maintenance=True', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(nodes, HasLength(1)) self.assertEqual(NODE2['uuid'], getattr(nodes[0], 'uuid')) def test_node_list_detail(self): nodes = self.mgr.list(detail=True) expect = [ ('GET', '/v1/nodes/detail', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(2, len(nodes)) self.assertEqual(nodes[0].extra, {}) def test_node_list_fields(self): nodes = self.mgr.list(fields=['uuid', 'extra']) expect = [ ('GET', '/v1/nodes/?fields=uuid,extra', {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(nodes)) def test_node_list_detail_and_fields_fail(self): self.assertRaises(exc.InvalidAttribute, self.mgr.list, detail=True, fields=['uuid', 'extra']) def test_node_show(self): node = self.mgr.get(NODE1['uuid']) expect = [ ('GET', '/v1/nodes/%s' % NODE1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(NODE1['uuid'], node.uuid) def test_node_show_by_instance(self): node = self.mgr.get_by_instance_uuid(NODE2['instance_uuid']) expect = [ ('GET', '/v1/nodes/detail?instance_uuid=%s' % NODE2['instance_uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(NODE2['uuid'], node.uuid) def test_node_show_by_name(self): node = self.mgr.get(NODE1['name']) expect = [ ('GET', '/v1/nodes/%s' % NODE1['name'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(NODE1['uuid'], node.uuid) def test_node_show_fields(self): node = self.mgr.get(NODE1['uuid'], fields=['uuid', 'extra']) expect = [ ('GET', '/v1/nodes/%s?fields=uuid,extra' % NODE1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(NODE1['uuid'], node.uuid) def test_create(self): node = self.mgr.create(**CREATE_NODE) expect = [ ('POST', '/v1/nodes', {}, CREATE_NODE), ] self.assertEqual(expect, self.api.calls) self.assertTrue(node) def test_create_with_uuid(self): node = self.mgr.create(**CREATE_WITH_UUID) expect = [ ('POST', '/v1/nodes', {}, CREATE_WITH_UUID), ] self.assertEqual(expect, self.api.calls) self.assertTrue(node) def test_delete(self): node = self.mgr.delete(node_id=NODE1['uuid']) expect = [ ('DELETE', '/v1/nodes/%s' % NODE1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(node) def test_update(self): patch = {'op': 'replace', 'value': NEW_DRIVER, 'path': '/driver'} node = self.mgr.update(node_id=NODE1['uuid'], patch=patch) expect = [ ('PATCH', '/v1/nodes/%s' % NODE1['uuid'], {}, patch), ] self.assertEqual(expect, self.api.calls) self.assertEqual(NEW_DRIVER, node.driver) def test_node_port_list_with_uuid(self): ports = self.mgr.list_ports(NODE1['uuid']) expect = [ ('GET', '/v1/nodes/%s/ports' % NODE1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(ports, HasLength(1)) self.assertEqual(PORT['uuid'], ports[0].uuid) self.assertEqual(PORT['address'], ports[0].address) def test_node_port_list_with_name(self): ports = self.mgr.list_ports(NODE1['name']) expect = [ ('GET', '/v1/nodes/%s/ports' % NODE1['name'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(ports, HasLength(1)) self.assertEqual(PORT['uuid'], ports[0].uuid) self.assertEqual(PORT['address'], ports[0].address) def test_node_port_list_limit(self): self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = node.NodeManager(self.api) ports = self.mgr.list_ports(NODE1['uuid'], limit=1) expect = [ ('GET', '/v1/nodes/%s/ports?limit=1' % NODE1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(ports, HasLength(1)) self.assertEqual(PORT['uuid'], ports[0].uuid) self.assertEqual(PORT['address'], ports[0].address) def test_node_port_list_marker(self): self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = node.NodeManager(self.api) ports = self.mgr.list_ports(NODE1['uuid'], marker=PORT['uuid']) expect = [ ('GET', '/v1/nodes/%s/ports?marker=%s' % (NODE1['uuid'], PORT['uuid']), {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(ports, HasLength(1)) def test_node_port_list_sort_key(self): self.api = utils.FakeAPI(fake_responses_sorting) self.mgr = node.NodeManager(self.api) ports = self.mgr.list_ports(NODE1['uuid'], sort_key='updated_at') expect = [ ('GET', '/v1/nodes/%s/ports?sort_key=updated_at' % NODE1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(ports, HasLength(1)) self.assertEqual(PORT['uuid'], ports[0].uuid) self.assertEqual(PORT['address'], ports[0].address) def test_node_port_list_sort_dir(self): self.api = utils.FakeAPI(fake_responses_sorting) self.mgr = node.NodeManager(self.api) ports = self.mgr.list_ports(NODE1['uuid'], sort_dir='desc') expect = [ ('GET', '/v1/nodes/%s/ports?sort_dir=desc' % NODE1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(ports, HasLength(1)) self.assertEqual(PORT['uuid'], ports[0].uuid) self.assertEqual(PORT['address'], ports[0].address) def test_node_port_list_detail_with_uuid(self): ports = self.mgr.list_ports(NODE1['uuid'], detail=True) expect = [ ('GET', '/v1/nodes/%s/ports/detail' % NODE1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(ports, HasLength(1)) def test_node_port_list_detail_with_name(self): ports = self.mgr.list_ports(NODE1['name'], detail=True) expect = [ ('GET', '/v1/nodes/%s/ports/detail' % NODE1['name'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(ports, HasLength(1)) def test_node_port_list_fields(self): ports = self.mgr.list_ports(NODE1['uuid'], fields=['uuid', 'address']) expect = [ ('GET', '/v1/nodes/%s/ports?fields=uuid,address' % NODE1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertThat(ports, HasLength(1)) def test_node_port_list_detail_and_fields_fail(self): self.assertRaises(exc.InvalidAttribute, self.mgr.list_ports, NODE1['uuid'], detail=True, fields=['uuid', 'extra']) def _validate_node_volume_connector_list(self, expect, volume_connectors): self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(volume_connectors)) self.assertIsInstance(volume_connectors[0], volume_connector.VolumeConnector) self.assertEqual(CONNECTOR['uuid'], volume_connectors[0].uuid) self.assertEqual(CONNECTOR['type'], volume_connectors[0].type) self.assertEqual(CONNECTOR['connector_id'], volume_connectors[0].connector_id) def test_node_volume_connector_list(self): volume_connectors = self.mgr.list_volume_connectors(NODE1['uuid']) expect = [ ('GET', '/v1/nodes/%s/volume/connectors' % NODE1['uuid'], {}, None), ] self._validate_node_volume_connector_list(expect, volume_connectors) def test_node_volume_connector_list_limit(self): self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = node.NodeManager(self.api) volume_connectors = self.mgr.list_volume_connectors(NODE1['uuid'], limit=1) expect = [ ('GET', '/v1/nodes/%s/volume/connectors?limit=1' % NODE1['uuid'], {}, None), ] self._validate_node_volume_connector_list(expect, volume_connectors) def test_node_volume_connector_list_marker(self): self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = node.NodeManager(self.api) volume_connectors = self.mgr.list_volume_connectors( NODE1['uuid'], marker=CONNECTOR['uuid']) expect = [ ('GET', '/v1/nodes/%s/volume/connectors?marker=%s' % ( NODE1['uuid'], CONNECTOR['uuid']), {}, None), ] self._validate_node_volume_connector_list(expect, volume_connectors) def test_node_volume_connector_list_sort_key(self): self.api = utils.FakeAPI(fake_responses_sorting) self.mgr = node.NodeManager(self.api) volume_connectors = self.mgr.list_volume_connectors( NODE1['uuid'], sort_key='updated_at') expect = [ ('GET', '/v1/nodes/%s/volume/connectors?sort_key=updated_at' % NODE1['uuid'], {}, None), ] self._validate_node_volume_connector_list(expect, volume_connectors) def test_node_volume_connector_list_sort_dir(self): self.api = utils.FakeAPI(fake_responses_sorting) self.mgr = node.NodeManager(self.api) volume_connectors = self.mgr.list_volume_connectors(NODE1['uuid'], sort_dir='desc') expect = [ ('GET', '/v1/nodes/%s/volume/connectors?sort_dir=desc' % NODE1['uuid'], {}, None), ] self._validate_node_volume_connector_list(expect, volume_connectors) def test_node_volume_connector_list_detail(self): volume_connectors = self.mgr.list_volume_connectors(NODE1['uuid'], detail=True) expect = [ ('GET', '/v1/nodes/%s/volume/connectors?detail=True' % NODE1['uuid'], {}, None), ] self._validate_node_volume_connector_list(expect, volume_connectors) def test_node_volume_connector_list_fields(self): volume_connectors = self.mgr.list_volume_connectors( NODE1['uuid'], fields=['uuid', 'connector_id']) expect = [ ('GET', '/v1/nodes/%s/volume/connectors?fields=uuid,connector_id' % NODE1['uuid'], {}, None), ] self._validate_node_volume_connector_list(expect, volume_connectors) def test_node_volume_connector_list_detail_and_fields_fail(self): self.assertRaises(exc.InvalidAttribute, self.mgr.list_volume_connectors, NODE1['uuid'], detail=True, fields=['uuid', 'extra']) def _validate_node_volume_target_list(self, expect, volume_targets): self.assertEqual(expect, self.api.calls) self.assertEqual(1, len(volume_targets)) self.assertIsInstance(volume_targets[0], volume_target.VolumeTarget) self.assertEqual(TARGET['uuid'], volume_targets[0].uuid) self.assertEqual(TARGET['volume_type'], volume_targets[0].volume_type) self.assertEqual(TARGET['boot_index'], volume_targets[0].boot_index) self.assertEqual(TARGET['volume_id'], volume_targets[0].volume_id) self.assertEqual(TARGET['node_uuid'], volume_targets[0].node_uuid) def test_node_volume_target_list(self): volume_targets = self.mgr.list_volume_targets(NODE1['uuid']) expect = [ ('GET', '/v1/nodes/%s/volume/targets' % NODE1['uuid'], {}, None), ] self._validate_node_volume_target_list(expect, volume_targets) def test_node_volume_target_list_limit(self): self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = node.NodeManager(self.api) volume_targets = self.mgr.list_volume_targets(NODE1['uuid'], limit=1) expect = [ ('GET', '/v1/nodes/%s/volume/targets?limit=1' % NODE1['uuid'], {}, None), ] self._validate_node_volume_target_list(expect, volume_targets) def test_node_volume_target_list_marker(self): self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = node.NodeManager(self.api) volume_targets = self.mgr.list_volume_targets( NODE1['uuid'], marker=TARGET['uuid']) expect = [ ('GET', '/v1/nodes/%s/volume/targets?marker=%s' % ( NODE1['uuid'], TARGET['uuid']), {}, None), ] self._validate_node_volume_target_list(expect, volume_targets) def test_node_volume_target_list_sort_key(self): self.api = utils.FakeAPI(fake_responses_sorting) self.mgr = node.NodeManager(self.api) volume_targets = self.mgr.list_volume_targets( NODE1['uuid'], sort_key='updated_at') expect = [ ('GET', '/v1/nodes/%s/volume/targets?sort_key=updated_at' % NODE1['uuid'], {}, None), ] self._validate_node_volume_target_list(expect, volume_targets) def test_node_volume_target_list_sort_dir(self): self.api = utils.FakeAPI(fake_responses_sorting) self.mgr = node.NodeManager(self.api) volume_targets = self.mgr.list_volume_targets(NODE1['uuid'], sort_dir='desc') expect = [ ('GET', '/v1/nodes/%s/volume/targets?sort_dir=desc' % NODE1['uuid'], {}, None), ] self._validate_node_volume_target_list(expect, volume_targets) def test_node_volume_target_list_detail(self): volume_targets = self.mgr.list_volume_targets(NODE1['uuid'], detail=True) expect = [ ('GET', '/v1/nodes/%s/volume/targets?detail=True' % NODE1['uuid'], {}, None), ] self._validate_node_volume_target_list(expect, volume_targets) def test_node_volume_target_list_fields(self): volume_targets = self.mgr.list_volume_targets( NODE1['uuid'], fields=['uuid', 'value']) expect = [ ('GET', '/v1/nodes/%s/volume/targets?fields=uuid,value' % NODE1['uuid'], {}, None), ] self._validate_node_volume_target_list(expect, volume_targets) def test_node_volume_target_list_detail_and_fields_fail(self): self.assertRaises(exc.InvalidAttribute, self.mgr.list_volume_targets, NODE1['uuid'], detail=True, fields=['uuid', 'extra']) def test_node_set_maintenance_true(self): maintenance = self.mgr.set_maintenance(NODE1['uuid'], 'true', maint_reason='reason') body = {'reason': 'reason'} expect = [ ('PUT', '/v1/nodes/%s/maintenance' % NODE1['uuid'], {}, body), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(maintenance) def test_node_set_maintenance_false(self): maintenance = self.mgr.set_maintenance(NODE1['uuid'], 'false') expect = [ ('DELETE', '/v1/nodes/%s/maintenance' % NODE1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(maintenance) def test_node_set_maintenance_on(self): maintenance = self.mgr.set_maintenance(NODE1['uuid'], 'on', maint_reason='reason') body = {'reason': 'reason'} expect = [ ('PUT', '/v1/nodes/%s/maintenance' % NODE1['uuid'], {}, body), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(maintenance) def test_node_set_maintenance_off(self): maintenance = self.mgr.set_maintenance(NODE1['uuid'], 'off') expect = [ ('DELETE', '/v1/nodes/%s/maintenance' % NODE1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(maintenance) def test_node_set_maintenance_bad(self): self.assertRaises(exc.InvalidAttribute, self.mgr.set_maintenance, NODE1['uuid'], 'bad') def test_node_set_maintenance_bool(self): maintenance = self.mgr.set_maintenance(NODE1['uuid'], True, maint_reason='reason') body = {'reason': 'reason'} expect = [ ('PUT', '/v1/nodes/%s/maintenance' % NODE1['uuid'], {}, body), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(maintenance) def test_node_set_power_state(self): power_state = self.mgr.set_power_state(NODE1['uuid'], "off") body = {'target': 'power off'} expect = [ ('PUT', '/v1/nodes/%s/states/power' % NODE1['uuid'], {}, body), ] self.assertEqual(expect, self.api.calls) self.assertEqual('power off', power_state.target_power_state) def test_node_set_power_timeout(self): power_state = self.mgr.set_power_state(NODE1['uuid'], "off", timeout=2) body = {'target': 'power off', 'timeout': 2} expect = [ ('PUT', '/v1/nodes/%s/states/power' % NODE1['uuid'], {}, body), ] self.assertEqual(expect, self.api.calls) self.assertEqual('power off', power_state.target_power_state) def test_node_set_power_timeout_str(self): power_state = self.mgr.set_power_state(NODE1['uuid'], "off", timeout="2") body = {'target': 'power off', 'timeout': 2} expect = [ ('PUT', '/v1/nodes/%s/states/power' % NODE1['uuid'], {}, body), ] self.assertEqual(expect, self.api.calls) self.assertEqual('power off', power_state.target_power_state) def test_node_set_power_state_soft(self): power_state = self.mgr.set_power_state(NODE1['uuid'], "off", soft=True) body = {'target': 'soft power off'} expect = [ ('PUT', '/v1/nodes/%s/states/power' % NODE1['uuid'], {}, body), ] self.assertEqual(expect, self.api.calls) self.assertEqual('power off', power_state.target_power_state) def test_node_set_power_state_soft_fail(self): self.assertRaises(ValueError, self.mgr.set_power_state, NODE1['uuid'], 'on', soft=True) def test_node_set_power_state_on_timeout_fail(self): self.assertRaises(ValueError, self.mgr.set_power_state, NODE1['uuid'], 'off', soft=False, timeout=0) def test_node_set_power_state_on_timeout_type_error(self): self.assertRaises(ValueError, self.mgr.set_power_state, NODE1['uuid'], 'off', soft=False, timeout='a') def test_set_target_raid_config(self): self.mgr.set_target_raid_config( NODE1['uuid'], {'fake': 'config'}) expect = [('PUT', '/v1/nodes/%s/states/raid' % NODE1['uuid'], {}, {'fake': 'config'})] self.assertEqual(expect, self.api.calls) def test_node_validate(self): ifaces = self.mgr.validate(NODE1['uuid']) expect = [ ('GET', '/v1/nodes/%s/validate' % NODE1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(DRIVER_IFACES['power'], ifaces.power) self.assertEqual(DRIVER_IFACES['deploy'], ifaces.deploy) self.assertEqual(DRIVER_IFACES['rescue'], ifaces.rescue) self.assertEqual(DRIVER_IFACES['console'], ifaces.console) def test_node_set_provision_state(self): target_state = 'active' self.mgr.set_provision_state(NODE1['uuid'], target_state) body = {'target': target_state} expect = [ ('PUT', '/v1/nodes/%s/states/provision' % NODE1['uuid'], {}, body), ] self.assertEqual(expect, self.api.calls) def test_node_set_provision_state_with_configdrive(self): target_state = 'active' self.mgr.set_provision_state(NODE1['uuid'], target_state, configdrive='foo') body = {'target': target_state, 'configdrive': 'foo'} expect = [ ('PUT', '/v1/nodes/%s/states/provision' % NODE1['uuid'], {}, body), ] self.assertEqual(expect, self.api.calls) def test_node_set_provision_state_with_configdrive_file(self): target_state = 'active' file_content = b'foo bar cat meow dog bark' with tempfile.NamedTemporaryFile() as f: f.write(file_content) f.flush() self.mgr.set_provision_state(NODE1['uuid'], target_state, configdrive=f.name) body = {'target': target_state, 'configdrive': file_content} expect = [ ('PUT', '/v1/nodes/%s/states/provision' % NODE1['uuid'], {}, body), ] self.assertEqual(expect, self.api.calls) @mock.patch.object(common_utils, 'make_configdrive', autospec=True) def test_node_set_provision_state_with_configdrive_dir(self, mock_configdrive): mock_configdrive.return_value = 'fake-configdrive' target_state = 'active' with common_utils.tempdir() as dirname: self.mgr.set_provision_state(NODE1['uuid'], target_state, configdrive=dirname) mock_configdrive.assert_called_once_with(dirname) body = {'target': target_state, 'configdrive': 'fake-configdrive'} expect = [ ('PUT', '/v1/nodes/%s/states/provision' % NODE1['uuid'], {}, body), ] self.assertEqual(expect, self.api.calls) def test_node_set_provision_state_with_cleansteps(self): cleansteps = [{"step": "upgrade", "interface": "deploy"}] target_state = 'clean' self.mgr.set_provision_state(NODE1['uuid'], target_state, cleansteps=cleansteps) body = {'target': target_state, 'clean_steps': cleansteps} expect = [ ('PUT', '/v1/nodes/%s/states/provision' % NODE1['uuid'], {}, body), ] self.assertEqual(expect, self.api.calls) def test_node_states(self): states = self.mgr.states(NODE1['uuid']) expect = [ ('GET', '/v1/nodes/%s/states' % NODE1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) expected_fields = ['last_error', 'power_state', 'provision_state', 'target_power_state', 'target_provision_state'] self.assertEqual(sorted(expected_fields), sorted(states.to_dict().keys())) def test_node_set_console_mode(self): global ENABLE for enabled in ['true', True, 'False', False]: self.api.calls = [] ENABLE = enabled self.mgr.set_console_mode(NODE1['uuid'], enabled) body = {'enabled': enabled} expect = [ ('PUT', '/v1/nodes/%s/states/console' % NODE1['uuid'], {}, body), ] self.assertEqual(expect, self.api.calls) def test_node_get_console(self): info = self.mgr.get_console(NODE1['uuid']) expect = [ ('GET', '/v1/nodes/%s/states/console' % NODE1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(CONSOLE_DATA_ENABLED, info) def test_node_get_console_disabled(self): info = self.mgr.get_console(NODE2['uuid']) expect = [ ('GET', '/v1/nodes/%s/states/console' % NODE2['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(CONSOLE_DATA_DISABLED, info) @mock.patch.object(node.NodeManager, 'update', autospec=True) def test_vendor_passthru_update(self, update_mock): # For now just mock the tests because vendor-passthru doesn't return # anything to verify. vendor_passthru_args = {'arg1': 'val1'} kwargs = { 'node_id': 'node_uuid', 'method': 'method', 'args': vendor_passthru_args } final_path = 'node_uuid/vendor_passthru/method' for http_method in ('POST', 'PUT', 'PATCH'): kwargs['http_method'] = http_method self.mgr.vendor_passthru(**kwargs) update_mock.assert_called_once_with(mock.ANY, final_path, vendor_passthru_args, http_method=http_method) update_mock.reset_mock() @mock.patch.object(node.NodeManager, 'get', autospec=True) def test_vendor_passthru_get(self, get_mock): kwargs = { 'node_id': 'node_uuid', 'method': 'method', 'http_method': 'GET', } final_path = 'node_uuid/vendor_passthru/method' self.mgr.vendor_passthru(**kwargs) get_mock.assert_called_once_with(mock.ANY, final_path) @mock.patch.object(node.NodeManager, 'delete', autospec=True) def test_vendor_passthru_delete(self, delete_mock): kwargs = { 'node_id': 'node_uuid', 'method': 'method', 'http_method': 'DELETE', } final_path = 'node_uuid/vendor_passthru/method' self.mgr.vendor_passthru(**kwargs) delete_mock.assert_called_once_with(mock.ANY, final_path) @mock.patch.object(node.NodeManager, 'delete', autospec=True) def test_vendor_passthru_unknown_http_method(self, delete_mock): kwargs = { 'node_id': 'node_uuid', 'method': 'method', 'http_method': 'UNKNOWN', } self.assertRaises(exc.InvalidAttribute, self.mgr.vendor_passthru, **kwargs) @mock.patch.object(node.NodeManager, '_list', autospec=True) def test_vif_list(self, _list_mock): kwargs = { 'node_ident': NODE1['uuid'], } final_path = '/v1/nodes/%s/vifs' % NODE1['uuid'] self.mgr.vif_list(**kwargs) _list_mock.assert_called_once_with(mock.ANY, final_path, "vifs") @mock.patch.object(node.NodeManager, 'update', autospec=True) def test_vif_attach(self, update_mock): kwargs = { 'node_ident': NODE1['uuid'], 'vif_id': 'vif_id', } final_path = '%s/vifs' % NODE1['uuid'] self.mgr.vif_attach(**kwargs) update_mock.assert_called_once_with( mock.ANY, final_path, {'id': 'vif_id'}, http_method="POST") @mock.patch.object(node.NodeManager, 'update', autospec=True) def test_vif_attach_custom_fields(self, update_mock): kwargs = { 'node_ident': NODE1['uuid'], 'vif_id': 'vif_id', 'foo': 'bar', } final_path = '%s/vifs' % NODE1['uuid'] self.mgr.vif_attach(**kwargs) update_mock.assert_called_once_with( mock.ANY, final_path, {'id': 'vif_id', 'foo': 'bar'}, http_method="POST") @mock.patch.object(node.NodeManager, 'update', autospec=True) def test_vif_attach_custom_fields_id(self, update_mock): kwargs = { 'node_ident': NODE1['uuid'], 'vif_id': 'vif_id', 'id': 'bar', } self.assertRaises( exc.InvalidAttribute, self.mgr.vif_attach, **kwargs) @mock.patch.object(node.NodeManager, 'delete', autospec=True) def test_vif_detach(self, delete_mock): kwargs = { 'node_ident': NODE1['uuid'], 'vif_id': 'vif_id', } final_path = '%s/vifs/vif_id' % NODE1['uuid'] self.mgr.vif_detach(**kwargs) delete_mock.assert_called_once_with(mock.ANY, final_path) def _test_node_set_boot_device(self, boot_device, persistent=False): self.mgr.set_boot_device(NODE1['uuid'], boot_device, persistent) body = {'boot_device': boot_device, 'persistent': persistent} expect = [ ('PUT', '/v1/nodes/%s/management/boot_device' % NODE1['uuid'], {}, body), ] self.assertEqual(expect, self.api.calls) def test_node_set_boot_device(self): self._test_node_set_boot_device('pxe') def test_node_set_boot_device_persistent(self): self._test_node_set_boot_device('pxe', persistent=True) def test_node_get_boot_device(self): boot_device = self.mgr.get_boot_device(NODE1['uuid']) expect = [ ('GET', '/v1/nodes/%s/management/boot_device' % NODE1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(BOOT_DEVICE, boot_device) def test_node_inject_nmi(self): self.mgr.inject_nmi(NODE1['uuid']) expect = [ ('PUT', '/v1/nodes/%s/management/inject_nmi' % NODE1['uuid'], {}, {}), ] self.assertEqual(expect, self.api.calls) def test_node_get_supported_boot_devices(self): boot_device = self.mgr.get_supported_boot_devices(NODE1['uuid']) expect = [ ('GET', '/v1/nodes/%s/management/boot_device/supported' % NODE1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(SUPPORTED_BOOT_DEVICE, boot_device) def test_node_get_vendor_passthru_methods(self): vendor_methods = self.mgr.get_vendor_passthru_methods(NODE1['uuid']) expect = [ ('GET', '/v1/nodes/%s/vendor_passthru/methods' % NODE1['uuid'], {}, None) ] self.assertEqual(expect, self.api.calls) self.assertEqual(NODE_VENDOR_PASSTHRU_METHOD, vendor_methods) def _fake_node_for_wait(self, state, error=None, target=None): spec = ['provision_state', 'last_error', 'target_provision_state'] return mock.Mock(provision_state=state, last_error=error, target_provision_state=target, spec=spec) @mock.patch.object(time, 'sleep', autospec=True) @mock.patch.object(node.NodeManager, 'get', autospec=True) def test_wait_for_provision_state(self, mock_get, mock_sleep): mock_get.side_effect = [ self._fake_node_for_wait('deploying', target='active'), self._fake_node_for_wait('deploying', target='active'), self._fake_node_for_wait('active') ] self.mgr.wait_for_provision_state('node', 'active') mock_get.assert_called_with(self.mgr, 'node') self.assertEqual(3, mock_get.call_count) mock_sleep.assert_called_with(node._DEFAULT_POLL_INTERVAL) self.assertEqual(2, mock_sleep.call_count) @mock.patch.object(time, 'sleep', autospec=True) @mock.patch.object(node.NodeManager, 'get', autospec=True) def test_wait_for_provision_state_timeout(self, mock_get, mock_sleep): mock_get.return_value = self._fake_node_for_wait( 'deploying', target='active') self.assertRaises(exc.StateTransitionTimeout, self.mgr.wait_for_provision_state, 'node', 'active', timeout=0.001) @mock.patch.object(time, 'sleep', autospec=True) @mock.patch.object(node.NodeManager, 'get', autospec=True) def test_wait_for_provision_state_error(self, mock_get, mock_sleep): mock_get.side_effect = [ self._fake_node_for_wait('deploying', target='active'), self._fake_node_for_wait('deploy failed', error='boom'), ] self.assertRaisesRegex(exc.StateTransitionFailed, 'boom', self.mgr.wait_for_provision_state, 'node', 'active') mock_get.assert_called_with(self.mgr, 'node') self.assertEqual(2, mock_get.call_count) mock_sleep.assert_called_with(node._DEFAULT_POLL_INTERVAL) self.assertEqual(1, mock_sleep.call_count) @mock.patch.object(node.NodeManager, 'get', autospec=True) def test_wait_for_provision_state_custom_delay(self, mock_get): mock_get.side_effect = [ self._fake_node_for_wait('deploying', target='active'), self._fake_node_for_wait('active') ] delay_mock = mock.Mock() self.mgr.wait_for_provision_state('node', 'active', poll_delay_function=delay_mock) mock_get.assert_called_with(self.mgr, 'node') self.assertEqual(2, mock_get.call_count) delay_mock.assert_called_with(node._DEFAULT_POLL_INTERVAL) self.assertEqual(1, delay_mock.call_count) def test_wait_for_provision_state_wrong_input(self): self.assertRaises(ValueError, self.mgr.wait_for_provision_state, 'node', 'active', timeout='42') self.assertRaises(ValueError, self.mgr.wait_for_provision_state, 'node', 'active', timeout=-1) self.assertRaises(TypeError, self.mgr.wait_for_provision_state, 'node', 'active', poll_delay_function={}) @mock.patch.object(time, 'sleep', autospec=True) @mock.patch.object(node.NodeManager, 'get', autospec=True) def test_wait_for_provision_state_unexpected_stable_state( self, mock_get, mock_sleep): # This simulates aborted deployment mock_get.side_effect = [ self._fake_node_for_wait('deploying', target='active'), self._fake_node_for_wait('available'), ] self.assertRaisesRegex(exc.StateTransitionFailed, 'available', self.mgr.wait_for_provision_state, 'node', 'active') mock_get.assert_called_with(self.mgr, 'node') self.assertEqual(2, mock_get.call_count) mock_sleep.assert_called_with(node._DEFAULT_POLL_INTERVAL) self.assertEqual(1, mock_sleep.call_count) @mock.patch.object(time, 'sleep', autospec=True) @mock.patch.object(node.NodeManager, 'get', autospec=True) def test_wait_for_provision_state_unexpected_stable_state_allowed( self, mock_get, mock_sleep): mock_get.side_effect = [ self._fake_node_for_wait('deploying', target='active'), self._fake_node_for_wait('available'), self._fake_node_for_wait('deploying', target='active'), self._fake_node_for_wait('active'), ] self.mgr.wait_for_provision_state('node', 'active', fail_on_unexpected_state=False) mock_get.assert_called_with(self.mgr, 'node') self.assertEqual(4, mock_get.call_count) mock_sleep.assert_called_with(node._DEFAULT_POLL_INTERVAL) self.assertEqual(3, mock_sleep.call_count) def test_node_get_traits(self): traits = self.mgr.get_traits(NODE1['uuid']) expect = [ ('GET', '/v1/nodes/%s/traits' % NODE1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(TRAITS['traits'], traits) def test_node_add_trait(self): trait = 'CUSTOM_FOO' resp = self.mgr.add_trait(NODE1['uuid'], trait) expect = [ ('PUT', '/v1/nodes/%s/traits/%s' % (NODE1['uuid'], trait), {}, None), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(resp) def test_node_set_traits(self): traits = ['CUSTOM_FOO', 'CUSTOM_BAR'] resp = self.mgr.set_traits(NODE1['uuid'], traits) expect = [ ('PUT', '/v1/nodes/%s/traits' % NODE1['uuid'], {}, {'traits': traits}), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(resp) def test_node_remove_all_traits(self): resp = self.mgr.remove_all_traits(NODE1['uuid']) expect = [ ('DELETE', '/v1/nodes/%s/traits' % NODE1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(resp) def test_node_remove_trait(self): trait = 'CUSTOM_FOO' resp = self.mgr.remove_trait(NODE1['uuid'], trait) expect = [ ('DELETE', '/v1/nodes/%s/traits/%s' % (NODE1['uuid'], trait), {}, None), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(resp) python-ironicclient-2.2.0/ironicclient/tests/unit/v1/test_portgroup_shell.py0000666000175100017510000003031413232474343027542 0ustar zuulzuul00000000000000# Copyright 2016 SAP Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import mock from ironicclient.common.apiclient import exceptions from ironicclient.common import cliutils from ironicclient.common import utils as commonutils from ironicclient.tests.unit import utils import ironicclient.v1.portgroup_shell as pg_shell class PortgroupShellTest(utils.BaseTestCase): def test_portgroup_show(self): actual = {} fake_print_dict = lambda data, *args, **kwargs: actual.update(data) with mock.patch.object(cliutils, 'print_dict', fake_print_dict): portgroup = object() pg_shell._print_portgroup_show(portgroup) exp = ['address', 'created_at', 'extra', 'standalone_ports_supported', 'node_uuid', 'updated_at', 'uuid', 'name', 'internal_info', 'mode', 'properties'] act = actual.keys() self.assertEqual(sorted(exp), sorted(act)) def test_do_portgroup_show(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.portgroup = 'portgroup_uuid' args.address = False args.fields = None args.json = False pg_shell.do_portgroup_show(client_mock, args) client_mock.portgroup.get.assert_called_once_with('portgroup_uuid', fields=None) self.assertFalse(client_mock.portgroup.get_by_address.called) def test_do_portgroup_show_space_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.portgroup = ' ' args.address = False args.json = False self.assertRaises(exceptions.CommandError, pg_shell.do_portgroup_show, client_mock, args) def test_do_portgroup_show_empty_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.portgroup = '' args.address = False args.json = False self.assertRaises(exceptions.CommandError, pg_shell.do_portgroup_show, client_mock, args) def test_do_portgroup_show_by_address(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.portgroup = 'portgroup_address' args.address = True args.fields = None args.json = False pg_shell.do_portgroup_show(client_mock, args) client_mock.portgroup.get_by_address.assert_called_once_with( 'portgroup_address', fields=None) self.assertFalse(client_mock.portgroup.get.called) def test_do_portgroup_update(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.portgroup = 'portgroup_uuid' args.op = 'add' args.attributes = [['arg1=val1', 'arg2=val2']] args.json = False pg_shell.do_portgroup_update(client_mock, args) patch = commonutils.args_array_to_patch(args.op, args.attributes[0]) client_mock.portgroup.update.assert_called_once_with('portgroup_uuid', patch) def _get_client_mock_args(self, address=None, marker=None, limit=None, sort_dir=None, sort_key=None, detail=False, fields=None, node=None, json=False, portgroup=None): args = mock.MagicMock(spec=True) args.address = address args.node = node args.marker = marker args.limit = limit args.sort_dir = sort_dir args.sort_key = sort_key args.detail = detail args.fields = fields args.json = json args.portgroup = portgroup return args def test_do_portgroup_list(self): client_mock = mock.MagicMock() args = self._get_client_mock_args() pg_shell.do_portgroup_list(client_mock, args) client_mock.portgroup.list.assert_called_once_with(detail=False) def test_do_portgroup_list_detail(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(detail=True) pg_shell.do_portgroup_list(client_mock, args) client_mock.portgroup.list.assert_called_once_with(detail=True) def test_do_portgroup_list_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='uuid', detail=False) pg_shell.do_portgroup_list(client_mock, args) client_mock.portgroup.list.assert_called_once_with(sort_key='uuid', detail=False) def test_do_portgroup_list_wrong_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='node_uuid', detail=False) self.assertRaises(exceptions.CommandError, pg_shell.do_portgroup_list, client_mock, args) self.assertFalse(client_mock.portgroup.list.called) def test_do_portgroup_list_detail_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='uuid', detail=True) pg_shell.do_portgroup_list(client_mock, args) client_mock.portgroup.list.assert_called_once_with(sort_key='uuid', detail=True) def test_do_portgroup_list_detail_wrong_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='node_uuid', detail=True) self.assertRaises(exceptions.CommandError, pg_shell.do_portgroup_list, client_mock, args) self.assertFalse(client_mock.portgroup.list.called) def test_do_portgroup_port_list(self): client_mock = mock.MagicMock() pg_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(portgroup=pg_mock) pg_shell.do_portgroup_port_list(client_mock, args) client_mock.portgroup.list_ports.assert_called_once_with( pg_mock, detail=False) def test_do_portgroup_port_list_detail(self): client_mock = mock.MagicMock() pg_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(portgroup=pg_mock, detail=True) pg_shell.do_portgroup_port_list(client_mock, args) client_mock.portgroup.list_ports.assert_called_once_with( pg_mock, detail=True) def test_do_portgroup_port_list_sort_key(self): client_mock = mock.MagicMock() pg_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(portgroup=pg_mock, sort_key='created_at') pg_shell.do_portgroup_port_list(client_mock, args) client_mock.portgroup.list_ports.assert_called_once_with( pg_mock, detail=False, sort_key='created_at') def test_do_portgroup_port_list_wrong_sort_key(self): client_mock = mock.MagicMock() pg_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(portgroup=pg_mock, sort_key='node_uuid') self.assertRaises(exceptions.CommandError, pg_shell.do_portgroup_port_list, client_mock, args) self.assertFalse(client_mock.portgroup.list_ports.called) def test_do_portgroup_port_list_detail_sort_key(self): client_mock = mock.MagicMock() pg_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(portgroup=pg_mock, sort_key='created_at', detail=True) pg_shell.do_portgroup_port_list(client_mock, args) client_mock.portgroup.list_ports.assert_called_once_with( pg_mock, detail=True, sort_key='created_at') def test_do_portgroup_port_list_detail_wrong_sort_key(self): client_mock = mock.MagicMock() pg_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(portgroup=pg_mock, sort_key='node_uuid', detail=True) self.assertRaises(exceptions.CommandError, pg_shell.do_portgroup_port_list, client_mock, args) self.assertFalse(client_mock.portgroup.list_ports.called) def test_do_portgroup_port_list_sort_dir(self): client_mock = mock.MagicMock() pg_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(portgroup=pg_mock, sort_dir='desc') pg_shell.do_portgroup_port_list(client_mock, args) client_mock.portgroup.list_ports.assert_called_once_with( pg_mock, detail=False, sort_dir='desc') def test_do_portgroup_port_list_wrong_sort_dir(self): client_mock = mock.MagicMock() pg_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(portgroup=pg_mock, sort_dir='abc') self.assertRaises(exceptions.CommandError, pg_shell.do_portgroup_port_list, client_mock, args) self.assertFalse(client_mock.portgroup.list_ports.called) def test_do_portgroup_port_list_fields(self): client_mock = mock.MagicMock() pg_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(portgroup=pg_mock, fields=[['uuid', 'address']]) pg_shell.do_portgroup_port_list(client_mock, args) client_mock.portgroup.list_ports.assert_called_once_with( pg_mock, detail=False, fields=['uuid', 'address']) def test_do_portgroup_port_list_wrong_fields(self): client_mock = mock.MagicMock() pg_mock = mock.MagicMock(spec_set=[]) args = self._get_client_mock_args(portgroup=pg_mock, fields=[['foo', 'bar']]) self.assertRaises(exceptions.CommandError, pg_shell.do_portgroup_port_list, client_mock, args) self.assertFalse(client_mock.portgroup.list_ports.called) def test_do_portgroup_create(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.json = False pg_shell.do_portgroup_create(client_mock, args) client_mock.portgroup.create.assert_called_once_with() def test_do_portgroup_address(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.address = 'aa:bb:cc:dd:ee:ff' args.mode = '802.3ad' args.properties = ['xmit_hash_policy=layer3+4', 'miimon=100'] args.json = False pg_shell.do_portgroup_create(client_mock, args) client_mock.portgroup.create.assert_called_once_with( address='aa:bb:cc:dd:ee:ff', mode='802.3ad', properties={'xmit_hash_policy': 'layer3+4', 'miimon': 100}) def test_do_portgroup_node_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.node_uuid = 'node-uuid' args.json = False pg_shell.do_portgroup_create(client_mock, args) client_mock.portgroup.create.assert_called_once_with( node_uuid='node-uuid') def test_do_portgroup_delete(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.portgroup = ['portgroup_uuid'] pg_shell.do_portgroup_delete(client_mock, args) client_mock.portgroup.delete.assert_called_once_with('portgroup_uuid') python-ironicclient-2.2.0/ironicclient/tests/unit/v1/test_create_resources_shell.py0000666000175100017510000000237213232474343031041 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 mock from ironicclient.tests.unit import utils from ironicclient.v1 import create_resources from ironicclient.v1 import create_resources_shell class TestCreateResourcesShell(utils.BaseTestCase): def setUp(self): super(TestCreateResourcesShell, self).setUp() self.client = mock.MagicMock(autospec=True) @mock.patch.object(create_resources, 'create_resources', autospec=True) def test_create_shell(self, mock_create_resources): args = mock.MagicMock() files = ['file1', 'file2', 'file3'] args.resource_files = files create_resources_shell.do_create(self.client, args) mock_create_resources.assert_called_once_with(self.client, files) python-ironicclient-2.2.0/ironicclient/tests/unit/v1/test_volume_connector.py0000666000175100017510000002652113232474343027700 0ustar zuulzuul00000000000000# Copyright 2015 Hitachi Data Systems # # 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 copy import testtools from ironicclient import exc from ironicclient.tests.unit import utils import ironicclient.v1.port NODE_UUID = '55555555-4444-3333-2222-111111111111' CONNECTOR1 = {'uuid': '11111111-2222-3333-4444-555555555555', 'node_uuid': NODE_UUID, 'type': 'iqn', 'connector_id': 'iqn.2010-10.org.openstack:test', 'extra': {}} CONNECTOR2 = {'uuid': '66666666-7777-8888-9999-000000000000', 'node_uuid': NODE_UUID, 'type': 'wwpn', 'connector_id': '1234567890543216', 'extra': {}} CREATE_CONNECTOR = copy.deepcopy(CONNECTOR1) del CREATE_CONNECTOR['uuid'] CREATE_CONNECTOR_WITH_UUID = copy.deepcopy(CONNECTOR1) UPDATED_CONNECTOR = copy.deepcopy(CONNECTOR1) NEW_CONNECTOR_ID = '1234567890123456' UPDATED_CONNECTOR['connector_id'] = NEW_CONNECTOR_ID fake_responses = { '/v1/volume/connectors': { 'GET': ( {}, {"connectors": [CONNECTOR1]}, ), 'POST': ( {}, CONNECTOR1, ), }, '/v1/volume/connectors/?detail=True': { 'GET': ( {}, {"connectors": [CONNECTOR1]}, ), }, '/v1/volume/connectors/?fields=uuid,connector_id': { 'GET': ( {}, {"connectors": [CONNECTOR1]}, ), }, '/v1/volume/connectors/%s' % CONNECTOR1['uuid']: { 'GET': ( {}, CONNECTOR1, ), 'DELETE': ( {}, None, ), 'PATCH': ( {}, UPDATED_CONNECTOR, ), }, '/v1/volume/connectors/%s?fields=uuid,connector_id' % CONNECTOR1['uuid']: { 'GET': ( {}, CONNECTOR1, ), }, '/v1/volume/connectors/?detail=True&node=%s' % NODE_UUID: { 'GET': ( {}, {"connectors": [CONNECTOR1]}, ), }, '/v1/volume/connectors/?node=%s' % NODE_UUID: { 'GET': ( {}, {"connectors": [CONNECTOR1]}, ), } } fake_responses_pagination = { '/v1/volume/connectors': { 'GET': ( {}, {"connectors": [CONNECTOR1], "next": "http://127.0.0.1:6385/v1/volume/connectors/?marker=%s" % CONNECTOR1['uuid']} ), }, '/v1/volume/connectors/?limit=1': { 'GET': ( {}, {"connectors": [CONNECTOR1], "next": "http://127.0.0.1:6385/v1/volume/connectors/?limit=1" "&marker=%s" % CONNECTOR1['uuid']} ), }, '/v1/volume/connectors/?limit=1&marker=%s' % CONNECTOR1['uuid']: { 'GET': ( {}, {"connectors": [CONNECTOR2]} ), }, '/v1/volume/connectors/?marker=%s' % CONNECTOR1['uuid']: { 'GET': ( {}, {"connectors": [CONNECTOR2]} ), }, } fake_responses_sorting = { '/v1/volume/connectors/?sort_key=updated_at': { 'GET': ( {}, {"connectors": [CONNECTOR2, CONNECTOR1]} ), }, '/v1/volume/connectors/?sort_dir=desc': { 'GET': ( {}, {"connectors": [CONNECTOR2, CONNECTOR1]} ), }, } class VolumeConnectorManagerTestBase(testtools.TestCase): def _validate_obj(self, expect, obj): self.assertEqual(expect['uuid'], obj.uuid) self.assertEqual(expect['type'], obj.type) self.assertEqual(expect['connector_id'], obj.connector_id) self.assertEqual(expect['node_uuid'], obj.node_uuid) def _validate_list(self, expect_request, expect_connectors, actual_connectors): self.assertEqual(expect_request, self.api.calls) self.assertEqual(len(expect_connectors), len(actual_connectors)) for expect, obj in zip(expect_connectors, actual_connectors): self._validate_obj(expect, obj) class VolumeConnectorManagerTest(VolumeConnectorManagerTestBase): def setUp(self): super(VolumeConnectorManagerTest, self).setUp() self.api = utils.FakeAPI(fake_responses) self.mgr = ironicclient.v1.volume_connector.VolumeConnectorManager( self.api) def test_volume_connectors_list(self): volume_connectors = self.mgr.list() expect = [ ('GET', '/v1/volume/connectors', {}, None), ] expect_connectors = [CONNECTOR1] self._validate_list(expect, expect_connectors, volume_connectors) def test_volume_connectors_list_by_node(self): volume_connectors = self.mgr.list(node=NODE_UUID) expect = [ ('GET', '/v1/volume/connectors/?node=%s' % NODE_UUID, {}, None), ] expect_connectors = [CONNECTOR1] self._validate_list(expect, expect_connectors, volume_connectors) def test_volume_connectors_list_by_node_detail(self): volume_connectors = self.mgr.list(node=NODE_UUID, detail=True) expect = [ ('GET', '/v1/volume/connectors/?detail=True&node=%s' % NODE_UUID, {}, None), ] expect_connectors = [CONNECTOR1] self._validate_list(expect, expect_connectors, volume_connectors) def test_volume_connectors_list_detail(self): volume_connectors = self.mgr.list(detail=True) expect = [ ('GET', '/v1/volume/connectors/?detail=True', {}, None), ] expect_connectors = [CONNECTOR1] self._validate_list(expect, expect_connectors, volume_connectors) def test_volume_connector_list_fields(self): volume_connectors = self.mgr.list(fields=['uuid', 'connector_id']) expect = [ ('GET', '/v1/volume/connectors/?fields=uuid,connector_id', {}, None), ] expect_connectors = [CONNECTOR1] self._validate_list(expect, expect_connectors, volume_connectors) def test_volume_connector_list_detail_and_fields_fail(self): self.assertRaises(exc.InvalidAttribute, self.mgr.list, detail=True, fields=['uuid', 'connector_id']) def test_volume_connectors_show(self): volume_connector = self.mgr.get(CONNECTOR1['uuid']) expect = [ ('GET', '/v1/volume/connectors/%s' % CONNECTOR1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self._validate_obj(CONNECTOR1, volume_connector) def test_volume_connector_show_fields(self): volume_connector = self.mgr.get(CONNECTOR1['uuid'], fields=['uuid', 'connector_id']) expect = [ ('GET', '/v1/volume/connectors/%s?fields=uuid,connector_id' % CONNECTOR1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(CONNECTOR1['uuid'], volume_connector.uuid) self.assertEqual(CONNECTOR1['connector_id'], volume_connector.connector_id) def test_create(self): volume_connector = self.mgr.create(**CREATE_CONNECTOR) expect = [ ('POST', '/v1/volume/connectors', {}, CREATE_CONNECTOR), ] self.assertEqual(expect, self.api.calls) self._validate_obj(CONNECTOR1, volume_connector) def test_create_with_uuid(self): volume_connector = self.mgr.create(**CREATE_CONNECTOR_WITH_UUID) expect = [ ('POST', '/v1/volume/connectors', {}, CREATE_CONNECTOR_WITH_UUID), ] self.assertEqual(expect, self.api.calls) self._validate_obj(CREATE_CONNECTOR_WITH_UUID, volume_connector) def test_delete(self): volume_connector = self.mgr.delete(CONNECTOR1['uuid']) expect = [ ('DELETE', '/v1/volume/connectors/%s' % CONNECTOR1['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(volume_connector) def test_update(self): patch = {'op': 'replace', 'connector_id': NEW_CONNECTOR_ID, 'path': '/connector_id'} volume_connector = self.mgr.update( volume_connector_id=CONNECTOR1['uuid'], patch=patch) expect = [ ('PATCH', '/v1/volume/connectors/%s' % CONNECTOR1['uuid'], {}, patch), ] self.assertEqual(expect, self.api.calls) self._validate_obj(UPDATED_CONNECTOR, volume_connector) class VolumeConnectorManagerPaginationTest(VolumeConnectorManagerTestBase): def setUp(self): super(VolumeConnectorManagerPaginationTest, self).setUp() self.api = utils.FakeAPI(fake_responses_pagination) self.mgr = ironicclient.v1.volume_connector.VolumeConnectorManager( self.api) def test_volume_connectors_list_limit(self): volume_connectors = self.mgr.list(limit=1) expect = [ ('GET', '/v1/volume/connectors/?limit=1', {}, None), ] expect_connectors = [CONNECTOR1] self._validate_list(expect, expect_connectors, volume_connectors) def test_volume_connectors_list_marker(self): volume_connectors = self.mgr.list(marker=CONNECTOR1['uuid']) expect = [ ('GET', '/v1/volume/connectors/?marker=%s' % CONNECTOR1['uuid'], {}, None), ] expect_connectors = [CONNECTOR2] self._validate_list(expect, expect_connectors, volume_connectors) def test_volume_connectors_list_pagination_no_limit(self): volume_connectors = self.mgr.list(limit=0) expect = [ ('GET', '/v1/volume/connectors', {}, None), ('GET', '/v1/volume/connectors/?marker=%s' % CONNECTOR1['uuid'], {}, None) ] expect_connectors = [CONNECTOR1, CONNECTOR2] self._validate_list(expect, expect_connectors, volume_connectors) class VolumeConnectorManagerSortingTest(VolumeConnectorManagerTestBase): def setUp(self): super(VolumeConnectorManagerSortingTest, self).setUp() self.api = utils.FakeAPI(fake_responses_sorting) self.mgr = ironicclient.v1.volume_connector.VolumeConnectorManager( self.api) def test_volume_connectors_list_sort_key(self): volume_connectors = self.mgr.list(sort_key='updated_at') expect = [ ('GET', '/v1/volume/connectors/?sort_key=updated_at', {}, None) ] expect_connectors = [CONNECTOR2, CONNECTOR1] self._validate_list(expect, expect_connectors, volume_connectors) def test_volume_connectors_list_sort_dir(self): volume_connectors = self.mgr.list(sort_dir='desc') expect = [ ('GET', '/v1/volume/connectors/?sort_dir=desc', {}, None) ] expect_connectors = [CONNECTOR2, CONNECTOR1] self._validate_list(expect, expect_connectors, volume_connectors) python-ironicclient-2.2.0/ironicclient/tests/unit/v1/__init__.py0000666000175100017510000000000013232474343024777 0ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/tests/unit/v1/test_volume_target_shell.py0000666000175100017510000002671513232474343030370 0ustar zuulzuul00000000000000# Copyright 2017 Hitachi, Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import mock from oslo_utils import uuidutils from ironicclient.common.apiclient import exceptions from ironicclient.common import cliutils from ironicclient.common import utils as commonutils from ironicclient.tests.unit import utils import ironicclient.v1.volume_target_shell as vt_shell class Volume_TargetShellTest(utils.BaseTestCase): def test_volume_target_show(self): actual = {} fake_print_dict = lambda data, *args, **kwargs: actual.update(data) with mock.patch.object(cliutils, 'print_dict', fake_print_dict): volume_target = object() vt_shell._print_volume_target_show(volume_target) exp = ['created_at', 'extra', 'node_uuid', 'volume_type', 'updated_at', 'uuid', 'properties', 'boot_index', 'volume_id'] act = actual.keys() self.assertEqual(sorted(exp), sorted(act)) def test_do_volume_target_show(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_target = 'volume_target_uuid' args.fields = None args.json = False vt_shell.do_volume_target_show(client_mock, args) client_mock.volume_target.get.assert_called_once_with( 'volume_target_uuid', fields=None) def test_do_volume_target_show_space_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_target = ' ' self.assertRaises(exceptions.CommandError, vt_shell.do_volume_target_show, client_mock, args) def test_do_volume_target_show_empty_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_target = '' self.assertRaises(exceptions.CommandError, vt_shell.do_volume_target_show, client_mock, args) def test_do_volume_target_show_fields(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_target = 'volume_target_uuid' args.fields = [['uuid', 'boot_index']] args.json = False vt_shell.do_volume_target_show(client_mock, args) client_mock.volume_target.get.assert_called_once_with( 'volume_target_uuid', fields=['uuid', 'boot_index']) def test_do_volume_target_show_invalid_fields(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_target = 'volume_target_uuid' args.fields = [['foo', 'bar']] args.json = False self.assertRaises(exceptions.CommandError, vt_shell.do_volume_target_show, client_mock, args) def test_do_volume_target_update(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_target = 'volume_target_uuid' args.op = 'add' args.attributes = [['arg1=val1', 'arg2=val2']] args.json = False vt_shell.do_volume_target_update(client_mock, args) patch = commonutils.args_array_to_patch(args.op, args.attributes[0]) client_mock.volume_target.update.assert_called_once_with( 'volume_target_uuid', patch) def test_do_volume_target_update_wrong_op(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_target = 'volume_target_uuid' args.op = 'foo' args.attributes = [['arg1=val1', 'arg2=val2']] self.assertRaises(exceptions.CommandError, vt_shell.do_volume_target_update, client_mock, args) self.assertFalse(client_mock.volume_target.update.called) def _get_client_mock_args(self, node=None, marker=None, limit=None, sort_dir=None, sort_key=None, detail=False, fields=None, json=False): args = mock.MagicMock(spec=True) args.node = node args.marker = marker args.limit = limit args.sort_dir = sort_dir args.sort_key = sort_key args.detail = detail args.fields = fields args.json = json return args def test_do_volume_target_list(self): client_mock = mock.MagicMock() args = self._get_client_mock_args() vt_shell.do_volume_target_list(client_mock, args) client_mock.volume_target.list.assert_called_once_with(detail=False) def test_do_volume_target_list_detail(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(detail=True) vt_shell.do_volume_target_list(client_mock, args) client_mock.volume_target.list.assert_called_once_with(detail=True) def test_do_volume_target_list_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='uuid', detail=False) vt_shell.do_volume_target_list(client_mock, args) client_mock.volume_target.list.assert_called_once_with( sort_key='uuid', detail=False) def test_do_volume_target_list_wrong_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='node_uuid', detail=False) self.assertRaises(exceptions.CommandError, vt_shell.do_volume_target_list, client_mock, args) self.assertFalse(client_mock.volume_target.list.called) def test_do_volume_target_list_detail_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='uuid', detail=True) vt_shell.do_volume_target_list(client_mock, args) client_mock.volume_target.list.assert_called_once_with( sort_key='uuid', detail=True) def test_do_volume_target_list_detail_wrong_sort_key(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_key='node_uuid', detail=True) self.assertRaises(exceptions.CommandError, vt_shell.do_volume_target_list, client_mock, args) self.assertFalse(client_mock.volume_target.list.called) def test_do_volume_target_list_fields(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(fields=[['uuid', 'boot_index']]) vt_shell.do_volume_target_list(client_mock, args) client_mock.volume_target.list.assert_called_once_with( fields=['uuid', 'boot_index'], detail=False) def test_do_volume_target_list_invalid_fields(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(fields=[['foo', 'bar']]) self.assertRaises(exceptions.CommandError, vt_shell.do_volume_target_list, client_mock, args) def test_do_volume_target_list_sort_dir(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_dir='desc', detail=False) vt_shell.do_volume_target_list(client_mock, args) client_mock.volume_target.list.assert_called_once_with( sort_dir='desc', detail=False) def test_do_volume_target_list_detail_sort_dir(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_dir='asc', detail=True) vt_shell.do_volume_target_list(client_mock, args) client_mock.volume_target.list.assert_called_once_with( sort_dir='asc', detail=True) def test_do_volume_target_wrong_sort_dir(self): client_mock = mock.MagicMock() args = self._get_client_mock_args(sort_dir='abc', detail=False) self.assertRaises(exceptions.CommandError, vt_shell.do_volume_target_list, client_mock, args) self.assertFalse(client_mock.volume_target.list.called) def test_do_volume_target_create(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.json = False vt_shell.do_volume_target_create(client_mock, args) client_mock.volume_target.create.assert_called_once_with() def test_do_volume_target_create_with_uuid(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.uuid = uuidutils.generate_uuid() args.json = False vt_shell.do_volume_target_create(client_mock, args) client_mock.volume_target.create.assert_called_once_with( uuid=args.uuid) def test_do_volume_target_create_valid_fields_values(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_type = 'volume_type' args.properties = ["key1=val1", "key2=val2"] args.boot_index = 100 args.node_uuid = 'uuid' args.volume_id = 'volume_id' args.extra = ["key1=val1", "key2=val2"] args.json = False vt_shell.do_volume_target_create(client_mock, args) client_mock.volume_target.create.assert_called_once_with( volume_type='volume_type', properties={'key1': 'val1', 'key2': 'val2'}, boot_index=100, node_uuid='uuid', volume_id='volume_id', extra={'key1': 'val1', 'key2': 'val2'}) def test_do_volume_target_create_invalid_extra_fields(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_type = 'volume_type' args.properties = ["key1=val1", "key2=val2"] args.boot_index = 100 args.node_uuid = 'uuid' args.volume_id = 'volume_id' args.extra = ["foo"] args.json = False self.assertRaises(exceptions.CommandError, vt_shell.do_volume_target_create, client_mock, args) def test_do_volume_target_delete(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_target = ['volume_target_uuid'] vt_shell.do_volume_target_delete(client_mock, args) client_mock.volume_target.delete.assert_called_once_with( 'volume_target_uuid') def test_do_volume_target_delete_multi(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_target = ['uuid1', 'uuid2'] vt_shell.do_volume_target_delete(client_mock, args) self.assertEqual([mock.call('uuid1'), mock.call('uuid2')], client_mock.volume_target.delete.call_args_list) def test_do_volume_target_delete_multi_error(self): client_mock = mock.MagicMock() args = mock.MagicMock() args.volume_target = ['uuid1', 'uuid2'] client_mock.volume_target.delete.side_effect = [ exceptions.ClientException('error'), None] self.assertRaises(exceptions.ClientException, vt_shell.do_volume_target_delete, client_mock, args) self.assertEqual([mock.call('uuid1'), mock.call('uuid2')], client_mock.volume_target.delete.call_args_list) python-ironicclient-2.2.0/ironicclient/tests/unit/osc/0000775000175100017510000000000013232474761023140 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/tests/unit/osc/v1/0000775000175100017510000000000013232474761023466 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/tests/unit/osc/v1/test_baremetal_driver.py0000666000175100017510000003627113232474343030415 0ustar zuulzuul00000000000000# Copyright (c) 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import copy from osc_lib.tests import utils as oscutils from ironicclient.osc.v1 import baremetal_driver from ironicclient.tests.unit.osc.v1 import fakes as baremetal_fakes class TestBaremetalDriver(baremetal_fakes.TestBaremetal): def setUp(self): super(TestBaremetalDriver, self).setUp() self.baremetal_mock = self.app.client_manager.baremetal self.baremetal_mock.reset_mock() class TestListBaremetalDriver(TestBaremetalDriver): def setUp(self): super(TestListBaremetalDriver, self).setUp() self.baremetal_mock.driver.list.return_value = [ baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL_DRIVER), loaded=True) ] self.cmd = baremetal_driver.ListBaremetalDriver(self.app, None) def test_baremetal_driver_list(self): arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) collist = ( "Supported driver(s)", "Active host(s)") self.assertEqual(collist, tuple(columns)) datalist = (( baremetal_fakes.baremetal_driver_name, ', '.join(baremetal_fakes.baremetal_driver_hosts)), ) self.assertEqual(datalist, tuple(data)) def test_baremetal_driver_list_with_type(self): arglist = ['--type', baremetal_fakes.baremetal_driver_type] verifylist = [('type', baremetal_fakes.baremetal_driver_type)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) collist = ( "Supported driver(s)", "Active host(s)") self.assertEqual(collist, tuple(columns)) datalist = (( baremetal_fakes.baremetal_driver_name, ', '.join(baremetal_fakes.baremetal_driver_hosts)),) self.assertEqual(datalist, tuple(data)) def test_baremetal_driver_list_with_detail(self): arglist = ['--long'] verifylist = [('long', True)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) collist = ( "Supported driver(s)", "Type", "Active host(s)", 'Default Boot Interface', 'Default Console Interface', 'Default Deploy Interface', 'Default Inspect Interface', 'Default Management Interface', 'Default Network Interface', 'Default Power Interface', 'Default RAID Interface', 'Default Storage Interface', 'Default Vendor Interface', 'Enabled Boot Interfaces', 'Enabled Console Interfaces', 'Enabled Deploy Interfaces', 'Enabled Inspect Interfaces', 'Enabled Management Interfaces', 'Enabled Network Interfaces', 'Enabled Power Interfaces', 'Enabled RAID Interfaces', 'Enabled Storage Interfaces', 'Enabled Vendor Interfaces' ) self.assertEqual(collist, tuple(columns)) datalist = (( baremetal_fakes.baremetal_driver_name, baremetal_fakes.baremetal_driver_type, ', '.join(baremetal_fakes.baremetal_driver_hosts), baremetal_fakes.baremetal_driver_default_boot_if, baremetal_fakes.baremetal_driver_default_console_if, baremetal_fakes.baremetal_driver_default_deploy_if, baremetal_fakes.baremetal_driver_default_inspect_if, baremetal_fakes.baremetal_driver_default_management_if, baremetal_fakes.baremetal_driver_default_network_if, baremetal_fakes.baremetal_driver_default_power_if, baremetal_fakes.baremetal_driver_default_raid_if, baremetal_fakes.baremetal_driver_default_storage_if, baremetal_fakes.baremetal_driver_default_vendor_if, ', '.join(baremetal_fakes.baremetal_driver_enabled_boot_ifs), ', '.join(baremetal_fakes.baremetal_driver_enabled_console_ifs), ', '.join(baremetal_fakes.baremetal_driver_enabled_deploy_ifs), ', '.join(baremetal_fakes.baremetal_driver_enabled_inspect_ifs), ', '.join(baremetal_fakes.baremetal_driver_enabled_management_ifs), ', '.join(baremetal_fakes.baremetal_driver_enabled_network_ifs), ', '.join(baremetal_fakes.baremetal_driver_enabled_power_ifs), ', '.join(baremetal_fakes.baremetal_driver_enabled_raid_ifs), ', '.join(baremetal_fakes.baremetal_driver_enabled_storage_ifs), ', '.join(baremetal_fakes.baremetal_driver_enabled_vendor_ifs), ),) self.assertEqual(datalist, tuple(data)) class TestListBaremetalDriverProperty(TestBaremetalDriver): def setUp(self): super(TestListBaremetalDriverProperty, self).setUp() self.baremetal_mock.driver.properties.return_value = { 'property1': 'description1', 'property2': 'description2'} self.cmd = baremetal_driver.ListBaremetalDriverProperty( self.app, None) def test_baremetal_driver_property_list(self): arglist = ['fakedrivername'] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.baremetal_mock.driver.properties.assert_called_with(*arglist) collist = ['Property', 'Description'] self.assertEqual(collist, columns) expected_data = [('property1', 'description1'), ('property2', 'description2')] self.assertEqual(expected_data, data) def test_baremetal_driver_list_no_arg(self): arglist = [] verifylist = [] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestListBaremetalDriverRaidProperty(TestBaremetalDriver): def setUp(self): super(TestListBaremetalDriverRaidProperty, self).setUp() (self.baremetal_mock.driver. raid_logical_disk_properties.return_value) = { 'RAIDProperty1': 'driver_raid_property1', 'RAIDProperty2': 'driver_raid_property2', } self.cmd = ( baremetal_driver.ListBaremetalDriverRaidProperty( self.app, None)) def test_baremetal_driver_raid_property_list(self): arglist = ['fakedrivername'] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) (self.baremetal_mock.driver. raid_logical_disk_properties.assert_called_with(*arglist)) collist = ('Property', 'Description') self.assertEqual(collist, tuple(columns)) expected_data = [('RAIDProperty1', 'driver_raid_property1'), ('RAIDProperty2', 'driver_raid_property2')] self.assertEqual(expected_data, data) def test_baremetal_driver_raid_property_list_no_arg(self): arglist = [] verifylist = [] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestPassthruCallBaremetalDriver(TestBaremetalDriver): def setUp(self): super(TestPassthruCallBaremetalDriver, self).setUp() self.baremetal_mock.driver.vendor_passthru.return_value = ( baremetal_fakes.BAREMETAL_DRIVER_PASSTHRU ) self.cmd = baremetal_driver.PassthruCallBaremetalDriver(self.app, None) def test_baremetal_driver_passthru_call_with_min_args(self): arglist = [ baremetal_fakes.baremetal_driver_name, baremetal_fakes.baremetal_driver_passthru_method, ] verifylist = [ ('driver', baremetal_fakes.baremetal_driver_name), ('method', baremetal_fakes.baremetal_driver_passthru_method), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) # Set expected values args = [ baremetal_fakes.baremetal_driver_name, baremetal_fakes.baremetal_driver_passthru_method, ] kwargs = { 'http_method': 'POST', 'args': {} } (self.baremetal_mock.driver.vendor_passthru. assert_called_once_with(*args, **kwargs)) def test_baremetal_driver_passthru_call_with_all_args(self): arglist = [ baremetal_fakes.baremetal_driver_name, baremetal_fakes.baremetal_driver_passthru_method, '--arg', 'arg1=val1', '--arg', 'arg2=val2', '--http-method', 'POST' ] verifylist = [ ('driver', baremetal_fakes.baremetal_driver_name), ('method', baremetal_fakes.baremetal_driver_passthru_method), ('arg', ['arg1=val1', 'arg2=val2']), ('http_method', 'POST') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) # Set expected values args = [ baremetal_fakes.baremetal_driver_name, baremetal_fakes.baremetal_driver_passthru_method, ] kwargs = { 'http_method': 'POST', 'args': {'arg1': 'val1', 'arg2': 'val2'} } (self.baremetal_mock.driver.vendor_passthru. assert_called_once_with(*args, **kwargs)) def test_baremetal_driver_passthru_call_no_arg(self): arglist = [] verifylist = [] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestPassthruListBaremetalDriver(TestBaremetalDriver): def setUp(self): super(TestPassthruListBaremetalDriver, self).setUp() self.baremetal_mock.driver.get_vendor_passthru_methods.return_value = ( baremetal_fakes.BAREMETAL_DRIVER_PASSTHRU ) self.cmd = baremetal_driver.PassthruListBaremetalDriver(self.app, None) def test_baremetal_driver_passthru_list(self): arglist = ['fakedrivername'] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) args = ['fakedrivername'] (self.baremetal_mock.driver.get_vendor_passthru_methods. assert_called_with(*args)) collist = ( "Name", "Supported HTTP methods", "Async", "Description", "Response is attachment", ) self.assertEqual(collist, tuple(columns)) datalist = (('lookup', 'POST', 'false', '', 'false'),) self.assertEqual(datalist, tuple(data)) def test_baremetal_driver_passthru_list_no_arg(self): arglist = [] verifylist = [] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestShowBaremetalDriver(TestBaremetalDriver): def setUp(self): super(TestShowBaremetalDriver, self).setUp() self.baremetal_mock.driver.get.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL_DRIVER), loaded=True)) self.cmd = baremetal_driver.ShowBaremetalDriver(self.app, None) def test_baremetal_driver_show(self): arglist = ['fakedrivername'] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) args = ['fakedrivername'] self.baremetal_mock.driver.get.assert_called_with(*args) self.assertFalse(self.baremetal_mock.driver.properties.called) collist = ('default_boot_interface', 'default_console_interface', 'default_deploy_interface', 'default_inspect_interface', 'default_management_interface', 'default_network_interface', 'default_power_interface', 'default_raid_interface', 'default_storage_interface', 'default_vendor_interface', 'enabled_boot_interfaces', 'enabled_console_interfaces', 'enabled_deploy_interfaces', 'enabled_inspect_interfaces', 'enabled_management_interfaces', 'enabled_network_interfaces', 'enabled_power_interfaces', 'enabled_raid_interfaces', 'enabled_storage_interfaces', 'enabled_vendor_interfaces', 'hosts', 'name', 'type') self.assertEqual(collist, columns) datalist = ( baremetal_fakes.baremetal_driver_default_boot_if, baremetal_fakes.baremetal_driver_default_console_if, baremetal_fakes.baremetal_driver_default_deploy_if, baremetal_fakes.baremetal_driver_default_inspect_if, baremetal_fakes.baremetal_driver_default_management_if, baremetal_fakes.baremetal_driver_default_network_if, baremetal_fakes.baremetal_driver_default_power_if, baremetal_fakes.baremetal_driver_default_raid_if, baremetal_fakes.baremetal_driver_default_storage_if, baremetal_fakes.baremetal_driver_default_vendor_if, ', '.join(baremetal_fakes.baremetal_driver_enabled_boot_ifs), ', '.join(baremetal_fakes.baremetal_driver_enabled_console_ifs), ', '.join(baremetal_fakes.baremetal_driver_enabled_deploy_ifs), ', '.join(baremetal_fakes.baremetal_driver_enabled_inspect_ifs), ', '.join(baremetal_fakes.baremetal_driver_enabled_management_ifs), ', '.join(baremetal_fakes.baremetal_driver_enabled_network_ifs), ', '.join(baremetal_fakes.baremetal_driver_enabled_power_ifs), ', '.join(baremetal_fakes.baremetal_driver_enabled_raid_ifs), ', '.join(baremetal_fakes.baremetal_driver_enabled_storage_ifs), ', '.join(baremetal_fakes.baremetal_driver_enabled_vendor_ifs), ', '.join(baremetal_fakes.baremetal_driver_hosts), baremetal_fakes.baremetal_driver_name, baremetal_fakes.baremetal_driver_type) self.assertEqual(datalist, tuple(data)) def test_baremetal_driver_show_no_arg(self): arglist = [] verifylist = [] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) python-ironicclient-2.2.0/ironicclient/tests/unit/osc/v1/test_baremetal_node.py0000666000175100017510000026620213232474373030051 0ustar zuulzuul00000000000000# # Copyright 2015 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import copy import mock from osc_lib.tests import utils as oscutils from ironicclient.common import utils as commonutils from ironicclient import exc from ironicclient.osc.v1 import baremetal_node from ironicclient.tests.unit.osc.v1 import fakes as baremetal_fakes from ironicclient.v1 import utils as v1_utils class TestBaremetal(baremetal_fakes.TestBaremetal): def setUp(self): super(TestBaremetal, self).setUp() # Get a shortcut to the baremetal manager mock self.baremetal_mock = self.app.client_manager.baremetal self.baremetal_mock.reset_mock() class TestAdopt(TestBaremetal): def setUp(self): super(TestAdopt, self).setUp() # Get the command object to test self.cmd = baremetal_node.AdoptBaremetalNode(self.app, None) def test_adopt(self): arglist = ['node_uuid'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'adopt'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_provision_state.assert_called_once_with( 'node_uuid', 'adopt', cleansteps=None, configdrive=None) def test_adopt_no_wait(self): arglist = ['node_uuid'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'adopt') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.wait_for_provision_state.assert_not_called() def test_adopt_baremetal_provision_state_active_and_wait(self): arglist = ['node_uuid', '--wait', '15'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'adopt'), ('wait_timeout', 15) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( 'node_uuid', expected_state='active', poll_interval=2, timeout=15) def test_adopt_baremetal_provision_state_default_wait(self): arglist = ['node_uuid', '--wait'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'adopt'), ('wait_timeout', 0) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( 'node_uuid', expected_state='active', poll_interval=2, timeout=0) class TestBootdeviceSet(TestBaremetal): def setUp(self): super(TestBootdeviceSet, self).setUp() # Get the command object to test self.cmd = baremetal_node.BootdeviceSetBaremetalNode(self.app, None) def test_bootdevice_set(self): arglist = ['node_uuid', 'bios'] verifylist = [('node', 'node_uuid'), ('device', 'bios')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_boot_device.assert_called_once_with( 'node_uuid', 'bios', False) def test_bootdevice_set_persistent(self): arglist = ['node_uuid', 'bios', '--persistent'] verifylist = [('node', 'node_uuid'), ('device', 'bios'), ('persistent', True)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_boot_device.assert_called_once_with( 'node_uuid', 'bios', True) def test_bootdevice_set_invalid_device(self): arglist = ['node_uuid', 'foo'] verifylist = [('node', 'node_uuid'), ('device', 'foo')] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_bootdevice_set_device_only(self): arglist = ['bios'] verifylist = [('device', 'bios')] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestBootdeviceShow(TestBaremetal): def setUp(self): super(TestBootdeviceShow, self).setUp() # Get the command object to test self.cmd = baremetal_node.BootdeviceShowBaremetalNode(self.app, None) self.baremetal_mock.node.get_boot_device.return_value = { "boot_device": "pxe", "persistent": False} self.baremetal_mock.node.get_supported_boot_devices.return_value = { "supported_boot_devices": v1_utils.BOOT_DEVICES} def test_bootdevice_show(self): arglist = ['node_uuid'] verifylist = [('node', 'node_uuid')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.get_boot_device.assert_called_once_with( 'node_uuid') def test_bootdevice_supported_show(self): arglist = ['node_uuid', '--supported'] verifylist = [('node', 'node_uuid'), ('supported', True)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) mock = self.baremetal_mock.node.get_supported_boot_devices mock.assert_called_once_with('node_uuid') class TestConsoleDisable(TestBaremetal): def setUp(self): super(TestConsoleDisable, self).setUp() # Get the command object to test self.cmd = baremetal_node.ConsoleDisableBaremetalNode(self.app, None) def test_console_disable(self): arglist = ['node_uuid'] verifylist = [('node', 'node_uuid')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_console_mode.assert_called_once_with( 'node_uuid', False) class TestConsoleEnable(TestBaremetal): def setUp(self): super(TestConsoleEnable, self).setUp() # Get the command object to test self.cmd = baremetal_node.ConsoleEnableBaremetalNode(self.app, None) def test_console_enable(self): arglist = ['node_uuid'] verifylist = [('node', 'node_uuid')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_console_mode.assert_called_once_with( 'node_uuid', True) class TestConsoleShow(TestBaremetal): def setUp(self): super(TestConsoleShow, self).setUp() # Get the command object to test self.cmd = baremetal_node.ConsoleShowBaremetalNode(self.app, None) self.baremetal_mock.node.get_console.return_value = { "console_enabled": False, "console_info": None} def test_console_show(self): arglist = ['node_uuid'] verifylist = [('node', 'node_uuid')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.get_console.assert_called_once_with( 'node_uuid') class TestBaremetalCreate(TestBaremetal): def setUp(self): super(TestBaremetalCreate, self).setUp() self.baremetal_mock.node.create.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL), loaded=True, )) # Get the command object to test self.cmd = baremetal_node.CreateBaremetalNode(self.app, None) self.arglist = ['--driver', 'fake_driver'] self.verifylist = [('driver', 'fake_driver')] self.collist = ('chassis_uuid', 'instance_uuid', 'maintenance', 'name', 'power_state', 'provision_state', 'uuid' ) self.datalist = ( baremetal_fakes.baremetal_chassis_uuid_empty, baremetal_fakes.baremetal_instance_uuid, baremetal_fakes.baremetal_maintenance, baremetal_fakes.baremetal_name, baremetal_fakes.baremetal_power_state, baremetal_fakes.baremetal_provision_state, baremetal_fakes.baremetal_uuid, ) self.actual_kwargs = { 'driver': 'fake_driver', } def check_with_options(self, addl_arglist, addl_verifylist, addl_kwargs): arglist = copy.copy(self.arglist) + addl_arglist verifylist = copy.copy(self.verifylist) + addl_verifylist parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) collist = copy.copy(self.collist) self.assertEqual(collist, columns) datalist = copy.copy(self.datalist) self.assertEqual(datalist, tuple(data)) kwargs = copy.copy(self.actual_kwargs) kwargs.update(addl_kwargs) self.baremetal_mock.node.create.assert_called_once_with(**kwargs) def test_baremetal_create_no_options(self): arglist = [] verifylist = [] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_create_with_driver(self): arglist = copy.copy(self.arglist) verifylist = copy.copy(self.verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) collist = copy.copy(self.collist) self.assertEqual(collist, columns) self.assertNotIn('ports', columns) self.assertNotIn('states', columns) datalist = copy.copy(self.datalist) self.assertEqual(datalist, tuple(data)) kwargs = copy.copy(self.actual_kwargs) self.baremetal_mock.node.create.assert_called_once_with(**kwargs) def test_baremetal_create_with_chassis(self): self.check_with_options(['--chassis-uuid', 'chassis_uuid'], [('chassis_uuid', 'chassis_uuid')], {'chassis_uuid': 'chassis_uuid'}) def test_baremetal_create_with_driver_info(self): self.check_with_options(['--driver-info', 'arg1=val1', '--driver-info', 'arg2=val2'], [('driver_info', ['arg1=val1', 'arg2=val2'])], {'driver_info': { 'arg1': 'val1', 'arg2': 'val2'}}) def test_baremetal_create_with_properties(self): self.check_with_options(['--property', 'arg1=val1', '--property', 'arg2=val2'], [('properties', ['arg1=val1', 'arg2=val2'])], {'properties': { 'arg1': 'val1', 'arg2': 'val2'}}) def test_baremetal_create_with_extra(self): self.check_with_options(['--extra', 'arg1=val1', '--extra', 'arg2=val2'], [('extra', ['arg1=val1', 'arg2=val2'])], {'extra': { 'arg1': 'val1', 'arg2': 'val2'}}) def test_baremetal_create_with_uuid(self): self.check_with_options(['--uuid', 'uuid'], [('uuid', 'uuid')], {'uuid': 'uuid'}) def test_baremetal_create_with_name(self): self.check_with_options(['--name', 'name'], [('name', 'name')], {'name': 'name'}) def test_baremetal_create_with_boot_interface(self): self.check_with_options(['--boot-interface', 'boot'], [('boot_interface', 'boot')], {'boot_interface': 'boot'}) def test_baremetal_create_with_console_interface(self): self.check_with_options(['--console-interface', 'console'], [('console_interface', 'console')], {'console_interface': 'console'}) def test_baremetal_create_with_deploy_interface(self): self.check_with_options(['--deploy-interface', 'deploy'], [('deploy_interface', 'deploy')], {'deploy_interface': 'deploy'}) def test_baremetal_create_with_inspect_interface(self): self.check_with_options(['--inspect-interface', 'inspect'], [('inspect_interface', 'inspect')], {'inspect_interface': 'inspect'}) def test_baremetal_create_with_management_interface(self): self.check_with_options(['--management-interface', 'management'], [('management_interface', 'management')], {'management_interface': 'management'}) def test_baremetal_create_with_network_interface(self): self.check_with_options(['--network-interface', 'neutron'], [('network_interface', 'neutron')], {'network_interface': 'neutron'}) def test_baremetal_create_with_power_interface(self): self.check_with_options(['--power-interface', 'power'], [('power_interface', 'power')], {'power_interface': 'power'}) def test_baremetal_create_with_raid_interface(self): self.check_with_options(['--raid-interface', 'raid'], [('raid_interface', 'raid')], {'raid_interface': 'raid'}) def test_baremetal_create_with_storage_interface(self): self.check_with_options(['--storage-interface', 'storage'], [('storage_interface', 'storage')], {'storage_interface': 'storage'}) def test_baremetal_create_with_vendor_interface(self): self.check_with_options(['--vendor-interface', 'vendor'], [('vendor_interface', 'vendor')], {'vendor_interface': 'vendor'}) def test_baremetal_create_with_resource_class(self): self.check_with_options(['--resource-class', 'foo'], [('resource_class', 'foo')], {'resource_class': 'foo'}) class TestBaremetalDelete(TestBaremetal): def setUp(self): super(TestBaremetalDelete, self).setUp() self.baremetal_mock.node.get.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL), loaded=True, )) # Get the command object to test self.cmd = baremetal_node.DeleteBaremetalNode(self.app, None) def test_baremetal_delete(self): arglist = ['xxx-xxxxxx-xxxx'] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) # Set expected values args = 'xxx-xxxxxx-xxxx' self.baremetal_mock.node.delete.assert_called_with( args ) def test_baremetal_delete_multiple(self): arglist = ['xxx-xxxxxx-xxxx', 'fakename'] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) # Set expected values args = ['xxx-xxxxxx-xxxx', 'fakename'] self.baremetal_mock.node.delete.has_calls( [mock.call(x) for x in args] ) self.assertEqual(2, self.baremetal_mock.node.delete.call_count) def test_baremetal_delete_multiple_with_failure(self): arglist = ['xxx-xxxxxx-xxxx', 'badname'] verifylist = [] self.baremetal_mock.node.delete.side_effect = ['', exc.ClientException] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exc.ClientException, self.cmd.take_action, parsed_args) # Set expected values args = ['xxx-xxxxxx-xxxx', 'badname'] self.baremetal_mock.node.delete.has_calls( [mock.call(x) for x in args] ) self.assertEqual(2, self.baremetal_mock.node.delete.call_count) class TestBaremetalList(TestBaremetal): def setUp(self): super(TestBaremetalList, self).setUp() self.baremetal_mock.node.list.return_value = [ baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL), loaded=True, ), ] # Get the command object to test self.cmd = baremetal_node.ListBaremetalNode(self.app, None) def test_baremetal_list_no_options(self): arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) # Set expected values kwargs = { 'marker': None, 'limit': None, } self.baremetal_mock.node.list.assert_called_with( **kwargs ) collist = ( "UUID", "Name", "Instance UUID", "Power State", "Provisioning State", "Maintenance" ) self.assertEqual(collist, columns) datalist = (( baremetal_fakes.baremetal_uuid, baremetal_fakes.baremetal_name, baremetal_fakes.baremetal_instance_uuid, baremetal_fakes.baremetal_power_state, baremetal_fakes.baremetal_provision_state, baremetal_fakes.baremetal_maintenance, ), ) self.assertEqual(datalist, tuple(data)) def test_baremetal_list_long(self): arglist = [ '--long', ] verifylist = [ ('long', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) # Set expected values kwargs = { 'detail': True, 'marker': None, 'limit': None, } self.baremetal_mock.node.list.assert_called_with( **kwargs ) collist = ('Chassis UUID', 'Created At', 'Clean Step', 'Console Enabled', 'Driver', 'Driver Info', 'Driver Internal Info', 'Extra', 'Instance Info', 'Instance UUID', 'Last Error', 'Maintenance', 'Maintenance Reason', 'Power State', 'Properties', 'Provisioning State', 'Provision Updated At', 'Current RAID configuration', 'Reservation', 'Resource Class', 'Target Power State', 'Target Provision State', 'Target RAID configuration', 'Traits', 'Updated At', 'Inspection Finished At', 'Inspection Started At', 'UUID', 'Name', 'Boot Interface', 'Console Interface', 'Deploy Interface', 'Inspect Interface', 'Management Interface', 'Network Interface', 'Power Interface', 'RAID Interface', 'Storage Interface', 'Vendor Interface') self.assertEqual(collist, columns) datalist = (( '', '', '', '', '', '', '', '', '', baremetal_fakes.baremetal_instance_uuid, '', baremetal_fakes.baremetal_maintenance, '', baremetal_fakes.baremetal_power_state, '', baremetal_fakes.baremetal_provision_state, '', '', '', '', '', '', '', '', '', '', '', baremetal_fakes.baremetal_uuid, baremetal_fakes.baremetal_name, '', '', '', '', '', '', '', '', '', '', ), ) self.assertEqual(datalist, tuple(data)) def _test_baremetal_list_maintenance(self, maint_option, maint_value): arglist = [ maint_option, ] verifylist = [ ('maintenance', maint_value), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values kwargs = { 'marker': None, 'limit': None, 'maintenance': maint_value, } self.baremetal_mock.node.list.assert_called_with( **kwargs ) def test_baremetal_list_maintenance(self): self._test_baremetal_list_maintenance('--maintenance', True) def test_baremetal_list_no_maintenance(self): self._test_baremetal_list_maintenance('--no-maintenance', False) def test_baremetal_list_none_maintenance(self): arglist = [ ] verifylist = [ ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values kwargs = { 'marker': None, 'limit': None, } self.baremetal_mock.node.list.assert_called_with( **kwargs ) def test_baremetal_list_both_maintenances(self): arglist = [ '--maintenance', '--no-maintenance', ] verifylist = [] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_list_associated(self): arglist = [ '--associated', ] verifylist = [ ('associated', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values kwargs = { 'marker': None, 'limit': None, 'associated': True, } self.baremetal_mock.node.list.assert_called_with( **kwargs ) def test_baremetal_list_unassociated(self): arglist = [ '--unassociated', ] verifylist = [ ('associated', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) # Set expected values kwargs = { 'marker': None, 'limit': None, 'associated': False, } self.baremetal_mock.node.list.assert_called_with( **kwargs ) def test_baremetal_list_both_associated_unassociated_not_allowed(self): arglist = [ '--associated', '--unassociated', ] verifylist = [] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_list_provision_state(self): arglist = [ '--provision-state', 'active', ] verifylist = [ ('provision_state', 'active'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values kwargs = { 'marker': None, 'limit': None, 'provision_state': 'active' } self.baremetal_mock.node.list.assert_called_with( **kwargs ) def test_baremetal_list_driver(self): arglist = [ '--driver', 'ipmi', ] verifylist = [ ('driver', 'ipmi'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values kwargs = { 'marker': None, 'limit': None, 'driver': 'ipmi' } self.baremetal_mock.node.list.assert_called_with( **kwargs ) def test_baremetal_list_resource_class(self): arglist = [ '--resource-class', 'foo', ] verifylist = [ ('resource_class', 'foo'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values kwargs = { 'marker': None, 'limit': None, 'resource_class': 'foo' } self.baremetal_mock.node.list.assert_called_with( **kwargs ) def test_baremetal_list_chassis(self): chassis_uuid = 'aaaaaaaa-1111-bbbb-2222-cccccccccccc' arglist = [ '--chassis', chassis_uuid, ] verifylist = [ ('chassis', chassis_uuid), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values kwargs = { 'marker': None, 'limit': None, 'chassis': chassis_uuid } self.baremetal_mock.node.list.assert_called_with( **kwargs ) def test_baremetal_list_fields(self): arglist = [ '--fields', 'uuid', 'name', ] verifylist = [ ('fields', [['uuid', 'name']]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values kwargs = { 'marker': None, 'limit': None, 'detail': False, 'fields': ('uuid', 'name'), } self.baremetal_mock.node.list.assert_called_with( **kwargs ) def test_baremetal_list_fields_multiple(self): arglist = [ '--fields', 'uuid', 'name', '--fields', 'extra', ] verifylist = [ ('fields', [['uuid', 'name'], ['extra']]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) # Set expected values kwargs = { 'marker': None, 'limit': None, 'detail': False, 'fields': ('uuid', 'name', 'extra') } self.baremetal_mock.node.list.assert_called_with( **kwargs ) def test_baremetal_list_invalid_fields(self): arglist = [ '--fields', 'uuid', 'invalid' ] verifylist = [ ('fields', [['uuid', 'invalid']]) ] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestBaremetalMaintenanceSet(TestBaremetal): def setUp(self): super(TestBaremetalMaintenanceSet, self).setUp() # Get the command object to test self.cmd = baremetal_node.MaintenanceSetBaremetalNode(self.app, None) def test_baremetal_maintenance_on(self): arglist = ['node_uuid', '--reason', 'maintenance reason'] verifylist = [ ('node', 'node_uuid'), ('reason', 'maintenance reason'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_maintenance.assert_called_once_with( 'node_uuid', True, maint_reason='maintenance reason' ) def test_baremetal_maintenance_on_no_reason(self): arglist = ['node_uuid'] verifylist = [ ('node', 'node_uuid'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_maintenance.assert_called_once_with( 'node_uuid', True, maint_reason=None ) class TestBaremetalMaintenanceUnset(TestBaremetal): def setUp(self): super(TestBaremetalMaintenanceUnset, self).setUp() # Get the command object to test self.cmd = baremetal_node.MaintenanceUnsetBaremetalNode(self.app, None) def test_baremetal_maintenance_off(self): arglist = ['node_uuid'] verifylist = [('node', 'node_uuid')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_maintenance.assert_called_once_with( 'node_uuid', False) class TestPassthruCall(TestBaremetal): def setUp(self): super(TestPassthruCall, self).setUp() # Get the command object to test self.cmd = baremetal_node.PassthruCallBaremetalNode(self.app, None) def test_passthru_call(self): arglist = ['node_uuid', 'heartbeat'] verifylist = [('node', 'node_uuid'), ('method', 'heartbeat')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.vendor_passthru.assert_called_once_with( 'node_uuid', 'heartbeat', http_method='POST', args={}) def test_passthru_call_http_method(self): arglist = ['node_uuid', 'heartbeat', '--http-method', 'PUT'] verifylist = [('node', 'node_uuid'), ('method', 'heartbeat'), ('http_method', 'PUT')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.vendor_passthru.assert_called_once_with( 'node_uuid', 'heartbeat', http_method='PUT', args={}) def test_passthru_call_args(self): arglist = ['node_uuid', 'heartbeat', '--arg', 'key1=value1', '--arg', 'key2=value2'] verifylist = [('node', 'node_uuid'), ('method', 'heartbeat'), ('arg', ['key1=value1', 'key2=value2'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) expected_dict = {'key1': 'value1', 'key2': 'value2'} self.baremetal_mock.node.vendor_passthru.assert_called_once_with( 'node_uuid', 'heartbeat', http_method='POST', args=expected_dict) class TestPassthruList(TestBaremetal): def setUp(self): super(TestPassthruList, self).setUp() # Get the command object to test self.cmd = baremetal_node.PassthruListBaremetalNode(self.app, None) self.baremetal_mock.node.get_vendor_passthru_methods.return_value = { "send_raw": {"require_exclusive_lock": True, "attach": False, "http_methods": ["POST"], "description": "", "async": True}, "bmc_reset": {"require_exclusive_lock": True, "attach": False, "http_methods": ["POST"], "description": "", "async": True}} def test_passthru_list(self): arglist = ['node_uuid'] verifylist = [('node', 'node_uuid')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) mock = self.baremetal_mock.node.get_vendor_passthru_methods mock.assert_called_once_with('node_uuid') class TestPower(TestBaremetal): def setUp(self): super(TestPower, self).setUp() # Get the command object to test self.cmd = baremetal_node.PowerBaremetalNode(self.app, None) def test_baremetal_power(self): arglist = ['node_uuid'] verifylist = [('node', 'node_uuid')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaisesRegex(AttributeError, ".*no attribute 'POWER_STATE'", self.cmd.take_action, parsed_args) class TestPowerOff(TestBaremetal): def setUp(self): super(TestPowerOff, self).setUp() # Get the command object to test self.cmd = baremetal_node.PowerOffBaremetalNode(self.app, None) def test_baremetal_power_off(self): arglist = ['node_uuid'] verifylist = [('node', 'node_uuid'), ('soft', False), ('power_timeout', None)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_power_state.assert_called_once_with( 'node_uuid', 'off', False, timeout=None) def test_baremetal_power_off_timeout(self): arglist = ['node_uuid', '--power-timeout', '2'] verifylist = [('node', 'node_uuid'), ('soft', False), ('power_timeout', 2)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_power_state.assert_called_once_with( 'node_uuid', 'off', False, timeout=2) def test_baremetal_soft_power_off(self): arglist = ['node_uuid', '--soft'] verifylist = [('node', 'node_uuid'), ('soft', True), ('power_timeout', None)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_power_state.assert_called_once_with( 'node_uuid', 'off', True, timeout=None) def test_baremetal_soft_power_off_timeout(self): arglist = ['node_uuid', '--soft', '--power-timeout', '2'] verifylist = [('node', 'node_uuid'), ('soft', True), ('power_timeout', 2)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_power_state.assert_called_once_with( 'node_uuid', 'off', True, timeout=2) def test_baremetal_power_off_no_args(self): arglist = [] verifylist = [] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestPowerOn(TestBaremetal): def setUp(self): super(TestPowerOn, self).setUp() # Get the command object to test self.cmd = baremetal_node.PowerOnBaremetalNode(self.app, None) def test_baremetal_power_on(self): arglist = ['node_uuid'] verifylist = [('node', 'node_uuid'), ('power_timeout', None)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_power_state.assert_called_once_with( 'node_uuid', 'on', False, timeout=None) def test_baremetal_power_on_timeout(self): arglist = ['node_uuid', '--power-timeout', '2'] verifylist = [('node', 'node_uuid'), ('power_timeout', 2)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_power_state.assert_called_once_with( 'node_uuid', 'on', False, timeout=2) def test_baremetal_power_on_no_args(self): arglist = [] verifylist = [] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestDeployBaremetalProvisionState(TestBaremetal): def setUp(self): super(TestDeployBaremetalProvisionState, self).setUp() # Get the command object to test self.cmd = baremetal_node.DeployBaremetalNode(self.app, None) def test_deploy_baremetal_provision_state_active_and_configdrive(self): arglist = ['node_uuid', '--config-drive', 'path/to/drive'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'active'), ('config_drive', 'path/to/drive'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_provision_state.assert_called_once_with( 'node_uuid', 'active', cleansteps=None, configdrive='path/to/drive') def test_deploy_no_wait(self): arglist = ['node_uuid'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'active') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.wait_for_provision_state.assert_not_called() def test_deploy_baremetal_provision_state_active_and_wait(self): arglist = ['node_uuid', '--wait', '15'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'active'), ('wait_timeout', 15) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( 'node_uuid', expected_state='active', poll_interval=10, timeout=15) def test_deploy_baremetal_provision_state_default_wait(self): arglist = ['node_uuid', '--wait'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'active'), ('wait_timeout', 0) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( 'node_uuid', expected_state='active', poll_interval=10, timeout=0) def test_deploy_baremetal_provision_state_mismatch(self): arglist = ['node_uuid', '--provision-state', 'abort'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'active'), ] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestManageBaremetalProvisionState(TestBaremetal): def setUp(self): super(TestManageBaremetalProvisionState, self).setUp() # Get the command object to test self.cmd = baremetal_node.ManageBaremetalNode(self.app, None) def test_manage_no_wait(self): arglist = ['node_uuid'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'manage') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.wait_for_provision_state.assert_not_called() def test_manage_baremetal_provision_state_manageable_and_wait(self): arglist = ['node_uuid', '--wait', '15'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'manage'), ('wait_timeout', 15) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( 'node_uuid', expected_state='manageable', poll_interval=2, timeout=15) def test_manage_baremetal_provision_state_default_wait(self): arglist = ['node_uuid', '--wait'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'manage'), ('wait_timeout', 0) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( 'node_uuid', expected_state='manageable', poll_interval=2, timeout=0) class TestCleanBaremetalProvisionState(TestBaremetal): def setUp(self): super(TestCleanBaremetalProvisionState, self).setUp() # Get the command object to test self.cmd = baremetal_node.CleanBaremetalNode(self.app, None) def test_clean_no_wait(self): arglist = ['node_uuid', '--clean-steps', '-'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'clean'), ('clean_steps', '-') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.wait_for_provision_state.assert_not_called() def test_clean_baremetal_provision_state_manageable_and_wait(self): arglist = ['node_uuid', '--wait', '15', '--clean-steps', '-'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'clean'), ('wait_timeout', 15), ('clean_steps', '-') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( 'node_uuid', expected_state='manageable', poll_interval=10, timeout=15) def test_clean_baremetal_provision_state_default_wait(self): arglist = ['node_uuid', '--wait', '--clean-steps', '-'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'clean'), ('wait_timeout', 0), ('clean_steps', '-') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( 'node_uuid', expected_state='manageable', poll_interval=10, timeout=0) class TestInspectBaremetalProvisionState(TestBaremetal): def setUp(self): super(TestInspectBaremetalProvisionState, self).setUp() # Get the command object to test self.cmd = baremetal_node.InspectBaremetalNode(self.app, None) def test_inspect_no_wait(self): arglist = ['node_uuid'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'inspect') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.wait_for_provision_state.assert_not_called() def test_inspect_baremetal_provision_state_managable_and_wait(self): arglist = ['node_uuid', '--wait', '15'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'inspect'), ('wait_timeout', 15) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( 'node_uuid', expected_state='manageable', poll_interval=2, timeout=15) def test_inspect_baremetal_provision_state_default_wait(self): arglist = ['node_uuid', '--wait'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'inspect'), ('wait_timeout', 0) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( 'node_uuid', expected_state='manageable', poll_interval=2, timeout=0) class TestProvideBaremetalProvisionState(TestBaremetal): def setUp(self): super(TestProvideBaremetalProvisionState, self).setUp() # Get the command object to test self.cmd = baremetal_node.ProvideBaremetalNode(self.app, None) def test_provide_no_wait(self): arglist = ['node_uuid'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'provide') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.wait_for_provision_state.assert_not_called() def test_provide_baremetal_provision_state_available_and_wait(self): arglist = ['node_uuid', '--wait', '15'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'provide'), ('wait_timeout', 15) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( 'node_uuid', expected_state='available', poll_interval=10, timeout=15) def test_provide_baremetal_provision_state_default_wait(self): arglist = ['node_uuid', '--wait'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'provide'), ('wait_timeout', 0) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( 'node_uuid', expected_state='available', poll_interval=10, timeout=0) class TestRebuildBaremetalProvisionState(TestBaremetal): def setUp(self): super(TestRebuildBaremetalProvisionState, self).setUp() # Get the command object to test self.cmd = baremetal_node.RebuildBaremetalNode(self.app, None) def test_rebuild_baremetal_provision_state_active_and_configdrive(self): arglist = ['node_uuid', '--config-drive', 'path/to/drive'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'rebuild'), ('config_drive', 'path/to/drive'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_provision_state.assert_called_once_with( 'node_uuid', 'rebuild', cleansteps=None, configdrive='path/to/drive') def test_rebuild_no_wait(self): arglist = ['node_uuid'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'rebuild') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_provision_state.assert_called_once_with( 'node_uuid', 'rebuild', cleansteps=None, configdrive=None) self.baremetal_mock.node.wait_for_provision_state.assert_not_called() def test_rebuild_baremetal_provision_state_active_and_wait(self): arglist = ['node_uuid', '--wait', '15'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'rebuild'), ('wait_timeout', 15) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( 'node_uuid', expected_state='active', poll_interval=10, timeout=15) def test_rebuild_baremetal_provision_state_default_wait(self): arglist = ['node_uuid', '--wait'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'rebuild'), ('wait_timeout', 0) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( 'node_uuid', expected_state='active', poll_interval=10, timeout=0) class TestUndeployBaremetalProvisionState(TestBaremetal): def setUp(self): super(TestUndeployBaremetalProvisionState, self).setUp() # Get the command object to test self.cmd = baremetal_node.UndeployBaremetalNode(self.app, None) def test_undeploy_no_wait(self): arglist = ['node_uuid'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'deleted') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.wait_for_provision_state.assert_not_called() def test_undeploy_baremetal_provision_state_available_and_wait(self): arglist = ['node_uuid', '--wait', '15'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'deleted'), ('wait_timeout', 15) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( 'node_uuid', expected_state='available', poll_interval=10, timeout=15) def test_undeploy_baremetal_provision_state_default_wait(self): arglist = ['node_uuid', '--wait'] verifylist = [ ('node', 'node_uuid'), ('provision_state', 'deleted'), ('wait_timeout', 0) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) test_node = self.baremetal_mock.node test_node.wait_for_provision_state.assert_called_once_with( 'node_uuid', expected_state='available', poll_interval=10, timeout=0) class TestBaremetalReboot(TestBaremetal): def setUp(self): super(TestBaremetalReboot, self).setUp() # Get the command object to test self.cmd = baremetal_node.RebootBaremetalNode(self.app, None) def test_baremetal_reboot_no_options(self): arglist = [] verifylist = [] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_reboot_uuid_only(self): arglist = ['node_uuid'] verifylist = [('node', 'node_uuid'), ('soft', False), ('power_timeout', None)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_power_state.assert_called_once_with( 'node_uuid', 'reboot', False, timeout=None) def test_baremetal_reboot_timeout(self): arglist = ['node_uuid', '--power-timeout', '2'] verifylist = [('node', 'node_uuid'), ('soft', False), ('power_timeout', 2)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_power_state.assert_called_once_with( 'node_uuid', 'reboot', False, timeout=2) def test_baremetal_soft_reboot(self): arglist = ['node_uuid', '--soft'] verifylist = [('node', 'node_uuid'), ('soft', True), ('power_timeout', None)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_power_state.assert_called_once_with( 'node_uuid', 'reboot', True, timeout=None) def test_baremetal_soft_reboot_timeout(self): arglist = ['node_uuid', '--soft', '--power-timeout', '2'] verifylist = [('node', 'node_uuid'), ('soft', True), ('power_timeout', 2)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.set_power_state.assert_called_once_with( 'node_uuid', 'reboot', True, timeout=2) class TestBaremetalSet(TestBaremetal): def setUp(self): super(TestBaremetalSet, self).setUp() self.baremetal_mock.node.update.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL), loaded=True, )) # Get the command object to test self.cmd = baremetal_node.SetBaremetalNode(self.app, None) def test_baremetal_set_no_options(self): arglist = [] verifylist = [] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_set_no_property(self): arglist = ['node_uuid'] verifylist = [ ('node', 'node_uuid'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.assertFalse(self.baremetal_mock.node.update.called) def test_baremetal_set_one_property(self): arglist = ['node_uuid', '--property', 'path/to/property=value'] verifylist = [ ('node', 'node_uuid'), ('property', ['path/to/property=value']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/properties/path/to/property', 'value': 'value', 'op': 'add'}]) def test_baremetal_set_multiple_properties(self): arglist = [ 'node_uuid', '--property', 'path/to/property=value', '--property', 'other/path=value2' ] verifylist = [ ('node', 'node_uuid'), ('property', [ 'path/to/property=value', 'other/path=value2', ]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/properties/path/to/property', 'value': 'value', 'op': 'add'}, {'path': '/properties/other/path', 'value': 'value2', 'op': 'add'}] ) def test_baremetal_set_instance_uuid(self): arglist = [ 'node_uuid', '--instance-uuid', 'xxxxx', ] verifylist = [ ('node', 'node_uuid'), ('instance_uuid', 'xxxxx') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/instance_uuid', 'value': 'xxxxx', 'op': 'add'}] ) def test_baremetal_set_name(self): arglist = [ 'node_uuid', '--name', 'xxxxx', ] verifylist = [ ('node', 'node_uuid'), ('name', 'xxxxx') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/name', 'value': 'xxxxx', 'op': 'add'}] ) def test_baremetal_set_chassis(self): chassis = '4f4135ea-7e58-4e3d-bcc4-b87ca16e980b' arglist = [ 'node_uuid', '--chassis-uuid', chassis, ] verifylist = [ ('node', 'node_uuid'), ('chassis_uuid', chassis) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/chassis_uuid', 'value': chassis, 'op': 'add'}] ) def test_baremetal_set_driver(self): arglist = [ 'node_uuid', '--driver', 'xxxxx', ] verifylist = [ ('node', 'node_uuid'), ('driver', 'xxxxx') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/driver', 'value': 'xxxxx', 'op': 'add'}] ) def _test_baremetal_set_hardware_interface(self, interface): arglist = [ 'node_uuid', '--%s-interface' % interface, 'xxxxx', ] verifylist = [ ('node', 'node_uuid'), ('%s_interface' % interface, 'xxxxx') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/%s_interface' % interface, 'value': 'xxxxx', 'op': 'add'}] ) def test_baremetal_set_boot_interface(self): self._test_baremetal_set_hardware_interface('boot') def test_baremetal_set_console_interface(self): self._test_baremetal_set_hardware_interface('console') def test_baremetal_set_deploy_interface(self): self._test_baremetal_set_hardware_interface('deploy') def test_baremetal_set_inspect_interface(self): self._test_baremetal_set_hardware_interface('inspect') def test_baremetal_set_management_interface(self): self._test_baremetal_set_hardware_interface('management') def test_baremetal_set_network_interface(self): self._test_baremetal_set_hardware_interface('network') def test_baremetal_set_power_interface(self): self._test_baremetal_set_hardware_interface('power') def test_baremetal_set_raid_interface(self): self._test_baremetal_set_hardware_interface('raid') def test_baremetal_set_storage_interface(self): self._test_baremetal_set_hardware_interface('storage') def test_baremetal_set_vendor_interface(self): self._test_baremetal_set_hardware_interface('vendor') def test_baremetal_set_resource_class(self): arglist = [ 'node_uuid', '--resource-class', 'foo', ] verifylist = [ ('node', 'node_uuid'), ('resource_class', 'foo') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/resource_class', 'value': 'foo', 'op': 'add'}] ) def test_baremetal_set_extra(self): arglist = [ 'node_uuid', '--extra', 'foo=bar', ] verifylist = [ ('node', 'node_uuid'), ('extra', ['foo=bar']) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/extra/foo', 'value': 'bar', 'op': 'add'}] ) def test_baremetal_set_driver_info(self): arglist = [ 'node_uuid', '--driver-info', 'foo=bar', ] verifylist = [ ('node', 'node_uuid'), ('driver_info', ['foo=bar']) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/driver_info/foo', 'value': 'bar', 'op': 'add'}] ) def test_baremetal_set_instance_info(self): arglist = [ 'node_uuid', '--instance-info', 'foo=bar', ] verifylist = [ ('node', 'node_uuid'), ('instance_info', ['foo=bar']) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/instance_info/foo', 'value': 'bar', 'op': 'add'}] ) @mock.patch.object(commonutils, 'get_from_stdin', autospec=True) @mock.patch.object(commonutils, 'handle_json_or_file_arg', autospec=True) def test_baremetal_set_target_raid_config(self, mock_handle, mock_stdin): self.cmd.log = mock.Mock(autospec=True) target_raid_config_string = '{"raid": "config"}' expected_target_raid_config = {'raid': 'config'} mock_handle.return_value = expected_target_raid_config.copy() arglist = ['node_uuid', '--target-raid-config', target_raid_config_string] verifylist = [('node', 'node_uuid'), ('target_raid_config', target_raid_config_string)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.cmd.log.warning.assert_not_called() self.assertFalse(mock_stdin.called) mock_handle.assert_called_once_with(target_raid_config_string) self.baremetal_mock.node.set_target_raid_config.\ assert_called_once_with('node_uuid', expected_target_raid_config) self.assertFalse(self.baremetal_mock.node.update.called) @mock.patch.object(commonutils, 'get_from_stdin', autospec=True) @mock.patch.object(commonutils, 'handle_json_or_file_arg', autospec=True) def test_baremetal_set_target_raid_config_and_name( self, mock_handle, mock_stdin): self.cmd.log = mock.Mock(autospec=True) target_raid_config_string = '{"raid": "config"}' expected_target_raid_config = {'raid': 'config'} mock_handle.return_value = expected_target_raid_config.copy() arglist = ['node_uuid', '--name', 'xxxxx', '--target-raid-config', target_raid_config_string] verifylist = [('node', 'node_uuid'), ('name', 'xxxxx'), ('target_raid_config', target_raid_config_string)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.cmd.log.warning.assert_not_called() self.assertFalse(mock_stdin.called) mock_handle.assert_called_once_with(target_raid_config_string) self.baremetal_mock.node.set_target_raid_config.\ assert_called_once_with('node_uuid', expected_target_raid_config) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/name', 'value': 'xxxxx', 'op': 'add'}]) @mock.patch.object(commonutils, 'get_from_stdin', autospec=True) @mock.patch.object(commonutils, 'handle_json_or_file_arg', autospec=True) def test_baremetal_set_target_raid_config_stdin(self, mock_handle, mock_stdin): self.cmd.log = mock.Mock(autospec=True) target_value = '-' target_raid_config_string = '{"raid": "config"}' expected_target_raid_config = {'raid': 'config'} mock_stdin.return_value = target_raid_config_string mock_handle.return_value = expected_target_raid_config.copy() arglist = ['node_uuid', '--target-raid-config', target_value] verifylist = [('node', 'node_uuid'), ('target_raid_config', target_value)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.cmd.log.warning.assert_not_called() mock_stdin.assert_called_once_with('target_raid_config') mock_handle.assert_called_once_with(target_raid_config_string) self.baremetal_mock.node.set_target_raid_config.\ assert_called_once_with('node_uuid', expected_target_raid_config) self.assertFalse(self.baremetal_mock.node.update.called) @mock.patch.object(commonutils, 'get_from_stdin', autospec=True) @mock.patch.object(commonutils, 'handle_json_or_file_arg', autospec=True) def test_baremetal_set_target_raid_config_stdin_exception( self, mock_handle, mock_stdin): self.cmd.log = mock.Mock(autospec=True) target_value = '-' mock_stdin.side_effect = exc.InvalidAttribute('bad') arglist = ['node_uuid', '--target-raid-config', target_value] verifylist = [('node', 'node_uuid'), ('target_raid_config', target_value)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exc.InvalidAttribute, self.cmd.take_action, parsed_args) self.cmd.log.warning.assert_not_called() mock_stdin.assert_called_once_with('target_raid_config') self.assertFalse(mock_handle.called) self.assertFalse( self.baremetal_mock.node.set_target_raid_config.called) self.assertFalse(self.baremetal_mock.node.update.called) class TestBaremetalShow(TestBaremetal): def setUp(self): super(TestBaremetalShow, self).setUp() self.baremetal_mock.node.get.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL), loaded=True, )) self.baremetal_mock.node.get_by_instance_uuid.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL), loaded=True, )) # Get the command object to test self.cmd = baremetal_node.ShowBaremetalNode(self.app, None) def test_baremetal_show(self): arglist = ['xxx-xxxxxx-xxxx'] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) # Set expected values args = ['xxx-xxxxxx-xxxx'] self.baremetal_mock.node.get.assert_called_with( *args, fields=None ) collist = ('chassis_uuid', 'instance_uuid', 'maintenance', 'name', 'power_state', 'provision_state', 'uuid' ) self.assertEqual(collist, columns) self.assertNotIn('ports', columns) self.assertNotIn('states', columns) datalist = ( baremetal_fakes.baremetal_chassis_uuid_empty, baremetal_fakes.baremetal_instance_uuid, baremetal_fakes.baremetal_maintenance, baremetal_fakes.baremetal_name, baremetal_fakes.baremetal_power_state, baremetal_fakes.baremetal_provision_state, baremetal_fakes.baremetal_uuid ) self.assertEqual(datalist, tuple(data)) def test_baremetal_show_no_node(self): arglist = [] verifylist = [] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_show_with_instance_uuid(self): arglist = [ 'xxx-xxxxxx-xxxx', '--instance', ] verifylist = [ ('instance_uuid', True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values args = ['xxx-xxxxxx-xxxx'] self.baremetal_mock.node.get_by_instance_uuid.assert_called_with( *args, fields=None ) def test_baremetal_show_fields(self): arglist = [ 'xxxxx', '--fields', 'uuid', 'name', ] verifylist = [ ('node', 'xxxxx'), ('fields', [['uuid', 'name']]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) self.assertNotIn('chassis_uuid', columns) # Set expected values args = ['xxxxx'] fields = ['uuid', 'name'] self.baremetal_mock.node.get.assert_called_with( *args, fields=fields ) def test_baremetal_show_fields_multiple(self): arglist = [ 'xxxxx', '--fields', 'uuid', 'name', '--fields', 'extra', ] verifylist = [ ('node', 'xxxxx'), ('fields', [['uuid', 'name'], ['extra']]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.assertNotIn('chassis_uuid', columns) # Set expected values args = ['xxxxx'] fields = ['uuid', 'name', 'extra'] self.baremetal_mock.node.get.assert_called_with( *args, fields=fields ) def test_baremetal_show_invalid_fields(self): arglist = [ 'xxxxx', '--fields', 'uuid', 'invalid' ] verifylist = [ ('node', 'xxxxx'), ('fields', [['uuid', 'invalid']]) ] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestBaremetalUnset(TestBaremetal): def setUp(self): super(TestBaremetalUnset, self).setUp() self.baremetal_mock.node.update.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL), loaded=True, )) # Get the command object to test self.cmd = baremetal_node.UnsetBaremetalNode(self.app, None) def test_baremetal_unset_no_options(self): arglist = [] verifylist = [] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_unset_no_property(self): arglist = ['node_uuid'] verifylist = [('node', 'node_uuid')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.assertFalse(self.baremetal_mock.node.update.called) def test_baremetal_unset_one_property(self): arglist = ['node_uuid', '--property', 'path/to/property'] verifylist = [('node', 'node_uuid'), ('property', ['path/to/property'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/properties/path/to/property', 'op': 'remove'}]) def test_baremetal_unset_multiple_properties(self): arglist = ['node_uuid', '--property', 'path/to/property', '--property', 'other/path'] verifylist = [('node', 'node_uuid'), ('property', ['path/to/property', 'other/path'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/properties/path/to/property', 'op': 'remove'}, {'path': '/properties/other/path', 'op': 'remove'}] ) def test_baremetal_unset_instance_uuid(self): arglist = [ 'node_uuid', '--instance-uuid', ] verifylist = [ ('node', 'node_uuid'), ('instance_uuid', True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/instance_uuid', 'op': 'remove'}] ) def test_baremetal_unset_name(self): arglist = [ 'node_uuid', '--name', ] verifylist = [ ('node', 'node_uuid'), ('name', True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/name', 'op': 'remove'}] ) def test_baremetal_unset_resource_class(self): arglist = [ 'node_uuid', '--resource-class', ] verifylist = [ ('node', 'node_uuid'), ('resource_class', True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/resource_class', 'op': 'remove'}] ) def test_baremetal_unset_extra(self): arglist = [ 'node_uuid', '--extra', 'foo', ] verifylist = [ ('node', 'node_uuid'), ('extra', ['foo']) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/extra/foo', 'op': 'remove'}] ) def test_baremetal_unset_driver_info(self): arglist = [ 'node_uuid', '--driver-info', 'foo', ] verifylist = [ ('node', 'node_uuid'), ('driver_info', ['foo']) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/driver_info/foo', 'op': 'remove'}] ) def test_baremetal_unset_instance_info(self): arglist = [ 'node_uuid', '--instance-info', 'foo', ] verifylist = [ ('node', 'node_uuid'), ('instance_info', ['foo']) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/instance_info/foo', 'op': 'remove'}] ) def test_baremetal_unset_target_raid_config(self): self.cmd.log = mock.Mock(autospec=True) arglist = [ 'node_uuid', '--target-raid-config', ] verifylist = [ ('node', 'node_uuid'), ('target_raid_config', True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.cmd.log.warning.assert_not_called() self.assertFalse(self.baremetal_mock.node.update.called) self.baremetal_mock.node.set_target_raid_config.\ assert_called_once_with('node_uuid', {}) def test_baremetal_unset_target_raid_config_and_name(self): self.cmd.log = mock.Mock(autospec=True) arglist = [ 'node_uuid', '--name', '--target-raid-config', ] verifylist = [ ('node', 'node_uuid'), ('name', True), ('target_raid_config', True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.cmd.log.warning.assert_not_called() self.baremetal_mock.node.set_target_raid_config.\ assert_called_once_with('node_uuid', {}) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/name', 'op': 'remove'}] ) def test_baremetal_unset_chassis_uuid(self): arglist = [ 'node_uuid', '--chassis-uuid', ] verifylist = [ ('node', 'node_uuid'), ('chassis_uuid', True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/chassis_uuid', 'op': 'remove'}] ) def _test_baremetal_unset_hw_interface(self, interface): arglist = [ 'node_uuid', '--%s-interface' % interface, ] verifylist = [ ('node', 'node_uuid'), ('%s_interface' % interface, True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.update.assert_called_once_with( 'node_uuid', [{'path': '/%s_interface' % interface, 'op': 'remove'}] ) def test_baremetal_unset_boot_interface(self): self._test_baremetal_unset_hw_interface('boot') def test_baremetal_unset_console_interface(self): self._test_baremetal_unset_hw_interface('console') def test_baremetal_unset_deploy_interface(self): self._test_baremetal_unset_hw_interface('deploy') def test_baremetal_unset_inspect_interface(self): self._test_baremetal_unset_hw_interface('inspect') def test_baremetal_unset_management_interface(self): self._test_baremetal_unset_hw_interface('management') def test_baremetal_unset_network_interface(self): self._test_baremetal_unset_hw_interface('network') def test_baremetal_unset_power_interface(self): self._test_baremetal_unset_hw_interface('power') def test_baremetal_unset_raid_interface(self): self._test_baremetal_unset_hw_interface('raid') def test_baremetal_unset_storage_interface(self): self._test_baremetal_unset_hw_interface('storage') def test_baremetal_unset_vendor_interface(self): self._test_baremetal_unset_hw_interface('vendor') class TestValidate(TestBaremetal): def setUp(self): super(TestValidate, self).setUp() # Get the command object to test self.cmd = baremetal_node.ValidateBaremetalNode(self.app, None) self.baremetal_mock.node.validate.return_value = ( baremetal_fakes.FakeBaremetalResource( None, {'management': {'result': True}, 'console': {'reason': "Missing 'ipmi_terminal_port'", 'result': False}, 'network': {'result': True}, 'power': {'result': True}, 'deploy': {'result': True}, 'inspect': {'reason': "not supported", 'result': None}, 'boot': {'result': True}, 'raid': {'result': True}}, loaded=True, )) def test_baremetal_validate_no_arg(self): arglist = [] verifylist = [] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_validate(self): arglist = ['node_uuid'] verifylist = [('node', 'node_uuid')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.validate.assert_called_once_with('node_uuid') class TestVifList(TestBaremetal): def setUp(self): super(TestVifList, self).setUp() self.baremetal_mock.node.vif_list.return_value = [ baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.VIFS), loaded=True, ), ] # Get the command object to test self.cmd = baremetal_node.VifListBaremetalNode(self.app, None) def test_baremetal_vif_list(self): arglist = ['node_uuid'] verifylist = [('node', 'node_uuid')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.vif_list.assert_called_once_with('node_uuid') class TestVifAttach(TestBaremetal): def setUp(self): super(TestVifAttach, self).setUp() # Get the command object to test self.cmd = baremetal_node.VifAttachBaremetalNode(self.app, None) def test_baremetal_vif_attach(self): arglist = ['node_uuid', 'aaa-aaa'] verifylist = [('node', 'node_uuid'), ('vif_id', 'aaa-aaa')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.vif_attach.assert_called_once_with( 'node_uuid', 'aaa-aaa') def test_baremetal_vif_attach_custom_fields(self): arglist = ['node_uuid', 'aaa-aaa', '--vif-info', 'foo=bar'] verifylist = [('node', 'node_uuid'), ('vif_id', 'aaa-aaa'), ('vif_info', ['foo=bar'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.vif_attach.assert_called_once_with( 'node_uuid', 'aaa-aaa', foo='bar') class TestVifDetach(TestBaremetal): def setUp(self): super(TestVifDetach, self).setUp() # Get the command object to test self.cmd = baremetal_node.VifDetachBaremetalNode(self.app, None) def test_baremetal_vif_detach(self): arglist = ['node_uuid', 'aaa-aaa'] verifylist = [('node', 'node_uuid'), ('vif_id', 'aaa-aaa')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.vif_detach.assert_called_once_with( 'node_uuid', 'aaa-aaa') class TestBaremetalInject(TestBaremetal): def setUp(self): super(TestBaremetalInject, self).setUp() # Get the command object to test self.cmd = baremetal_node.InjectNmiBaremetalNode(self.app, None) def test_baremetal_inject_no_options(self): arglist = [] verifylist = [] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_inject_nmi_uuid(self): arglist = ['node_uuid'] verifylist = [('node', 'node_uuid')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.inject_nmi.assert_called_once_with( 'node_uuid') class TestListTraits(TestBaremetal): def setUp(self): super(TestListTraits, self).setUp() self.baremetal_mock.node.get_traits.return_value = ( baremetal_fakes.TRAITS) # Get the command object to test self.cmd = baremetal_node.ListTraitsBaremetalNode(self.app, None) def test_baremetal_list_traits(self): arglist = ['node_uuid'] verifylist = [('node', 'node_uuid')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.get_traits.assert_called_once_with( 'node_uuid') class TestAddTrait(TestBaremetal): def setUp(self): super(TestAddTrait, self).setUp() # Get the command object to test self.cmd = baremetal_node.AddTraitBaremetalNode(self.app, None) def test_baremetal_add_trait(self): arglist = ['node_uuid', 'CUSTOM_FOO'] verifylist = [('node', 'node_uuid'), ('traits', ['CUSTOM_FOO'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.add_trait.assert_called_once_with( 'node_uuid', 'CUSTOM_FOO') def test_baremetal_add_traits_multiple(self): arglist = ['node_uuid', 'CUSTOM_FOO', 'CUSTOM_BAR'] verifylist = [('node', 'node_uuid'), ('traits', ['CUSTOM_FOO', 'CUSTOM_BAR'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) expected_calls = [ mock.call('node_uuid', 'CUSTOM_FOO'), mock.call('node_uuid', 'CUSTOM_BAR'), ] self.assertEqual(expected_calls, self.baremetal_mock.node.add_trait.call_args_list) def test_baremetal_add_traits_multiple_with_failure(self): arglist = ['node_uuid', 'CUSTOM_FOO', 'CUSTOM_BAR'] verifylist = [('node', 'node_uuid'), ('traits', ['CUSTOM_FOO', 'CUSTOM_BAR'])] self.baremetal_mock.node.add_trait.side_effect = [ '', exc.ClientException] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exc.ClientException, self.cmd.take_action, parsed_args) expected_calls = [ mock.call('node_uuid', 'CUSTOM_FOO'), mock.call('node_uuid', 'CUSTOM_BAR'), ] self.assertEqual(expected_calls, self.baremetal_mock.node.add_trait.call_args_list) def test_baremetal_add_traits_no_traits(self): arglist = ['node_uuid'] verifylist = [('node', 'node_uuid')] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestRemoveTrait(TestBaremetal): def setUp(self): super(TestRemoveTrait, self).setUp() # Get the command object to test self.cmd = baremetal_node.RemoveTraitBaremetalNode(self.app, None) def test_baremetal_remove_trait(self): arglist = ['node_uuid', 'CUSTOM_FOO'] verifylist = [('node', 'node_uuid'), ('traits', ['CUSTOM_FOO'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.remove_trait.assert_called_once_with( 'node_uuid', 'CUSTOM_FOO') def test_baremetal_remove_trait_multiple(self): arglist = ['node_uuid', 'CUSTOM_FOO', 'CUSTOM_BAR'] verifylist = [('node', 'node_uuid'), ('traits', ['CUSTOM_FOO', 'CUSTOM_BAR'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) expected_calls = [ mock.call('node_uuid', 'CUSTOM_FOO'), mock.call('node_uuid', 'CUSTOM_BAR'), ] self.assertEqual(expected_calls, self.baremetal_mock.node.remove_trait.call_args_list) def test_baremetal_remove_trait_multiple_with_failure(self): arglist = ['node_uuid', 'CUSTOM_FOO', 'CUSTOM_BAR'] verifylist = [('node', 'node_uuid'), ('traits', ['CUSTOM_FOO', 'CUSTOM_BAR'])] self.baremetal_mock.node.remove_trait.side_effect = [ '', exc.ClientException] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exc.ClientException, self.cmd.take_action, parsed_args) expected_calls = [ mock.call('node_uuid', 'CUSTOM_FOO'), mock.call('node_uuid', 'CUSTOM_BAR'), ] self.assertEqual(expected_calls, self.baremetal_mock.node.remove_trait.call_args_list) def test_baremetal_remove_trait_all(self): arglist = ['node_uuid', '--all'] verifylist = [('node', 'node_uuid'), ('remove_all', True)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.node.remove_all_traits.assert_called_once_with( 'node_uuid') def test_baremetal_remove_trait_traits_and_all(self): arglist = ['node_uuid', 'CUSTOM_FOO', '--all'] verifylist = [('node', 'node_uuid'), ('traits', ['CUSTOM_FOO']), ('remove_all', True)] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) self.baremetal_mock.node.remove_all_traits.assert_not_called() self.baremetal_mock.node.remove_trait.assert_not_called() def test_baremetal_remove_traits_no_traits_no_all(self): arglist = ['node_uuid'] verifylist = [('node', 'node_uuid')] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) self.baremetal_mock.node.remove_all_traits.assert_not_called() self.baremetal_mock.node.remove_trait.assert_not_called() python-ironicclient-2.2.0/ironicclient/tests/unit/osc/v1/test_baremetal_create.py0000666000175100017510000000327513232474343030363 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 mock from osc_lib.tests import utils as oscutils from ironicclient.osc.v1 import baremetal_create from ironicclient.tests.unit.osc.v1 import fakes as baremetal_fakes from ironicclient.v1 import create_resources class TestBaremetalCreate(baremetal_fakes.TestBaremetal): def setUp(self): super(TestBaremetalCreate, self).setUp() self.cmd = baremetal_create.CreateBaremetal(self.app, None) def test_baremetal_create_no_args(self): arglist = [] verifylist = [] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @mock.patch.object(create_resources, 'create_resources', autospec=True) def test_baremetal_create_resource_files(self, mock_create): arglist = ['file.yaml', 'file.json'] verifylist = [('resource_files', ['file.yaml', 'file.json'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) mock_create.assert_called_once_with(self.app.client_manager.baremetal, ['file.yaml', 'file.json']) python-ironicclient-2.2.0/ironicclient/tests/unit/osc/v1/test_baremetal_chassis.py0000666000175100017510000004304313232474343030552 0ustar zuulzuul00000000000000# # Copyright 2016 Intel Corporation # # 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 copy import mock from osc_lib.tests import utils as oscutils from ironicclient import exc from ironicclient.osc.v1 import baremetal_chassis from ironicclient.tests.unit.osc.v1 import fakes as baremetal_fakes class TestChassis(baremetal_fakes.TestBaremetal): def setUp(self): super(TestChassis, self).setUp() # Get a shortcut to the baremetal manager mock self.baremetal_mock = self.app.client_manager.baremetal self.baremetal_mock.reset_mock() class TestChassisCreate(TestChassis): def setUp(self): super(TestChassisCreate, self).setUp() self.baremetal_mock.chassis.create.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL_CHASSIS), loaded=True, )) # Get the command object to test self.cmd = baremetal_chassis.CreateBaremetalChassis(self.app, None) self.arglist = [] self.verifylist = [] self.collist = ( 'description', 'extra', 'uuid', ) self.datalist = ( baremetal_fakes.baremetal_chassis_description, baremetal_fakes.baremetal_chassis_extra, baremetal_fakes.baremetal_chassis_uuid, ) self.actual_kwargs = {} def check_with_options(self, addl_arglist, addl_verifylist, addl_kwargs): arglist = copy.copy(self.arglist) + addl_arglist verifylist = copy.copy(self.verifylist) + addl_verifylist parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) collist = copy.copy(self.collist) self.assertEqual(collist, columns) self.assertNotIn('nodes', columns) datalist = copy.copy(self.datalist) self.assertEqual(datalist, tuple(data)) kwargs = copy.copy(self.actual_kwargs) kwargs.update(addl_kwargs) self.baremetal_mock.chassis.create.assert_called_once_with(**kwargs) def test_chassis_create_no_options(self): self.check_with_options([], [], {}) def test_chassis_create_with_description(self): description = 'chassis description' self.check_with_options(['--description', description], [('description', description)], {'description': description}) def test_chassis_create_with_extra(self): extra1 = 'arg1=val1' extra2 = 'arg2=val2' self.check_with_options(['--extra', extra1, '--extra', extra2], [('extra', [extra1, extra2])], {'extra': { 'arg1': 'val1', 'arg2': 'val2'}}) def test_chassis_create_with_uuid(self): uuid = baremetal_fakes.baremetal_chassis_uuid self.check_with_options(['--uuid', uuid], [('uuid', uuid)], {'uuid': uuid}) class TestChassisDelete(TestChassis): def setUp(self): super(TestChassisDelete, self).setUp() self.baremetal_mock.chassis.get.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL_CHASSIS), loaded=True, )) # Get the command object to test self.cmd = baremetal_chassis.DeleteBaremetalChassis(self.app, None) def test_chassis_delete(self): uuid = baremetal_fakes.baremetal_chassis_uuid arglist = [uuid] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.chassis.delete.assert_called_with(uuid) def test_chassis_delete_multiple(self): uuid1 = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' uuid2 = '11111111-2222-3333-4444-555555555555' arglist = [uuid1, uuid2] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) # Set expected values args = [uuid1, uuid2] self.baremetal_mock.chassis.delete.has_calls( [mock.call(x) for x in args] ) self.assertEqual(2, self.baremetal_mock.chassis.delete.call_count) def test_chassis_delete_multiple_with_failure(self): uuid1 = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' uuid2 = '11111111-2222-3333-4444-555555555555' arglist = [uuid1, uuid2] verifylist = [] self.baremetal_mock.chassis.delete.side_effect = [ '', exc.ClientException] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exc.ClientException, self.cmd.take_action, parsed_args) # Set expected values args = [uuid1, uuid2] self.baremetal_mock.chassis.delete.has_calls( [mock.call(x) for x in args] ) self.assertEqual(2, self.baremetal_mock.chassis.delete.call_count) class TestChassisList(TestChassis): def setUp(self): super(TestChassisList, self).setUp() self.baremetal_mock.chassis.list.return_value = [ baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL_CHASSIS), loaded=True, ), ] # Get the command object to test self.cmd = baremetal_chassis.ListBaremetalChassis(self.app, None) def test_chassis_list_no_options(self): arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) # Set expected values kwargs = { 'marker': None, 'limit': None, } self.baremetal_mock.chassis.list.assert_called_with( **kwargs ) collist = ( "UUID", "Description", ) self.assertEqual(collist, columns) datalist = (( baremetal_fakes.baremetal_chassis_uuid, baremetal_fakes.baremetal_chassis_description, ), ) self.assertEqual(datalist, tuple(data)) def test_chassis_list_long(self): arglist = [ '--long', ] verifylist = [ ('long', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) # Set expected values kwargs = { 'detail': True, 'marker': None, 'limit': None, } self.baremetal_mock.chassis.list.assert_called_with( **kwargs ) collist = ('UUID', 'Description', 'Created At', 'Updated At', 'Extra') self.assertEqual(collist, columns) datalist = (( baremetal_fakes.baremetal_chassis_uuid, baremetal_fakes.baremetal_chassis_description, '', '', baremetal_fakes.baremetal_chassis_extra, ), ) self.assertEqual(datalist, tuple(data)) def test_chassis_list_fields(self): arglist = [ '--fields', 'uuid', 'extra', ] verifylist = [ ('fields', [['uuid', 'extra']]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) # Set expected values kwargs = { 'marker': None, 'limit': None, 'detail': False, 'fields': ('uuid', 'extra'), } self.baremetal_mock.chassis.list.assert_called_with( **kwargs ) def test_chassis_list_fields_multiple(self): arglist = [ '--fields', 'uuid', 'description', '--fields', 'extra', ] verifylist = [ ('fields', [['uuid', 'description'], ['extra']]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) # Set expected values kwargs = { 'marker': None, 'limit': None, 'detail': False, 'fields': ('uuid', 'description', 'extra') } self.baremetal_mock.chassis.list.assert_called_with( **kwargs ) def test_chassis_list_invalid_fields(self): arglist = [ '--fields', 'uuid', 'invalid' ] verifylist = [ ('fields', [['uuid', 'invalid']]) ] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_chassis_list_long_and_fields(self): arglist = [ '--long', '--fields', 'uuid', 'invalid' ] verifylist = [ ('long', True), ('fields', [['uuid', 'invalid']]) ] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestChassisSet(TestChassis): def setUp(self): super(TestChassisSet, self).setUp() self.baremetal_mock.chassis.update.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL_CHASSIS), loaded=True, )) # Get the command object to test self.cmd = baremetal_chassis.SetBaremetalChassis(self.app, None) def test_chassis_set_no_options(self): arglist = [] verifylist = [] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_chassis_set_no_property(self): uuid = baremetal_fakes.baremetal_chassis_uuid arglist = [uuid] verifylist = [('chassis', uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.assertFalse(self.baremetal_mock.chassis.update.called) def test_chassis_set_description(self): description = 'new description' uuid = baremetal_fakes.baremetal_chassis_uuid arglist = [ uuid, '--description', 'new description', ] verifylist = [ ('chassis', uuid), ('description', description) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.chassis.update.assert_called_once_with( uuid, [{'path': '/description', 'value': description, 'op': 'add'}] ) def test_chassis_set_extra(self): uuid = baremetal_fakes.baremetal_chassis_uuid extra = 'foo=bar' arglist = [ uuid, '--extra', extra, ] verifylist = [ ('chassis', uuid), ('extra', [extra]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.chassis.update.assert_called_once_with( uuid, [{'path': '/extra/foo', 'value': 'bar', 'op': 'add'}] ) class TestChassisShow(TestChassis): def setUp(self): super(TestChassisShow, self).setUp() self.baremetal_mock.chassis.get.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL_CHASSIS), loaded=True, )) # Get the command object to test self.cmd = baremetal_chassis.ShowBaremetalChassis(self.app, None) def test_chassis_show(self): arglist = [baremetal_fakes.baremetal_chassis_uuid] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) # Set expected values args = [baremetal_fakes.baremetal_chassis_uuid] self.baremetal_mock.chassis.get.assert_called_with( *args, fields=None ) collist = ( 'description', 'extra', 'uuid' ) self.assertEqual(collist, columns) self.assertNotIn('nodes', columns) datalist = ( baremetal_fakes.baremetal_chassis_description, baremetal_fakes.baremetal_chassis_extra, baremetal_fakes.baremetal_chassis_uuid, ) self.assertEqual(datalist, tuple(data)) def test_chassis_show_no_chassis(self): arglist = [] verifylist = [] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_chassis_show_fields(self): uuid = baremetal_fakes.baremetal_chassis_uuid arglist = [ uuid, '--fields', 'uuid', 'description', ] verifylist = [ ('chassis', uuid), ('fields', [['uuid', 'description']]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) # Set expected values args = [uuid] fields = ['uuid', 'description'] self.baremetal_mock.chassis.get.assert_called_with( *args, fields=fields ) def test_chassis_show_fields_multiple(self): uuid = baremetal_fakes.baremetal_chassis_uuid arglist = [ uuid, '--fields', 'uuid', 'description', '--fields', 'extra', ] verifylist = [ ('chassis', uuid), ('fields', [['uuid', 'description'], ['extra']]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) # Set expected values args = [uuid] fields = ['uuid', 'description', 'extra'] self.baremetal_mock.chassis.get.assert_called_with( *args, fields=fields ) def test_chassis_show_invalid_fields(self): uuid = baremetal_fakes.baremetal_chassis_uuid arglist = [ uuid, '--fields', 'uuid', 'invalid' ] verifylist = [ ('chassis', uuid), ('fields', [['uuid', 'invalid']]) ] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestChassisUnset(TestChassis): def setUp(self): super(TestChassisUnset, self).setUp() self.baremetal_mock.chassis.update.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL_CHASSIS), loaded=True, )) # Get the command object to test self.cmd = baremetal_chassis.UnsetBaremetalChassis(self.app, None) def test_chassis_unset_no_options(self): arglist = [] verifylist = [] self.assertRaises(oscutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_chassis_unset_no_property(self): uuid = baremetal_fakes.baremetal_chassis_uuid arglist = [uuid] verifylist = [('chassis', uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.assertFalse(self.baremetal_mock.chassis.update.called) def test_chassis_unset_description(self): uuid = baremetal_fakes.baremetal_chassis_uuid arglist = [ uuid, '--description', ] verifylist = [ ('chassis', uuid), ('description', True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.chassis.update.assert_called_once_with( uuid, [{'path': '/description', 'op': 'remove'}] ) def test_chassis_unset_extra(self): uuid = baremetal_fakes.baremetal_chassis_uuid arglist = [ uuid, '--extra', 'foo', ] verifylist = [ ('chassis', uuid), ('extra', ['foo']) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.chassis.update.assert_called_once_with( uuid, [{'path': '/extra/foo', 'op': 'remove'}] ) python-ironicclient-2.2.0/ironicclient/tests/unit/osc/v1/test_baremetal_volume_connector.py0000666000175100017510000007631413232474343032505 0ustar zuulzuul00000000000000# Copyright 2017 FUJITSU LIMITED # # 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 copy import mock from osc_lib.tests import utils as osctestutils from ironicclient import exc from ironicclient.osc.v1 import baremetal_volume_connector as bm_vol_connector from ironicclient.tests.unit.osc.v1 import fakes as baremetal_fakes class TestBaremetalVolumeConnector(baremetal_fakes.TestBaremetal): def setUp(self): super(TestBaremetalVolumeConnector, self).setUp() self.baremetal_mock = self.app.client_manager.baremetal self.baremetal_mock.reset_mock() class TestCreateBaremetalVolumeConnector(TestBaremetalVolumeConnector): def setUp(self): super(TestCreateBaremetalVolumeConnector, self).setUp() self.baremetal_mock.volume_connector.create.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.VOLUME_CONNECTOR), loaded=True, )) # Get the command object to test self.cmd = ( bm_vol_connector.CreateBaremetalVolumeConnector(self.app, None)) def test_baremetal_volume_connector_create(self): arglist = [ '--node', baremetal_fakes.baremetal_uuid, '--type', baremetal_fakes.baremetal_volume_connector_type, '--connector-id', baremetal_fakes.baremetal_volume_connector_connector_id, '--uuid', baremetal_fakes.baremetal_volume_connector_uuid, ] verifylist = [ ('node_uuid', baremetal_fakes.baremetal_uuid), ('type', baremetal_fakes.baremetal_volume_connector_type), ('connector_id', baremetal_fakes.baremetal_volume_connector_connector_id), ('uuid', baremetal_fakes.baremetal_volume_connector_uuid), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) args = { 'node_uuid': baremetal_fakes.baremetal_uuid, 'type': baremetal_fakes.baremetal_volume_connector_type, 'connector_id': baremetal_fakes.baremetal_volume_connector_connector_id, 'uuid': baremetal_fakes.baremetal_volume_connector_uuid, } self.baremetal_mock.volume_connector.create.assert_called_once_with( **args) def test_baremetal_volume_connector_create_without_uuid(self): arglist = [ '--node', baremetal_fakes.baremetal_uuid, '--type', baremetal_fakes.baremetal_volume_connector_type, '--connector-id', baremetal_fakes.baremetal_volume_connector_connector_id, ] verifylist = [ ('node_uuid', baremetal_fakes.baremetal_uuid), ('type', baremetal_fakes.baremetal_volume_connector_type), ('connector_id', baremetal_fakes.baremetal_volume_connector_connector_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) args = { 'node_uuid': baremetal_fakes.baremetal_uuid, 'type': baremetal_fakes.baremetal_volume_connector_type, 'connector_id': baremetal_fakes.baremetal_volume_connector_connector_id, } self.baremetal_mock.volume_connector.create.assert_called_once_with( **args) def test_baremetal_volume_connector_create_extras(self): arglist = [ '--node', baremetal_fakes.baremetal_uuid, '--type', baremetal_fakes.baremetal_volume_connector_type, '--connector-id', baremetal_fakes.baremetal_volume_connector_connector_id, '--extra', 'key1=value1', '--extra', 'key2=value2', ] verifylist = [ ('node_uuid', baremetal_fakes.baremetal_uuid), ('type', baremetal_fakes.baremetal_volume_connector_type), ('connector_id', baremetal_fakes.baremetal_volume_connector_connector_id), ('extra', ['key1=value1', 'key2=value2']) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) args = { 'node_uuid': baremetal_fakes.baremetal_uuid, 'type': baremetal_fakes.baremetal_volume_connector_type, 'connector_id': baremetal_fakes.baremetal_volume_connector_connector_id, 'extra': baremetal_fakes.baremetal_volume_connector_extra, } self.baremetal_mock.volume_connector.create.assert_called_once_with( **args) def test_baremetal_volume_connector_create_invalid_type(self): arglist = [ '--node', baremetal_fakes.baremetal_uuid, '--type', 'invalid', '--connector-id', baremetal_fakes.baremetal_volume_connector_connector_id, '--uuid', baremetal_fakes.baremetal_volume_connector_uuid, ] verifylist = None self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_volume_connector_create_missing_node(self): arglist = [ '--type', baremetal_fakes.baremetal_volume_connector_type, '--connector-id', baremetal_fakes.baremetal_volume_connector_connector_id, '--uuid', baremetal_fakes.baremetal_volume_connector_uuid, ] verifylist = None self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_volume_connector_create_missing_type(self): arglist = [ '--node', baremetal_fakes.baremetal_uuid, '--connector-id', baremetal_fakes.baremetal_volume_connector_connector_id, '--uuid', baremetal_fakes.baremetal_volume_connector_uuid, ] verifylist = None self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_volume_connector_create_missing_connector_id(self): arglist = [ '--node', baremetal_fakes.baremetal_uuid, '--type', baremetal_fakes.baremetal_volume_connector_type, '--uuid', baremetal_fakes.baremetal_volume_connector_uuid, ] verifylist = None self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestShowBaremetalVolumeConnector(TestBaremetalVolumeConnector): def setUp(self): super(TestShowBaremetalVolumeConnector, self).setUp() self.baremetal_mock.volume_connector.get.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.VOLUME_CONNECTOR), loaded=True)) self.cmd = ( bm_vol_connector.ShowBaremetalVolumeConnector(self.app, None)) def test_baremetal_volume_connector_show(self): arglist = ['vvv-cccccc-vvvv'] verifylist = [('volume_connector', baremetal_fakes.baremetal_volume_connector_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) args = ['vvv-cccccc-vvvv'] self.baremetal_mock.volume_connector.get.assert_called_once_with( *args, fields=None) collist = ('connector_id', 'extra', 'node_uuid', 'type', 'uuid') self.assertEqual(collist, columns) datalist = ( baremetal_fakes.baremetal_volume_connector_connector_id, baremetal_fakes.baremetal_volume_connector_extra, baremetal_fakes.baremetal_uuid, baremetal_fakes.baremetal_volume_connector_type, baremetal_fakes.baremetal_volume_connector_uuid, ) self.assertEqual(datalist, tuple(data)) def test_baremetal_volume_connector_show_no_options(self): arglist = [] verifylist = [] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_volume_connector_show_fields(self): arglist = ['vvv-cccccc-vvvv', '--fields', 'uuid', 'connector_id'] verifylist = [('fields', [['uuid', 'connector_id']]), ('volume_connector', baremetal_fakes.baremetal_volume_connector_uuid)] fake_vc = copy.deepcopy(baremetal_fakes.VOLUME_CONNECTOR) fake_vc.pop('type') fake_vc.pop('extra') fake_vc.pop('node_uuid') self.baremetal_mock.volume_connector.get.return_value = ( baremetal_fakes.FakeBaremetalResource( None, fake_vc, loaded=True)) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) args = ['vvv-cccccc-vvvv'] fields = ['uuid', 'connector_id'] self.baremetal_mock.volume_connector.get.assert_called_once_with( *args, fields=fields) collist = ('connector_id', 'uuid') self.assertEqual(collist, columns) datalist = ( baremetal_fakes.baremetal_volume_connector_connector_id, baremetal_fakes.baremetal_volume_connector_uuid, ) self.assertEqual(datalist, tuple(data)) def test_baremetal_volume_connector_show_fields_multiple(self): arglist = ['vvv-cccccc-vvvv', '--fields', 'uuid', 'connector_id', '--fields', 'type'] verifylist = [('fields', [['uuid', 'connector_id'], ['type']]), ('volume_connector', baremetal_fakes.baremetal_volume_connector_uuid)] fake_vc = copy.deepcopy(baremetal_fakes.VOLUME_CONNECTOR) fake_vc.pop('extra') fake_vc.pop('node_uuid') self.baremetal_mock.volume_connector.get.return_value = ( baremetal_fakes.FakeBaremetalResource( None, fake_vc, loaded=True)) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) args = ['vvv-cccccc-vvvv'] fields = ['uuid', 'connector_id', 'type'] self.baremetal_mock.volume_connector.get.assert_called_once_with( *args, fields=fields) collist = ('connector_id', 'type', 'uuid') self.assertEqual(collist, columns) datalist = ( baremetal_fakes.baremetal_volume_connector_connector_id, baremetal_fakes.baremetal_volume_connector_type, baremetal_fakes.baremetal_volume_connector_uuid, ) self.assertEqual(datalist, tuple(data)) class TestListBaremetalVolumeConnector(TestBaremetalVolumeConnector): def setUp(self): super(TestListBaremetalVolumeConnector, self).setUp() self.baremetal_mock.volume_connector.list.return_value = [ baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.VOLUME_CONNECTOR), loaded=True) ] self.cmd = ( bm_vol_connector.ListBaremetalVolumeConnector(self.app, None)) def test_baremetal_volume_connector_list(self): arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) kwargs = { 'marker': None, 'limit': None} self.baremetal_mock.volume_connector.list.assert_called_once_with( **kwargs) collist = ( "UUID", "Node UUID", "Type", "Connector ID") self.assertEqual(collist, columns) datalist = ((baremetal_fakes.baremetal_volume_connector_uuid, baremetal_fakes.baremetal_uuid, baremetal_fakes.baremetal_volume_connector_type, baremetal_fakes.baremetal_volume_connector_connector_id),) self.assertEqual(datalist, tuple(data)) def test_baremetal_volume_connector_list_node(self): arglist = ['--node', baremetal_fakes.baremetal_uuid] verifylist = [('node', baremetal_fakes.baremetal_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) kwargs = { 'node': baremetal_fakes.baremetal_uuid, 'marker': None, 'limit': None} self.baremetal_mock.volume_connector.list.assert_called_once_with( **kwargs) collist = ( "UUID", "Node UUID", "Type", "Connector ID") self.assertEqual(collist, columns) datalist = ((baremetal_fakes.baremetal_volume_connector_uuid, baremetal_fakes.baremetal_uuid, baremetal_fakes.baremetal_volume_connector_type, baremetal_fakes.baremetal_volume_connector_connector_id),) self.assertEqual(datalist, tuple(data)) def test_baremetal_volume_connector_list_long(self): arglist = ['--long'] verifylist = [('detail', True)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) kwargs = { 'detail': True, 'marker': None, 'limit': None, } self.baremetal_mock.volume_connector.list.assert_called_with(**kwargs) collist = ('UUID', 'Node UUID', 'Type', 'Connector ID', 'Extra', 'Created At', 'Updated At') self.assertEqual(collist, columns) datalist = ((baremetal_fakes.baremetal_volume_connector_uuid, baremetal_fakes.baremetal_uuid, baremetal_fakes.baremetal_volume_connector_type, baremetal_fakes.baremetal_volume_connector_connector_id, baremetal_fakes.baremetal_volume_connector_extra, '', ''),) self.assertEqual(datalist, tuple(data)) def test_baremetal_volume_connector_list_fields(self): arglist = ['--fields', 'uuid', 'connector_id'] verifylist = [('fields', [['uuid', 'connector_id']])] fake_vc = copy.deepcopy(baremetal_fakes.VOLUME_CONNECTOR) fake_vc.pop('type') fake_vc.pop('extra') fake_vc.pop('node_uuid') self.baremetal_mock.volume_connector.list.return_value = [ baremetal_fakes.FakeBaremetalResource( None, fake_vc, loaded=True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) kwargs = { 'detail': False, 'marker': None, 'limit': None, 'fields': ('uuid', 'connector_id') } self.baremetal_mock.volume_connector.list.assert_called_with(**kwargs) collist = ('UUID', 'Connector ID') self.assertEqual(collist, columns) datalist = ((baremetal_fakes.baremetal_volume_connector_uuid, baremetal_fakes.baremetal_volume_connector_connector_id),) self.assertEqual(datalist, tuple(data)) def test_baremetal_volume_connector_list_fields_multiple(self): arglist = ['--fields', 'uuid', 'connector_id', '--fields', 'extra'] verifylist = [('fields', [['uuid', 'connector_id'], ['extra']])] fake_vc = copy.deepcopy(baremetal_fakes.VOLUME_CONNECTOR) fake_vc.pop('type') fake_vc.pop('node_uuid') self.baremetal_mock.volume_connector.list.return_value = [ baremetal_fakes.FakeBaremetalResource( None, fake_vc, loaded=True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) kwargs = { 'detail': False, 'marker': None, 'limit': None, 'fields': ('uuid', 'connector_id', 'extra') } self.baremetal_mock.volume_connector.list.assert_called_with(**kwargs) collist = ('UUID', 'Connector ID', 'Extra') self.assertEqual(collist, columns) datalist = ((baremetal_fakes.baremetal_volume_connector_uuid, baremetal_fakes.baremetal_volume_connector_connector_id, baremetal_fakes.baremetal_volume_connector_extra),) self.assertEqual(datalist, tuple(data)) def test_baremetal_volume_connector_list_invalid_fields(self): arglist = ['--fields', 'uuid', 'invalid'] verifylist = [('fields', [['uuid', 'invalid']])] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_volume_connector_list_marker(self): arglist = ['--marker', baremetal_fakes.baremetal_volume_connector_uuid] verifylist = [ ('marker', baremetal_fakes.baremetal_volume_connector_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) kwargs = { 'marker': baremetal_fakes.baremetal_volume_connector_uuid, 'limit': None} self.baremetal_mock.volume_connector.list.assert_called_once_with( **kwargs) def test_baremetal_volume_connector_list_limit(self): arglist = ['--limit', '10'] verifylist = [('limit', 10)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) kwargs = { 'marker': None, 'limit': 10} self.baremetal_mock.volume_connector.list.assert_called_once_with( **kwargs) def test_baremetal_volume_connector_list_sort(self): arglist = ['--sort', 'type'] verifylist = [('sort', 'type')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) kwargs = { 'marker': None, 'limit': None} self.baremetal_mock.volume_connector.list.assert_called_once_with( **kwargs) def test_baremetal_volume_connector_list_sort_desc(self): arglist = ['--sort', 'type:desc'] verifylist = [('sort', 'type:desc')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) kwargs = { 'marker': None, 'limit': None} self.baremetal_mock.volume_connector.list.assert_called_once_with( **kwargs) def test_baremetal_volume_connector_list_exclusive_options(self): arglist = ['--fields', 'uuid', '--long'] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, []) def test_baremetal_volume_connector_list_negative_limit(self): arglist = ['--limit', '-1'] verifylist = [('limit', -1)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) class TestDeleteBaremetalVolumeConnector(TestBaremetalVolumeConnector): def setUp(self): super(TestDeleteBaremetalVolumeConnector, self).setUp() self.cmd = ( bm_vol_connector.DeleteBaremetalVolumeConnector(self.app, None)) def test_baremetal_volume_connector_delete(self): arglist = [baremetal_fakes.baremetal_volume_connector_uuid] verifylist = [('volume_connectors', [baremetal_fakes.baremetal_volume_connector_uuid])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_connector.delete.assert_called_with( baremetal_fakes.baremetal_volume_connector_uuid) def test_baremetal_volume_connector_delete_multiple(self): fake_volume_connector_uuid2 = 'vvv-cccccc-cccc' arglist = [baremetal_fakes.baremetal_volume_connector_uuid, fake_volume_connector_uuid2] verifylist = [('volume_connectors', [baremetal_fakes.baremetal_volume_connector_uuid, fake_volume_connector_uuid2])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_connector.delete.has_calls( [mock.call(baremetal_fakes.baremetal_volume_connector_uuid), mock.call(fake_volume_connector_uuid2)]) self.assertEqual( 2, self.baremetal_mock.volume_connector.delete.call_count) def test_baremetal_volume_connector_delete_no_options(self): arglist = [] verifylist = [] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_volume_connector_delete_error(self): arglist = [baremetal_fakes.baremetal_volume_connector_uuid] verifylist = [('volume_connectors', [baremetal_fakes.baremetal_volume_connector_uuid])] self.baremetal_mock.volume_connector.delete.side_effect = ( exc.NotFound()) parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exc.ClientException, self.cmd.take_action, parsed_args) self.baremetal_mock.volume_connector.delete.assert_called_with( baremetal_fakes.baremetal_volume_connector_uuid) def test_baremetal_volume_connector_delete_multiple_error(self): fake_volume_connector_uuid2 = 'vvv-cccccc-cccc' arglist = [baremetal_fakes.baremetal_volume_connector_uuid, fake_volume_connector_uuid2] verifylist = [('volume_connectors', [baremetal_fakes.baremetal_volume_connector_uuid, fake_volume_connector_uuid2])] self.baremetal_mock.volume_connector.delete.side_effect = [ None, exc.NotFound()] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exc.ClientException, self.cmd.take_action, parsed_args) self.baremetal_mock.volume_connector.delete.has_calls( [mock.call(baremetal_fakes.baremetal_volume_connector_uuid), mock.call(fake_volume_connector_uuid2)]) self.assertEqual( 2, self.baremetal_mock.volume_connector.delete.call_count) class TestSetBaremetalVolumeConnector(TestBaremetalVolumeConnector): def setUp(self): super(TestSetBaremetalVolumeConnector, self).setUp() self.cmd = ( bm_vol_connector.SetBaremetalVolumeConnector(self.app, None)) def test_baremetal_volume_connector_set_node_uuid(self): new_node_uuid = 'xxx-xxxxxx-zzzz' arglist = [ baremetal_fakes.baremetal_volume_connector_uuid, '--node', new_node_uuid] verifylist = [ ('volume_connector', baremetal_fakes.baremetal_volume_connector_uuid), ('node_uuid', new_node_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_connector.update.assert_called_once_with( baremetal_fakes.baremetal_volume_connector_uuid, [{'path': '/node_uuid', 'value': new_node_uuid, 'op': 'add'}]) def test_baremetal_volume_connector_set_type(self): new_type = 'wwnn' arglist = [ baremetal_fakes.baremetal_volume_connector_uuid, '--type', new_type] verifylist = [ ('volume_connector', baremetal_fakes.baremetal_volume_connector_uuid), ('type', new_type)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_connector.update.assert_called_once_with( baremetal_fakes.baremetal_volume_connector_uuid, [{'path': '/type', 'value': new_type, 'op': 'add'}]) def test_baremetal_volume_connector_set_invalid_type(self): new_type = 'invalid' arglist = [ baremetal_fakes.baremetal_volume_connector_uuid, '--type', new_type] verifylist = None self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_volume_connector_set_connector_id(self): new_conn_id = '11:22:33:44:55:66:77:88' arglist = [ baremetal_fakes.baremetal_volume_connector_uuid, '--connector-id', new_conn_id] verifylist = [ ('volume_connector', baremetal_fakes.baremetal_volume_connector_uuid), ('connector_id', new_conn_id)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_connector.update.assert_called_once_with( baremetal_fakes.baremetal_volume_connector_uuid, [{'path': '/connector_id', 'value': new_conn_id, 'op': 'add'}]) def test_baremetal_volume_connector_set_type_and_connector_id(self): new_type = 'wwnn' new_conn_id = '11:22:33:44:55:66:77:88' arglist = [ baremetal_fakes.baremetal_volume_connector_uuid, '--type', new_type, '--connector-id', new_conn_id] verifylist = [ ('volume_connector', baremetal_fakes.baremetal_volume_connector_uuid), ('type', new_type), ('connector_id', new_conn_id)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_connector.update.assert_called_once_with( baremetal_fakes.baremetal_volume_connector_uuid, [{'path': '/type', 'value': new_type, 'op': 'add'}, {'path': '/connector_id', 'value': new_conn_id, 'op': 'add'}]) def test_baremetal_volume_connector_set_extra(self): arglist = [ baremetal_fakes.baremetal_volume_connector_uuid, '--extra', 'foo=bar'] verifylist = [ ('volume_connector', baremetal_fakes.baremetal_volume_connector_uuid), ('extra', ['foo=bar'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_connector.update.assert_called_once_with( baremetal_fakes.baremetal_volume_connector_uuid, [{'path': '/extra/foo', 'value': 'bar', 'op': 'add'}]) def test_baremetal_volume_connector_set_multiple_extras(self): arglist = [ baremetal_fakes.baremetal_volume_connector_uuid, '--extra', 'key1=val1', '--extra', 'key2=val2'] verifylist = [ ('volume_connector', baremetal_fakes.baremetal_volume_connector_uuid), ('extra', ['key1=val1', 'key2=val2'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_connector.update.assert_called_once_with( baremetal_fakes.baremetal_volume_connector_uuid, [{'path': '/extra/key1', 'value': 'val1', 'op': 'add'}, {'path': '/extra/key2', 'value': 'val2', 'op': 'add'}]) def test_baremetal_volume_connector_set_no_options(self): arglist = [] verifylist = [] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_volume_connector_set_no_property(self): arglist = [baremetal_fakes.baremetal_volume_connector_uuid] verifylist = [('volume_connector', baremetal_fakes.baremetal_volume_connector_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_connector.update.assert_not_called() class TestUnsetBaremetalVolumeConnector(TestBaremetalVolumeConnector): def setUp(self): super(TestUnsetBaremetalVolumeConnector, self).setUp() self.cmd = ( bm_vol_connector.UnsetBaremetalVolumeConnector(self.app, None)) def test_baremetal_volume_connector_unset_extra(self): arglist = [baremetal_fakes.baremetal_volume_connector_uuid, '--extra', 'key1'] verifylist = [('volume_connector', baremetal_fakes.baremetal_volume_connector_uuid), ('extra', ['key1'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_connector.update.assert_called_once_with( baremetal_fakes.baremetal_volume_connector_uuid, [{'path': '/extra/key1', 'op': 'remove'}]) def test_baremetal_volume_connector_unset_multiple_extras(self): arglist = [baremetal_fakes.baremetal_volume_connector_uuid, '--extra', 'key1', '--extra', 'key2'] verifylist = [('volume_connector', baremetal_fakes.baremetal_volume_connector_uuid), ('extra', ['key1', 'key2'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_connector.update.assert_called_once_with( baremetal_fakes.baremetal_volume_connector_uuid, [{'path': '/extra/key1', 'op': 'remove'}, {'path': '/extra/key2', 'op': 'remove'}]) def test_baremetal_volume_connector_unset_no_options(self): arglist = [] verifylist = [] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_volume_connector_unset_no_property(self): arglist = [baremetal_fakes.baremetal_volume_connector_uuid] verifylist = [('volume_connector', baremetal_fakes.baremetal_volume_connector_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_connector.update.assert_not_called() python-ironicclient-2.2.0/ironicclient/tests/unit/osc/v1/fakes.py0000666000175100017510000001633513232474373025142 0ustar zuulzuul00000000000000# # Copyright 2015 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import mock from osc_lib.tests import utils from ironicclient.tests.unit.osc import fakes baremetal_chassis_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' baremetal_chassis_uuid_empty = '' baremetal_properties_empty = '' baremetal_chassis_description = 'chassis description' baremetal_chassis_extra = {} BAREMETAL_CHASSIS = { 'uuid': baremetal_chassis_uuid, 'description': baremetal_chassis_description, 'extra': baremetal_chassis_extra, } baremetal_uuid = 'xxx-xxxxxx-xxxx' baremetal_name = 'fake name' baremetal_instance_uuid = 'yyy-yyyyyy-yyyy' baremetal_power_state = None baremetal_provision_state = None baremetal_maintenance = False BAREMETAL = { 'uuid': baremetal_uuid, 'name': baremetal_name, 'instance_uuid': baremetal_instance_uuid, 'power_state': baremetal_power_state, 'provision_state': baremetal_provision_state, 'maintenance': baremetal_maintenance, 'links': [], 'volume': [], } baremetal_port_uuid = 'zzz-zzzzzz-zzzz' baremetal_port_address = 'AA:BB:CC:DD:EE:FF' baremetal_port_extra = {'key1': 'value1', 'key2': 'value2'} baremetal_port_physical_network = 'physnet1' BAREMETAL_PORT = { 'uuid': baremetal_port_uuid, 'address': baremetal_port_address, 'extra': baremetal_port_extra, 'node_uuid': baremetal_uuid, } baremetal_driver_hosts = ['fake-host1', 'fake-host2'] baremetal_driver_name = 'fakedrivername' baremetal_driver_type = 'classic' baremetal_driver_default_boot_if = 'boot' baremetal_driver_default_console_if = 'console' baremetal_driver_default_deploy_if = 'deploy' baremetal_driver_default_inspect_if = 'inspect' baremetal_driver_default_management_if = 'management' baremetal_driver_default_network_if = 'network' baremetal_driver_default_power_if = 'power' baremetal_driver_default_raid_if = 'raid' baremetal_driver_default_storage_if = 'storage' baremetal_driver_default_vendor_if = 'vendor' baremetal_driver_enabled_boot_ifs = ['boot', 'boot2'] baremetal_driver_enabled_console_ifs = ['console', 'console2'] baremetal_driver_enabled_deploy_ifs = ['deploy', 'deploy2'] baremetal_driver_enabled_inspect_ifs = ['inspect', 'inspect2'] baremetal_driver_enabled_management_ifs = ['management', 'management2'] baremetal_driver_enabled_network_ifs = ['network', 'network2'] baremetal_driver_enabled_power_ifs = ['power', 'power2'] baremetal_driver_enabled_raid_ifs = ['raid', 'raid2'] baremetal_driver_enabled_storage_ifs = ['storage', 'storage2'] baremetal_driver_enabled_vendor_ifs = ['vendor', 'vendor2'] BAREMETAL_DRIVER = { 'hosts': baremetal_driver_hosts, 'name': baremetal_driver_name, 'type': baremetal_driver_type, 'default_boot_interface': baremetal_driver_default_boot_if, 'default_console_interface': baremetal_driver_default_console_if, 'default_deploy_interface': baremetal_driver_default_deploy_if, 'default_inspect_interface': baremetal_driver_default_inspect_if, 'default_management_interface': baremetal_driver_default_management_if, 'default_network_interface': baremetal_driver_default_network_if, 'default_power_interface': baremetal_driver_default_power_if, 'default_raid_interface': baremetal_driver_default_raid_if, 'default_storage_interface': baremetal_driver_default_storage_if, 'default_vendor_interface': baremetal_driver_default_vendor_if, 'enabled_boot_interfaces': baremetal_driver_enabled_boot_ifs, 'enabled_console_interfaces': baremetal_driver_enabled_console_ifs, 'enabled_deploy_interfaces': baremetal_driver_enabled_deploy_ifs, 'enabled_inspect_interfaces': baremetal_driver_enabled_inspect_ifs, 'enabled_management_interfaces': baremetal_driver_enabled_management_ifs, 'enabled_network_interfaces': baremetal_driver_enabled_network_ifs, 'enabled_power_interfaces': baremetal_driver_enabled_power_ifs, 'enabled_raid_interfaces': baremetal_driver_enabled_raid_ifs, 'enabled_storage_interfaces': baremetal_driver_enabled_storage_ifs, 'enabled_vendor_interfaces': baremetal_driver_enabled_vendor_ifs, } baremetal_driver_passthru_method = 'lookup' BAREMETAL_DRIVER_PASSTHRU = {"lookup": {"attach": "false", "http_methods": ["POST"], "description": "", "async": "false"}} baremetal_portgroup_uuid = 'ppp-gggggg-pppp' baremetal_portgroup_name = 'Portgroup-name' baremetal_portgroup_address = 'AA:BB:CC:CC:BB:AA' baremetal_portgroup_mode = 'active-backup' baremetal_portgroup_extra = {'key1': 'value1', 'key2': 'value2'} baremetal_portgroup_properties = {'key1': 'value11', 'key2': 'value22'} PORTGROUP = {'uuid': baremetal_portgroup_uuid, 'name': baremetal_portgroup_name, 'node_uuid': baremetal_uuid, 'address': baremetal_portgroup_address, 'extra': baremetal_portgroup_extra, 'mode': baremetal_portgroup_mode, 'properties': baremetal_portgroup_properties, } VIFS = {'vifs': [{'id': 'aaa-aa'}]} TRAITS = ['CUSTOM_FOO', 'CUSTOM_BAR'] baremetal_volume_connector_uuid = 'vvv-cccccc-vvvv' baremetal_volume_connector_type = 'iqn' baremetal_volume_connector_connector_id = 'iqn.2017-01.connector' baremetal_volume_connector_extra = {'key1': 'value1', 'key2': 'value2'} VOLUME_CONNECTOR = { 'uuid': baremetal_volume_connector_uuid, 'node_uuid': baremetal_uuid, 'type': baremetal_volume_connector_type, 'connector_id': baremetal_volume_connector_connector_id, 'extra': baremetal_volume_connector_extra, } baremetal_volume_target_uuid = 'vvv-tttttt-vvvv' baremetal_volume_target_volume_type = 'iscsi' baremetal_volume_target_boot_index = 0 baremetal_volume_target_volume_id = 'vvv-tttttt-iii' baremetal_volume_target_extra = {'key1': 'value1', 'key2': 'value2'} baremetal_volume_target_properties = {'key11': 'value11', 'key22': 'value22'} VOLUME_TARGET = { 'uuid': baremetal_volume_target_uuid, 'node_uuid': baremetal_uuid, 'volume_type': baremetal_volume_target_volume_type, 'boot_index': baremetal_volume_target_boot_index, 'volume_id': baremetal_volume_target_volume_id, 'extra': baremetal_volume_target_extra, 'properties': baremetal_volume_target_properties, } class TestBaremetal(utils.TestCommand): def setUp(self): super(TestBaremetal, self).setUp() self.app.client_manager.auth_ref = mock.Mock(auth_token="TOKEN") self.app.client_manager.baremetal = mock.Mock() class FakeBaremetalResource(fakes.FakeResource): def get_keys(self): return {'property': 'value'} python-ironicclient-2.2.0/ironicclient/tests/unit/osc/v1/test_baremetal_volume_target.py0000666000175100017510000011255413232474343031776 0ustar zuulzuul00000000000000# Copyright 2017 FUJITSU LIMITED # # 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 copy import mock from osc_lib.tests import utils as osctestutils from ironicclient import exc from ironicclient.osc.v1 import baremetal_volume_target as bm_vol_target from ironicclient.tests.unit.osc.v1 import fakes as baremetal_fakes class TestBaremetalVolumeTarget(baremetal_fakes.TestBaremetal): def setUp(self): super(TestBaremetalVolumeTarget, self).setUp() self.baremetal_mock = self.app.client_manager.baremetal self.baremetal_mock.reset_mock() class TestCreateBaremetalVolumeTarget(TestBaremetalVolumeTarget): def setUp(self): super(TestCreateBaremetalVolumeTarget, self).setUp() self.baremetal_mock.volume_target.create.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.VOLUME_TARGET), loaded=True, )) # Get the command object to test self.cmd = ( bm_vol_target.CreateBaremetalVolumeTarget(self.app, None)) def test_baremetal_volume_target_create(self): arglist = [ '--node', baremetal_fakes.baremetal_uuid, '--type', baremetal_fakes.baremetal_volume_target_volume_type, '--boot-index', baremetal_fakes.baremetal_volume_target_boot_index, '--volume-id', baremetal_fakes.baremetal_volume_target_volume_id, '--uuid', baremetal_fakes.baremetal_volume_target_uuid, ] verifylist = [ ('node_uuid', baremetal_fakes.baremetal_uuid), ('volume_type', baremetal_fakes.baremetal_volume_target_volume_type), ('boot_index', baremetal_fakes.baremetal_volume_target_boot_index), ('volume_id', baremetal_fakes.baremetal_volume_target_volume_id), ('uuid', baremetal_fakes.baremetal_volume_target_uuid), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) args = { 'node_uuid': baremetal_fakes.baremetal_uuid, 'volume_type': baremetal_fakes.baremetal_volume_target_volume_type, 'boot_index': baremetal_fakes.baremetal_volume_target_boot_index, 'volume_id': baremetal_fakes.baremetal_volume_target_volume_id, 'uuid': baremetal_fakes.baremetal_volume_target_uuid, } self.baremetal_mock.volume_target.create.assert_called_once_with( **args) def test_baremetal_volume_target_create_without_uuid(self): arglist = [ '--node', baremetal_fakes.baremetal_uuid, '--type', baremetal_fakes.baremetal_volume_target_volume_type, '--boot-index', baremetal_fakes.baremetal_volume_target_boot_index, '--volume-id', baremetal_fakes.baremetal_volume_target_volume_id, ] verifylist = [ ('node_uuid', baremetal_fakes.baremetal_uuid), ('volume_type', baremetal_fakes.baremetal_volume_target_volume_type), ('boot_index', baremetal_fakes.baremetal_volume_target_boot_index), ('volume_id', baremetal_fakes.baremetal_volume_target_volume_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) args = { 'node_uuid': baremetal_fakes.baremetal_uuid, 'volume_type': baremetal_fakes.baremetal_volume_target_volume_type, 'boot_index': baremetal_fakes.baremetal_volume_target_boot_index, 'volume_id': baremetal_fakes.baremetal_volume_target_volume_id, } self.baremetal_mock.volume_target.create.assert_called_once_with( **args) def test_baremetal_volume_target_create_extras(self): arglist = [ '--node', baremetal_fakes.baremetal_uuid, '--type', baremetal_fakes.baremetal_volume_target_volume_type, '--boot-index', baremetal_fakes.baremetal_volume_target_boot_index, '--volume-id', baremetal_fakes.baremetal_volume_target_volume_id, '--extra', 'key1=value1', '--extra', 'key2=value2', ] verifylist = [ ('node_uuid', baremetal_fakes.baremetal_uuid), ('volume_type', baremetal_fakes.baremetal_volume_target_volume_type), ('boot_index', baremetal_fakes.baremetal_volume_target_boot_index), ('volume_id', baremetal_fakes.baremetal_volume_target_volume_id), ('extra', ['key1=value1', 'key2=value2']) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) args = { 'node_uuid': baremetal_fakes.baremetal_uuid, 'volume_type': baremetal_fakes.baremetal_volume_target_volume_type, 'boot_index': baremetal_fakes.baremetal_volume_target_boot_index, 'volume_id': baremetal_fakes.baremetal_volume_target_volume_id, 'extra': baremetal_fakes.baremetal_volume_target_extra, } self.baremetal_mock.volume_target.create.assert_called_once_with( **args) def _test_baremetal_volume_target_missing_param(self, missing): argdict = { '--node': baremetal_fakes.baremetal_uuid, '--type': baremetal_fakes.baremetal_volume_target_volume_type, '--boot-index': baremetal_fakes.baremetal_volume_target_boot_index, '--volume-id': baremetal_fakes.baremetal_volume_target_volume_id, '--uuid': baremetal_fakes.baremetal_volume_target_uuid, } arglist = [] for k, v in argdict.items(): if k not in missing: arglist += [k, v] verifylist = None self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_volume_target_create_missing_node(self): self._test_baremetal_volume_target_missing_param(['--node']) def test_baremetal_volume_target_create_missing_type(self): self._test_baremetal_volume_target_missing_param(['--type']) def test_baremetal_volume_target_create_missing_boot_index(self): self._test_baremetal_volume_target_missing_param(['--boot-index']) def test_baremetal_volume_target_create_missing_volume_id(self): self._test_baremetal_volume_target_missing_param(['--volume-id']) def test_baremetal_volume_target_create_invalid_boot_index(self): arglist = [ '--node', baremetal_fakes.baremetal_uuid, '--type', baremetal_fakes.baremetal_volume_target_volume_type, '--boot-index', 'string', '--volume-id', baremetal_fakes.baremetal_volume_target_volume_id, ] verifylist = None self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_volume_target_create_negative_boot_index(self): arglist = [ '--node', baremetal_fakes.baremetal_uuid, '--type', baremetal_fakes.baremetal_volume_target_volume_type, '--boot-index', '-1', '--volume-id', baremetal_fakes.baremetal_volume_target_volume_id, ] verifylist = [ ('node_uuid', baremetal_fakes.baremetal_uuid), ('volume_type', baremetal_fakes.baremetal_volume_target_volume_type), ('boot_index', -1), ('volume_id', baremetal_fakes.baremetal_volume_target_volume_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) class TestShowBaremetalVolumeTarget(TestBaremetalVolumeTarget): def setUp(self): super(TestShowBaremetalVolumeTarget, self).setUp() self.baremetal_mock.volume_target.get.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.VOLUME_TARGET), loaded=True)) self.cmd = ( bm_vol_target.ShowBaremetalVolumeTarget(self.app, None)) def test_baremetal_volume_target_show(self): arglist = ['vvv-tttttt-vvvv'] verifylist = [('volume_target', baremetal_fakes.baremetal_volume_target_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) args = ['vvv-tttttt-vvvv'] self.baremetal_mock.volume_target.get.assert_called_once_with( *args, fields=None) collist = ('boot_index', 'extra', 'node_uuid', 'properties', 'uuid', 'volume_id', 'volume_type') self.assertEqual(collist, columns) datalist = ( baremetal_fakes.baremetal_volume_target_boot_index, baremetal_fakes.baremetal_volume_target_extra, baremetal_fakes.baremetal_uuid, baremetal_fakes.baremetal_volume_target_properties, baremetal_fakes.baremetal_volume_target_uuid, baremetal_fakes.baremetal_volume_target_volume_id, baremetal_fakes.baremetal_volume_target_volume_type, ) self.assertEqual(datalist, tuple(data)) def test_baremetal_volume_target_show_no_options(self): arglist = [] verifylist = [] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_volume_target_show_fields(self): arglist = ['vvv-tttttt-vvvv', '--fields', 'uuid', 'volume_id'] verifylist = [('fields', [['uuid', 'volume_id']]), ('volume_target', baremetal_fakes.baremetal_volume_target_uuid)] fake_vt = copy.deepcopy(baremetal_fakes.VOLUME_TARGET) fake_vt.pop('node_uuid') fake_vt.pop('volume_type') fake_vt.pop('boot_index') fake_vt.pop('extra') fake_vt.pop('properties') self.baremetal_mock.volume_target.get.return_value = ( baremetal_fakes.FakeBaremetalResource( None, fake_vt, loaded=True)) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) args = ['vvv-tttttt-vvvv'] fields = ['uuid', 'volume_id'] self.baremetal_mock.volume_target.get.assert_called_once_with( *args, fields=fields) collist = ('uuid', 'volume_id') self.assertEqual(collist, columns) datalist = ( baremetal_fakes.baremetal_volume_target_uuid, baremetal_fakes.baremetal_volume_target_volume_id, ) self.assertEqual(datalist, tuple(data)) def test_baremetal_volume_target_show_fields_multiple(self): arglist = ['vvv-tttttt-vvvv', '--fields', 'uuid', 'volume_id', '--fields', 'volume_type'] verifylist = [('fields', [['uuid', 'volume_id'], ['volume_type']]), ('volume_target', baremetal_fakes.baremetal_volume_target_uuid)] fake_vt = copy.deepcopy(baremetal_fakes.VOLUME_TARGET) fake_vt.pop('node_uuid') fake_vt.pop('boot_index') fake_vt.pop('extra') fake_vt.pop('properties') self.baremetal_mock.volume_target.get.return_value = ( baremetal_fakes.FakeBaremetalResource( None, fake_vt, loaded=True)) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) args = ['vvv-tttttt-vvvv'] fields = ['uuid', 'volume_id', 'volume_type'] self.baremetal_mock.volume_target.get.assert_called_once_with( *args, fields=fields) collist = ('uuid', 'volume_id', 'volume_type') self.assertEqual(collist, columns) datalist = ( baremetal_fakes.baremetal_volume_target_uuid, baremetal_fakes.baremetal_volume_target_volume_id, baremetal_fakes.baremetal_volume_target_volume_type, ) self.assertEqual(datalist, tuple(data)) def test_baremetal_volume_target_show_invalid_fields(self): arglist = ['vvv-tttttt-vvvv', '--fields', 'uuid', 'invalid'] verifylist = None self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestListBaremetalVolumeTarget(TestBaremetalVolumeTarget): def setUp(self): super(TestListBaremetalVolumeTarget, self).setUp() self.baremetal_mock.volume_target.list.return_value = [ baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.VOLUME_TARGET), loaded=True) ] self.cmd = ( bm_vol_target.ListBaremetalVolumeTarget(self.app, None)) def test_baremetal_volume_target_list(self): arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) kwargs = { 'marker': None, 'limit': None} self.baremetal_mock.volume_target.list.assert_called_once_with( **kwargs) collist = ( "UUID", "Node UUID", "Driver Volume Type", "Boot Index", "Volume ID") self.assertEqual(collist, columns) datalist = ((baremetal_fakes.baremetal_volume_target_uuid, baremetal_fakes.baremetal_uuid, baremetal_fakes.baremetal_volume_target_volume_type, baremetal_fakes.baremetal_volume_target_boot_index, baremetal_fakes.baremetal_volume_target_volume_id),) self.assertEqual(datalist, tuple(data)) def test_baremetal_volume_target_list_node(self): arglist = ['--node', baremetal_fakes.baremetal_uuid] verifylist = [('node', baremetal_fakes.baremetal_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) kwargs = { 'node': baremetal_fakes.baremetal_uuid, 'marker': None, 'limit': None} self.baremetal_mock.volume_target.list.assert_called_once_with( **kwargs) collist = ( "UUID", "Node UUID", "Driver Volume Type", "Boot Index", "Volume ID") self.assertEqual(collist, columns) datalist = ((baremetal_fakes.baremetal_volume_target_uuid, baremetal_fakes.baremetal_uuid, baremetal_fakes.baremetal_volume_target_volume_type, baremetal_fakes.baremetal_volume_target_boot_index, baremetal_fakes.baremetal_volume_target_volume_id),) self.assertEqual(datalist, tuple(data)) def test_baremetal_volume_target_list_long(self): arglist = ['--long'] verifylist = [('detail', True)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) kwargs = { 'detail': True, 'marker': None, 'limit': None, } self.baremetal_mock.volume_target.list.assert_called_with(**kwargs) collist = ('UUID', 'Node UUID', 'Driver Volume Type', 'Properties', 'Boot Index', 'Extra', 'Volume ID', 'Created At', 'Updated At') self.assertEqual(collist, columns) datalist = ((baremetal_fakes.baremetal_volume_target_uuid, baremetal_fakes.baremetal_uuid, baremetal_fakes.baremetal_volume_target_volume_type, baremetal_fakes.baremetal_volume_target_properties, baremetal_fakes.baremetal_volume_target_boot_index, baremetal_fakes.baremetal_volume_target_extra, baremetal_fakes.baremetal_volume_target_volume_id, '', ''),) self.assertEqual(datalist, tuple(data)) def test_baremetal_volume_target_list_fields(self): arglist = ['--fields', 'uuid', 'boot_index'] verifylist = [('fields', [['uuid', 'boot_index']])] fake_vt = copy.deepcopy(baremetal_fakes.VOLUME_TARGET) fake_vt.pop('volume_type') fake_vt.pop('extra') fake_vt.pop('properties') fake_vt.pop('volume_id') fake_vt.pop('node_uuid') self.baremetal_mock.volume_target.list.return_value = [ baremetal_fakes.FakeBaremetalResource( None, fake_vt, loaded=True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) kwargs = { 'detail': False, 'marker': None, 'limit': None, 'fields': ('uuid', 'boot_index') } self.baremetal_mock.volume_target.list.assert_called_with(**kwargs) collist = ('UUID', 'Boot Index') self.assertEqual(collist, columns) datalist = ((baremetal_fakes.baremetal_volume_target_uuid, baremetal_fakes.baremetal_volume_target_boot_index),) self.assertEqual(datalist, tuple(data)) def test_baremetal_volume_target_list_fields_multiple(self): arglist = ['--fields', 'uuid', 'boot_index', '--fields', 'extra'] verifylist = [('fields', [['uuid', 'boot_index'], ['extra']])] fake_vt = copy.deepcopy(baremetal_fakes.VOLUME_TARGET) fake_vt.pop('volume_type') fake_vt.pop('properties') fake_vt.pop('volume_id') fake_vt.pop('node_uuid') self.baremetal_mock.volume_target.list.return_value = [ baremetal_fakes.FakeBaremetalResource( None, fake_vt, loaded=True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) kwargs = { 'detail': False, 'marker': None, 'limit': None, 'fields': ('uuid', 'boot_index', 'extra') } self.baremetal_mock.volume_target.list.assert_called_with(**kwargs) collist = ('UUID', 'Boot Index', 'Extra') self.assertEqual(collist, columns) datalist = ((baremetal_fakes.baremetal_volume_target_uuid, baremetal_fakes.baremetal_volume_target_boot_index, baremetal_fakes.baremetal_volume_target_extra),) self.assertEqual(datalist, tuple(data)) def test_baremetal_volume_target_list_invalid_fields(self): arglist = ['--fields', 'uuid', 'invalid'] verifylist = [('fields', [['uuid', 'invalid']])] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_volume_target_list_marker(self): arglist = ['--marker', baremetal_fakes.baremetal_volume_target_uuid] verifylist = [ ('marker', baremetal_fakes.baremetal_volume_target_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) kwargs = { 'marker': baremetal_fakes.baremetal_volume_target_uuid, 'limit': None} self.baremetal_mock.volume_target.list.assert_called_once_with( **kwargs) def test_baremetal_volume_target_list_limit(self): arglist = ['--limit', '10'] verifylist = [('limit', 10)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) kwargs = { 'marker': None, 'limit': 10} self.baremetal_mock.volume_target.list.assert_called_once_with( **kwargs) def test_baremetal_volume_target_list_sort(self): arglist = ['--sort', 'boot_index'] verifylist = [('sort', 'boot_index')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) kwargs = { 'marker': None, 'limit': None} self.baremetal_mock.volume_target.list.assert_called_once_with( **kwargs) def test_baremetal_volume_target_list_sort_desc(self): arglist = ['--sort', 'boot_index:desc'] verifylist = [('sort', 'boot_index:desc')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) kwargs = { 'marker': None, 'limit': None} self.baremetal_mock.volume_target.list.assert_called_once_with( **kwargs) def test_baremetal_volume_target_list_exclusive_options(self): arglist = ['--fields', 'uuid', '--long'] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, []) def test_baremetal_volume_target_list_negative_limit(self): arglist = ['--limit', '-1'] verifylist = [('limit', -1)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) class TestDeleteBaremetalVolumeTarget(TestBaremetalVolumeTarget): def setUp(self): super(TestDeleteBaremetalVolumeTarget, self).setUp() self.cmd = bm_vol_target.DeleteBaremetalVolumeTarget(self.app, None) def test_baremetal_volume_target_delete(self): arglist = [baremetal_fakes.baremetal_volume_target_uuid] verifylist = [('volume_targets', [baremetal_fakes.baremetal_volume_target_uuid])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_target.delete.assert_called_with( baremetal_fakes.baremetal_volume_target_uuid) def test_baremetal_volume_target_delete_multiple(self): fake_volume_target_uuid2 = 'vvv-tttttt-tttt' arglist = [baremetal_fakes.baremetal_volume_target_uuid, fake_volume_target_uuid2] verifylist = [('volume_targets', [baremetal_fakes.baremetal_volume_target_uuid, fake_volume_target_uuid2])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_target.delete.has_calls( [mock.call(baremetal_fakes.baremetal_volume_target_uuid), mock.call(fake_volume_target_uuid2)]) self.assertEqual( 2, self.baremetal_mock.volume_target.delete.call_count) def test_baremetal_volume_target_delete_no_options(self): arglist = [] verifylist = [] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_volume_target_delete_error(self): arglist = [baremetal_fakes.baremetal_volume_target_uuid] verifylist = [('volume_targets', [baremetal_fakes.baremetal_volume_target_uuid])] self.baremetal_mock.volume_target.delete.side_effect = ( exc.NotFound()) parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exc.ClientException, self.cmd.take_action, parsed_args) self.baremetal_mock.volume_target.delete.assert_called_with( baremetal_fakes.baremetal_volume_target_uuid) def test_baremetal_volume_target_delete_multiple_error(self): fake_volume_target_uuid2 = 'vvv-tttttt-tttt' arglist = [baremetal_fakes.baremetal_volume_target_uuid, fake_volume_target_uuid2] verifylist = [('volume_targets', [baremetal_fakes.baremetal_volume_target_uuid, fake_volume_target_uuid2])] self.baremetal_mock.volume_target.delete.side_effect = [ None, exc.NotFound()] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exc.ClientException, self.cmd.take_action, parsed_args) self.baremetal_mock.volume_target.delete.has_calls( [mock.call(baremetal_fakes.baremetal_volume_target_uuid), mock.call(fake_volume_target_uuid2)]) self.assertEqual( 2, self.baremetal_mock.volume_target.delete.call_count) class TestSetBaremetalVolumeTarget(TestBaremetalVolumeTarget): def setUp(self): super(TestSetBaremetalVolumeTarget, self).setUp() self.cmd = ( bm_vol_target.SetBaremetalVolumeTarget(self.app, None)) def test_baremetal_volume_target_set_node_uuid(self): new_node_uuid = 'xxx-xxxxxx-zzzz' arglist = [ baremetal_fakes.baremetal_volume_target_uuid, '--node', new_node_uuid] verifylist = [ ('volume_target', baremetal_fakes.baremetal_volume_target_uuid), ('node_uuid', new_node_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_target.update.assert_called_once_with( baremetal_fakes.baremetal_volume_target_uuid, [{'path': '/node_uuid', 'value': new_node_uuid, 'op': 'add'}]) def test_baremetal_volume_target_set_volume_type(self): new_type = 'fibre_channel' arglist = [ baremetal_fakes.baremetal_volume_target_uuid, '--type', new_type] verifylist = [ ('volume_target', baremetal_fakes.baremetal_volume_target_uuid), ('volume_type', new_type)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_target.update.assert_called_once_with( baremetal_fakes.baremetal_volume_target_uuid, [{'path': '/volume_type', 'value': new_type, 'op': 'add'}]) def test_baremetal_volume_target_set_boot_index(self): new_boot_idx = '3' arglist = [ baremetal_fakes.baremetal_volume_target_uuid, '--boot-index', new_boot_idx] verifylist = [ ('volume_target', baremetal_fakes.baremetal_volume_target_uuid), ('boot_index', int(new_boot_idx))] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_target.update.assert_called_once_with( baremetal_fakes.baremetal_volume_target_uuid, [{'path': '/boot_index', 'value': int(new_boot_idx), 'op': 'add'}]) def test_baremetal_volume_target_set_negative_boot_index(self): new_boot_idx = '-3' arglist = [ baremetal_fakes.baremetal_volume_target_uuid, '--boot-index', new_boot_idx] verifylist = [ ('volume_target', baremetal_fakes.baremetal_volume_target_uuid), ('boot_index', int(new_boot_idx))] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) def test_baremetal_volume_target_set_invalid_boot_index(self): new_boot_idx = 'string' arglist = [ baremetal_fakes.baremetal_volume_target_uuid, '--boot-index', new_boot_idx] verifylist = None self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_volume_target_set_volume_id(self): new_volume_id = 'new-volume-id' arglist = [ baremetal_fakes.baremetal_volume_target_uuid, '--volume-id', new_volume_id] verifylist = [ ('volume_target', baremetal_fakes.baremetal_volume_target_uuid), ('volume_id', new_volume_id)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_target.update.assert_called_once_with( baremetal_fakes.baremetal_volume_target_uuid, [{'path': '/volume_id', 'value': new_volume_id, 'op': 'add'}]) def test_baremetal_volume_target_set_volume_type_and_volume_id(self): new_volume_type = 'fibre_channel' new_volume_id = 'new-volume-id' arglist = [ baremetal_fakes.baremetal_volume_target_uuid, '--type', new_volume_type, '--volume-id', new_volume_id] verifylist = [ ('volume_target', baremetal_fakes.baremetal_volume_target_uuid), ('volume_type', new_volume_type), ('volume_id', new_volume_id)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_target.update.assert_called_once_with( baremetal_fakes.baremetal_volume_target_uuid, [{'path': '/volume_type', 'value': new_volume_type, 'op': 'add'}, {'path': '/volume_id', 'value': new_volume_id, 'op': 'add'}]) def test_baremetal_volume_target_set_extra(self): arglist = [ baremetal_fakes.baremetal_volume_target_uuid, '--extra', 'foo=bar'] verifylist = [ ('volume_target', baremetal_fakes.baremetal_volume_target_uuid), ('extra', ['foo=bar'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_target.update.assert_called_once_with( baremetal_fakes.baremetal_volume_target_uuid, [{'path': '/extra/foo', 'value': 'bar', 'op': 'add'}]) def test_baremetal_volume_target_set_multiple_extras(self): arglist = [ baremetal_fakes.baremetal_volume_target_uuid, '--extra', 'key1=val1', '--extra', 'key2=val2'] verifylist = [ ('volume_target', baremetal_fakes.baremetal_volume_target_uuid), ('extra', ['key1=val1', 'key2=val2'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_target.update.assert_called_once_with( baremetal_fakes.baremetal_volume_target_uuid, [{'path': '/extra/key1', 'value': 'val1', 'op': 'add'}, {'path': '/extra/key2', 'value': 'val2', 'op': 'add'}]) def test_baremetal_volume_target_set_property(self): arglist = [ baremetal_fakes.baremetal_volume_target_uuid, '--property', 'foo=bar'] verifylist = [ ('volume_target', baremetal_fakes.baremetal_volume_target_uuid), ('properties', ['foo=bar'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_target.update.assert_called_once_with( baremetal_fakes.baremetal_volume_target_uuid, [{'path': '/properties/foo', 'value': 'bar', 'op': 'add'}]) def test_baremetal_volume_target_set_multiple_properties(self): arglist = [ baremetal_fakes.baremetal_volume_target_uuid, '--property', 'key1=val1', '--property', 'key2=val2'] verifylist = [ ('volume_target', baremetal_fakes.baremetal_volume_target_uuid), ('properties', ['key1=val1', 'key2=val2'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_target.update.assert_called_once_with( baremetal_fakes.baremetal_volume_target_uuid, [{'path': '/properties/key1', 'value': 'val1', 'op': 'add'}, {'path': '/properties/key2', 'value': 'val2', 'op': 'add'}]) def test_baremetal_volume_target_set_no_options(self): arglist = [] verifylist = [] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_volume_target_set_no_property(self): arglist = [baremetal_fakes.baremetal_volume_target_uuid] verifylist = [('volume_target', baremetal_fakes.baremetal_volume_target_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_target.update.assert_not_called() class TestUnsetBaremetalVolumeTarget(TestBaremetalVolumeTarget): def setUp(self): super(TestUnsetBaremetalVolumeTarget, self).setUp() self.cmd = bm_vol_target.UnsetBaremetalVolumeTarget(self.app, None) def test_baremetal_volume_target_unset_extra(self): arglist = [baremetal_fakes.baremetal_volume_target_uuid, '--extra', 'key1'] verifylist = [('volume_target', baremetal_fakes.baremetal_volume_target_uuid), ('extra', ['key1'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_target.update.assert_called_once_with( baremetal_fakes.baremetal_volume_target_uuid, [{'path': '/extra/key1', 'op': 'remove'}]) def test_baremetal_volume_target_unset_multiple_extras(self): arglist = [baremetal_fakes.baremetal_volume_target_uuid, '--extra', 'key1', '--extra', 'key2'] verifylist = [('volume_target', baremetal_fakes.baremetal_volume_target_uuid), ('extra', ['key1', 'key2'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_target.update.assert_called_once_with( baremetal_fakes.baremetal_volume_target_uuid, [{'path': '/extra/key1', 'op': 'remove'}, {'path': '/extra/key2', 'op': 'remove'}]) def test_baremetal_volume_target_unset_property(self): arglist = [baremetal_fakes.baremetal_volume_target_uuid, '--property', 'key11'] verifylist = [('volume_target', baremetal_fakes.baremetal_volume_target_uuid), ('properties', ['key11'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_target.update.assert_called_once_with( baremetal_fakes.baremetal_volume_target_uuid, [{'path': '/properties/key11', 'op': 'remove'}]) def test_baremetal_volume_target_unset_multiple_properties(self): arglist = [baremetal_fakes.baremetal_volume_target_uuid, '--property', 'key11', '--property', 'key22'] verifylist = [('volume_target', baremetal_fakes.baremetal_volume_target_uuid), ('properties', ['key11', 'key22'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_target.update.assert_called_once_with( baremetal_fakes.baremetal_volume_target_uuid, [{'path': '/properties/key11', 'op': 'remove'}, {'path': '/properties/key22', 'op': 'remove'}]) def test_baremetal_volume_target_unset_no_options(self): arglist = [] verifylist = [] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_volume_target_unset_no_property(self): arglist = [baremetal_fakes.baremetal_volume_target_uuid] verifylist = [('volume_target', baremetal_fakes.baremetal_volume_target_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.volume_target.update.assert_not_called() python-ironicclient-2.2.0/ironicclient/tests/unit/osc/v1/test_baremetal_port.py0000666000175100017510000006532213232474343030105 0ustar zuulzuul00000000000000# # Copyright 2015 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import copy import mock from osc_lib.tests import utils as osctestutils from osc_lib import utils as oscutils from ironicclient import exc from ironicclient.osc.v1 import baremetal_port from ironicclient.tests.unit.osc.v1 import fakes as baremetal_fakes class TestBaremetalPort(baremetal_fakes.TestBaremetal): def setUp(self): super(TestBaremetalPort, self).setUp() self.baremetal_mock = self.app.client_manager.baremetal self.baremetal_mock.reset_mock() class TestCreateBaremetalPort(TestBaremetalPort): def setUp(self): super(TestCreateBaremetalPort, self).setUp() self.baremetal_mock.port.create.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL_PORT), loaded=True, )) # Get the command object to test self.cmd = baremetal_port.CreateBaremetalPort(self.app, None) def test_baremetal_port_create(self): arglist = [ baremetal_fakes.baremetal_port_address, '--node', baremetal_fakes.baremetal_uuid, ] verifylist = [ ('node_uuid', baremetal_fakes.baremetal_uuid), ('address', baremetal_fakes.baremetal_port_address), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) # Set expected values args = { 'address': baremetal_fakes.baremetal_port_address, 'node_uuid': baremetal_fakes.baremetal_uuid, } self.baremetal_mock.port.create.assert_called_once_with(**args) def test_baremetal_port_create_extras(self): arglist = [ baremetal_fakes.baremetal_port_address, '--node', baremetal_fakes.baremetal_uuid, '--extra', 'key1=value1', '--extra', 'key2=value2' ] verifylist = [ ('node_uuid', baremetal_fakes.baremetal_uuid), ('address', baremetal_fakes.baremetal_port_address), ('extra', ['key1=value1', 'key2=value2']) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) args = { 'address': baremetal_fakes.baremetal_port_address, 'node_uuid': baremetal_fakes.baremetal_uuid, 'extra': baremetal_fakes.baremetal_port_extra } self.baremetal_mock.port.create.assert_called_once_with(**args) def test_baremetal_port_create_no_address(self): arglist = ['--node', baremetal_fakes.baremetal_uuid] verifylist = [('node_uuid', baremetal_fakes.baremetal_uuid)] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_port_create_no_node(self): arglist = [baremetal_fakes.baremetal_port_address] verifylist = [ ('address', baremetal_fakes.baremetal_port_address) ] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_port_create_no_args(self): arglist = [] verifylist = [] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_port_create_uuid(self): port_uuid = "da6c8d2e-fbcd-457a-b2a7-cc5c775933af" arglist = [ baremetal_fakes.baremetal_port_address, '--node', baremetal_fakes.baremetal_uuid, '--uuid', port_uuid ] verifylist = [ ('node_uuid', baremetal_fakes.baremetal_uuid), ('address', baremetal_fakes.baremetal_port_address), ('uuid', port_uuid) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values args = { 'address': baremetal_fakes.baremetal_port_address, 'node_uuid': baremetal_fakes.baremetal_uuid, 'uuid': port_uuid } self.baremetal_mock.port.create.assert_called_once_with(**args) def _test_baremetal_port_create_llc_warning(self, additional_args, additional_verify_items): arglist = [ baremetal_fakes.baremetal_port_address, '--node', baremetal_fakes.baremetal_uuid, ] arglist.extend(additional_args) verifylist = [ ('node_uuid', baremetal_fakes.baremetal_uuid), ('address', baremetal_fakes.baremetal_port_address), ] verifylist.extend(additional_verify_items) parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.log = mock.Mock() self.cmd.take_action(parsed_args) # Set expected values args = { 'address': baremetal_fakes.baremetal_port_address, 'node_uuid': baremetal_fakes.baremetal_uuid, 'local_link_connection': {'switch_id': 'aa:bb:cc:dd:ee:ff', 'port_id': 'eth0'} } self.baremetal_mock.port.create.assert_called_once_with(**args) self.cmd.log.warning.assert_called() def test_baremetal_port_create_llc_warning_some_deprecated(self): self._test_baremetal_port_create_llc_warning( additional_args=['-l', 'port_id=eth0', '--local-link-connection', 'switch_id=aa:bb:cc:dd:ee:ff'], additional_verify_items=[ ('local_link_connection_deprecated', ['port_id=eth0']), ('local_link_connection', ['switch_id=aa:bb:cc:dd:ee:ff'])] ) def test_baremetal_port_create_llc_warning_all_deprecated(self): self._test_baremetal_port_create_llc_warning( additional_args=['-l', 'port_id=eth0', '-l', 'switch_id=aa:bb:cc:dd:ee:ff'], additional_verify_items=[('local_link_connection_deprecated', ['port_id=eth0', 'switch_id=aa:bb:cc:dd:ee:ff'])] ) def test_baremetal_port_create_portgroup_uuid(self): arglist = [ baremetal_fakes.baremetal_port_address, '--node', baremetal_fakes.baremetal_uuid, '--port-group', baremetal_fakes.baremetal_portgroup_uuid, ] verifylist = [ ('node_uuid', baremetal_fakes.baremetal_uuid), ('address', baremetal_fakes.baremetal_port_address), ('portgroup_uuid', baremetal_fakes.baremetal_portgroup_uuid) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values args = { 'address': baremetal_fakes.baremetal_port_address, 'node_uuid': baremetal_fakes.baremetal_uuid, 'portgroup_uuid': baremetal_fakes.baremetal_portgroup_uuid } self.baremetal_mock.port.create.assert_called_once_with(**args) def test_baremetal_port_create_physical_network(self): arglist = [ baremetal_fakes.baremetal_port_address, '--node', baremetal_fakes.baremetal_uuid, '--physical-network', baremetal_fakes.baremetal_port_physical_network, ] verifylist = [ ('node_uuid', baremetal_fakes.baremetal_uuid), ('address', baremetal_fakes.baremetal_port_address), ('physical_network', baremetal_fakes.baremetal_port_physical_network) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values args = { 'address': baremetal_fakes.baremetal_port_address, 'node_uuid': baremetal_fakes.baremetal_uuid, 'physical_network': baremetal_fakes.baremetal_port_physical_network } self.baremetal_mock.port.create.assert_called_once_with(**args) class TestShowBaremetalPort(TestBaremetalPort): def setUp(self): super(TestShowBaremetalPort, self).setUp() self.baremetal_mock.port.get.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL_PORT), loaded=True)) self.baremetal_mock.port.get_by_address.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL_PORT), loaded=True)) self.cmd = baremetal_port.ShowBaremetalPort(self.app, None) def test_baremetal_port_show(self): arglist = ['zzz-zzzzzz-zzzz'] verifylist = [('port', baremetal_fakes.baremetal_port_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Set expected values args = ['zzz-zzzzzz-zzzz'] self.baremetal_mock.port.get.assert_called_with(*args, fields=None) collist = ( 'address', 'extra', 'node_uuid', 'uuid') self.assertEqual(collist, columns) datalist = ( baremetal_fakes.baremetal_port_address, baremetal_fakes.baremetal_port_extra, baremetal_fakes.baremetal_uuid, baremetal_fakes.baremetal_port_uuid) self.assertEqual(datalist, tuple(data)) def test_baremetal_port_show_address(self): arglist = ['--address', baremetal_fakes.baremetal_port_address] verifylist = [('address', True)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) args = {'AA:BB:CC:DD:EE:FF'} self.baremetal_mock.port.get_by_address.assert_called_with( *args, fields=None) def test_baremetal_port_show_no_port(self): arglist = [] verifylist = [] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestBaremetalPortUnset(TestBaremetalPort): def setUp(self): super(TestBaremetalPortUnset, self).setUp() self.baremetal_mock.port.update.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL_PORT), loaded=True)) self.cmd = baremetal_port.UnsetBaremetalPort(self.app, None) def test_baremetal_port_unset_no_options(self): arglist = [] verifylist = [] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_port_unset_no_property(self): arglist = [baremetal_fakes.baremetal_port_uuid] verifylist = [('port', baremetal_fakes.baremetal_port_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.assertFalse(self.baremetal_mock.port.update.called) def test_baremetal_port_unset_extra(self): arglist = ['port', '--extra', 'foo'] verifylist = [('port', 'port'), ('extra', ['foo'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.port.update.assert_called_once_with( 'port', [{'path': '/extra/foo', 'op': 'remove'}]) def test_baremetal_port_unset_multiple_extras(self): arglist = ['port', '--extra', 'foo', '--extra', 'bar'] verifylist = [('port', 'port'), ('extra', ['foo', 'bar'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.port.update.assert_called_once_with( 'port', [{'path': '/extra/foo', 'op': 'remove'}, {'path': '/extra/bar', 'op': 'remove'}]) def test_baremetal_port_unset_portgroup_uuid(self): arglist = ['port', '--port-group'] verifylist = [('port', 'port'), ('portgroup', True)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.port.update.assert_called_once_with( 'port', [{'path': '/portgroup_uuid', 'op': 'remove'}]) def test_baremetal_port_unset_physical_network(self): arglist = ['port', '--physical-network'] verifylist = [('port', 'port'), ('physical_network', True)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.port.update.assert_called_once_with( 'port', [{'path': '/physical_network', 'op': 'remove'}]) class TestBaremetalPortSet(TestBaremetalPort): def setUp(self): super(TestBaremetalPortSet, self).setUp() self.baremetal_mock.port.update.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL_PORT), loaded=True)) self.cmd = baremetal_port.SetBaremetalPort(self.app, None) def test_baremetal_port_set_node_uuid(self): new_node_uuid = '1111-111111-1111' arglist = [ baremetal_fakes.baremetal_port_uuid, '--node', new_node_uuid] verifylist = [ ('port', baremetal_fakes.baremetal_port_uuid), ('node_uuid', new_node_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.port.update.assert_called_once_with( baremetal_fakes.baremetal_port_uuid, [{'path': '/node_uuid', 'value': new_node_uuid, 'op': 'add'}]) def test_baremetal_port_set_address(self): arglist = [ baremetal_fakes.baremetal_port_uuid, '--address', baremetal_fakes.baremetal_port_address] verifylist = [ ('port', baremetal_fakes.baremetal_port_uuid), ('address', baremetal_fakes.baremetal_port_address)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.port.update.assert_called_once_with( baremetal_fakes.baremetal_port_uuid, [{'path': '/address', 'value': baremetal_fakes.baremetal_port_address, 'op': 'add'}]) def test_baremetal_set_extra(self): arglist = ['port', '--extra', 'foo=bar'] verifylist = [('port', 'port'), ('extra', ['foo=bar'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.port.update.assert_called_once_with( 'port', [{'path': '/extra/foo', 'value': 'bar', 'op': 'add'}]) def test_baremetal_port_set_portgroup_uuid(self): new_portgroup_uuid = '1111-111111-1111' arglist = [ baremetal_fakes.baremetal_port_uuid, '--port-group', new_portgroup_uuid] verifylist = [ ('port', baremetal_fakes.baremetal_port_uuid), ('portgroup_uuid', new_portgroup_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.port.update.assert_called_once_with( baremetal_fakes.baremetal_port_uuid, [{'path': '/portgroup_uuid', 'value': new_portgroup_uuid, 'op': 'add'}]) def test_baremetal_set_local_link_connection(self): arglist = [ baremetal_fakes.baremetal_port_uuid, '--local-link-connection', 'switch_info=bar'] verifylist = [('port', baremetal_fakes.baremetal_port_uuid), ('local_link_connection', ['switch_info=bar'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.port.update.assert_called_once_with( baremetal_fakes.baremetal_port_uuid, [{'path': '/local_link_connection/switch_info', 'value': 'bar', 'op': 'add'}]) def test_baremetal_port_set_pxe_enabled(self): arglist = [ baremetal_fakes.baremetal_port_uuid, '--pxe-enabled'] verifylist = [ ('port', baremetal_fakes.baremetal_port_uuid), ('pxe_enabled', True)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.port.update.assert_called_once_with( baremetal_fakes.baremetal_port_uuid, [{'path': '/pxe_enabled', 'value': 'True', 'op': 'add'}]) def test_baremetal_port_set_pxe_disabled(self): arglist = [ baremetal_fakes.baremetal_port_uuid, '--pxe-disabled'] verifylist = [ ('port', baremetal_fakes.baremetal_port_uuid), ('pxe_enabled', False)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.port.update.assert_called_once_with( baremetal_fakes.baremetal_port_uuid, [{'path': '/pxe_enabled', 'value': 'False', 'op': 'add'}]) def test_baremetal_port_set_physical_network(self): new_physical_network = 'physnet2' arglist = [ baremetal_fakes.baremetal_port_uuid, '--physical-network', new_physical_network] verifylist = [ ('port', baremetal_fakes.baremetal_port_uuid), ('physical_network', new_physical_network)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.port.update.assert_called_once_with( baremetal_fakes.baremetal_port_uuid, [{'path': '/physical_network', 'value': new_physical_network, 'op': 'add'}]) def test_baremetal_port_set_no_options(self): arglist = [] verifylist = [] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_port_set_no_property(self): arglist = [baremetal_fakes.baremetal_port_uuid] verifylist = [('port', baremetal_fakes.baremetal_port_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.assertFalse(self.baremetal_mock.port.update.called) class TestBaremetalPortDelete(TestBaremetalPort): def setUp(self): super(TestBaremetalPortDelete, self).setUp() self.baremetal_mock.port.get.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL_PORT), loaded=True)) self.cmd = baremetal_port.DeleteBaremetalPort(self.app, None) def test_baremetal_port_delete(self): arglist = ['zzz-zzzzzz-zzzz'] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) args = 'zzz-zzzzzz-zzzz' self.baremetal_mock.port.delete.assert_called_with(args) def test_baremetal_port_delete_multiple(self): arglist = ['zzz-zzzzzz-zzzz', 'fakename'] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) args = ['zzz-zzzzzz-zzzz', 'fakename'] self.baremetal_mock.port.delete.has_calls( [mock.call(x) for x in args]) self.assertEqual(2, self.baremetal_mock.port.delete.call_count) def test_baremetal_port_delete_multiple_with_fail(self): arglist = ['zzz-zzzzzz-zzzz', 'badname'] verifylist = [] self.baremetal_mock.port.delete.side_effect = ['', exc.ClientException] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exc.ClientException, self.cmd.take_action, parsed_args) args = ['zzz-zzzzzz-zzzz', 'badname'] self.baremetal_mock.port.delete.has_calls( [mock.call(x) for x in args]) self.assertEqual(2, self.baremetal_mock.port.delete.call_count) def test_baremetal_port_delete_no_port(self): arglist = [] verifylist = [] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestBaremetalPortList(TestBaremetalPort): def setUp(self): super(TestBaremetalPortList, self).setUp() self.baremetal_mock.port.list.return_value = [ baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.BAREMETAL_PORT), loaded=True) ] self.cmd = baremetal_port.ListBaremetalPort(self.app, None) def test_baremetal_port_list(self): arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) kwargs = { 'marker': None, 'limit': None} self.baremetal_mock.port.list.assert_called_with(**kwargs) collist = ( "UUID", "Address") self.assertEqual(collist, columns) datalist = (( baremetal_fakes.baremetal_port_uuid, baremetal_fakes.baremetal_port_address ), ) self.assertEqual(datalist, tuple(data)) def test_baremetal_port_list_address(self): arglist = ['--address', baremetal_fakes.baremetal_port_address] verifylist = [('address', baremetal_fakes.baremetal_port_address)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) kwargs = { 'address': baremetal_fakes.baremetal_port_address, 'marker': None, 'limit': None, } self.baremetal_mock.port.list.assert_called_with(**kwargs) def test_baremetal_port_list_node(self): arglist = ['--node', baremetal_fakes.baremetal_uuid] verifylist = [('node', baremetal_fakes.baremetal_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) kwargs = { 'node': baremetal_fakes.baremetal_uuid, 'marker': None, 'limit': None, } self.baremetal_mock.port.list.assert_called_with(**kwargs) def test_baremetal_port_list_portgroup(self): arglist = ['--port-group', baremetal_fakes.baremetal_portgroup_uuid] verifylist = [('portgroup', baremetal_fakes.baremetal_portgroup_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) kwargs = { 'portgroup': baremetal_fakes.baremetal_portgroup_uuid, 'marker': None, 'limit': None, } self.baremetal_mock.port.list.assert_called_with(**kwargs) def test_baremetal_port_list_long(self): arglist = ['--long'] verifylist = [('detail', True)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) kwargs = { 'detail': True, 'marker': None, 'limit': None, } self.baremetal_mock.port.list.assert_called_with(**kwargs) collist = ('UUID', 'Address', 'Created At', 'Extra', 'Node UUID', 'Local Link Connection', 'Portgroup UUID', 'PXE boot enabled', 'Physical Network', 'Updated At', 'Internal Info') self.assertEqual(collist, columns) datalist = (( baremetal_fakes.baremetal_port_uuid, baremetal_fakes.baremetal_port_address, '', oscutils.format_dict(baremetal_fakes.baremetal_port_extra), baremetal_fakes.baremetal_uuid, '', '', '', '', '', '' ), ) self.assertEqual(datalist, tuple(data)) def test_baremetal_port_list_fields(self): arglist = ['--fields', 'uuid', 'address'] verifylist = [('fields', [['uuid', 'address']])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) kwargs = { 'marker': None, 'limit': None, 'detail': False, 'fields': ('uuid', 'address') } self.baremetal_mock.port.list.assert_called_with(**kwargs) def test_baremetal_port_list_fields_multiple(self): arglist = ['--fields', 'uuid', 'address', '--fields', 'extra'] verifylist = [('fields', [['uuid', 'address'], ['extra']])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) kwargs = { 'marker': None, 'limit': None, 'detail': False, 'fields': ('uuid', 'address', 'extra') } self.baremetal_mock.port.list.assert_called_with(**kwargs) def test_baremetal_port_list_invalid_fields(self): arglist = ['--fields', 'uuid', 'invalid'] verifylist = [('fields', [['uuid', 'invalid']])] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) python-ironicclient-2.2.0/ironicclient/tests/unit/osc/v1/test_baremetal_portgroup.py0000666000175100017510000006774213232474373031175 0ustar zuulzuul00000000000000# # Copyright 2016 Mirantis, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import copy import mock from osc_lib.tests import utils as osctestutils from ironicclient.osc.v1 import baremetal_portgroup from ironicclient.tests.unit.osc.v1 import fakes as baremetal_fakes class TestBaremetalPortGroup(baremetal_fakes.TestBaremetal): def setUp(self): super(TestBaremetalPortGroup, self).setUp() self.baremetal_mock = self.app.client_manager.baremetal self.baremetal_mock.reset_mock() class TestCreateBaremetalPortGroup(TestBaremetalPortGroup): def setUp(self): super(TestCreateBaremetalPortGroup, self).setUp() self.baremetal_mock.portgroup.create.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.PORTGROUP), loaded=True, )) # Get the command object to test self.cmd = baremetal_portgroup.CreateBaremetalPortGroup(self.app, None) def test_baremetal_portgroup_create(self): arglist = [ '--node', baremetal_fakes.baremetal_uuid, ] verifylist = [ ('node_uuid', baremetal_fakes.baremetal_uuid), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values args = { 'node_uuid': baremetal_fakes.baremetal_uuid, } self.baremetal_mock.portgroup.create.assert_called_once_with(**args) def test_baremetal_portgroup_create_name_address_uuid(self): arglist = [ '--address', baremetal_fakes.baremetal_portgroup_address, '--node', baremetal_fakes.baremetal_uuid, '--name', baremetal_fakes.baremetal_portgroup_name, '--uuid', baremetal_fakes.baremetal_portgroup_uuid, ] verifylist = [ ('node_uuid', baremetal_fakes.baremetal_uuid), ('address', baremetal_fakes.baremetal_portgroup_address), ('name', baremetal_fakes.baremetal_portgroup_name), ('uuid', baremetal_fakes.baremetal_portgroup_uuid), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values args = { 'address': baremetal_fakes.baremetal_portgroup_address, 'node_uuid': baremetal_fakes.baremetal_uuid, 'name': baremetal_fakes.baremetal_portgroup_name, 'uuid': baremetal_fakes.baremetal_portgroup_uuid, } self.baremetal_mock.portgroup.create.assert_called_once_with(**args) def test_baremetal_portgroup_create_support_standalone_ports(self): arglist = [ '--address', baremetal_fakes.baremetal_portgroup_address, '--node', baremetal_fakes.baremetal_uuid, '--support-standalone-ports' ] verifylist = [ ('node_uuid', baremetal_fakes.baremetal_uuid), ('address', baremetal_fakes.baremetal_portgroup_address), ('support_standalone_ports', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values args = { 'address': baremetal_fakes.baremetal_portgroup_address, 'node_uuid': baremetal_fakes.baremetal_uuid, 'standalone_ports_supported': True, } self.baremetal_mock.portgroup.create.assert_called_once_with(**args) def test_baremetal_portgroup_create_unsupport_standalone_ports(self): arglist = [ '--address', baremetal_fakes.baremetal_portgroup_address, '--node', baremetal_fakes.baremetal_uuid, '--unsupport-standalone-ports' ] verifylist = [ ('node_uuid', baremetal_fakes.baremetal_uuid), ('address', baremetal_fakes.baremetal_portgroup_address), ('unsupport_standalone_ports', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values args = { 'address': baremetal_fakes.baremetal_portgroup_address, 'node_uuid': baremetal_fakes.baremetal_uuid, 'standalone_ports_supported': False, } self.baremetal_mock.portgroup.create.assert_called_once_with(**args) def test_baremetal_portgroup_create_name_extras(self): arglist = [ '--address', baremetal_fakes.baremetal_portgroup_address, '--node', baremetal_fakes.baremetal_uuid, '--name', baremetal_fakes.baremetal_portgroup_name, '--extra', 'key1=value1', '--extra', 'key2=value2' ] verifylist = [ ('node_uuid', baremetal_fakes.baremetal_uuid), ('address', baremetal_fakes.baremetal_portgroup_address), ('name', baremetal_fakes.baremetal_portgroup_name), ('extra', ['key1=value1', 'key2=value2']) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values args = { 'address': baremetal_fakes.baremetal_portgroup_address, 'node_uuid': baremetal_fakes.baremetal_uuid, 'name': baremetal_fakes.baremetal_portgroup_name, 'extra': baremetal_fakes.baremetal_portgroup_extra } self.baremetal_mock.portgroup.create.assert_called_once_with(**args) def test_baremetal_portgroup_create_mode_properties(self): arglist = [ '--node', baremetal_fakes.baremetal_uuid, '--mode', baremetal_fakes.baremetal_portgroup_mode, '--property', 'key1=value11', '--property', 'key2=value22' ] verifylist = [ ('node_uuid', baremetal_fakes.baremetal_uuid), ('mode', baremetal_fakes.baremetal_portgroup_mode), ('properties', ['key1=value11', 'key2=value22']) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values args = { 'node_uuid': baremetal_fakes.baremetal_uuid, 'mode': baremetal_fakes.baremetal_portgroup_mode, 'properties': baremetal_fakes.baremetal_portgroup_properties } self.baremetal_mock.portgroup.create.assert_called_once_with(**args) def test_baremetal_portgroup_create_no_options(self): arglist = [] verifylist = [] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestShowBaremetalPortGroup(TestBaremetalPortGroup): def setUp(self): super(TestShowBaremetalPortGroup, self).setUp() self.baremetal_mock.portgroup.get.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.PORTGROUP), loaded=True)) self.baremetal_mock.portgroup.get_by_address.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.PORTGROUP), loaded=True)) self.cmd = baremetal_portgroup.ShowBaremetalPortGroup(self.app, None) def test_baremetal_portgroup_show(self): arglist = ['ppp-gggggg-pppp'] verifylist = [('portgroup', baremetal_fakes.baremetal_portgroup_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) # Set expected values args = ['ppp-gggggg-pppp'] self.baremetal_mock.portgroup.get.assert_called_with(*args, fields=None) collist = ('address', 'extra', 'mode', 'name', 'node_uuid', 'properties', 'uuid') self.assertEqual(collist, columns) datalist = ( baremetal_fakes.baremetal_portgroup_address, baremetal_fakes.baremetal_portgroup_extra, baremetal_fakes.baremetal_portgroup_mode, baremetal_fakes.baremetal_portgroup_name, baremetal_fakes.baremetal_uuid, baremetal_fakes.baremetal_portgroup_properties, baremetal_fakes.baremetal_portgroup_uuid, ) self.assertEqual(datalist, tuple(data)) def test_baremetal_portgroup_show_address(self): arglist = ['--address', baremetal_fakes.baremetal_portgroup_address] verifylist = [('address', True)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) args = {baremetal_fakes.baremetal_portgroup_address} self.baremetal_mock.portgroup.get_by_address.assert_called_with( *args, fields=None) def test_baremetal_portgroup_show_no_options(self): arglist = [] verifylist = [] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestBaremetalPortGroupList(TestBaremetalPortGroup): def setUp(self): super(TestBaremetalPortGroupList, self).setUp() self.baremetal_mock.portgroup.list.return_value = [ baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.PORTGROUP), loaded=True) ] self.cmd = baremetal_portgroup.ListBaremetalPortGroup(self.app, None) def test_baremetal_portgroup_list(self): arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) kwargs = { 'marker': None, 'limit': None} self.baremetal_mock.portgroup.list.assert_called_with(**kwargs) collist = ( "UUID", "Address", "Name") self.assertEqual(collist, columns) datalist = ((baremetal_fakes.baremetal_portgroup_uuid, baremetal_fakes.baremetal_portgroup_address, baremetal_fakes.baremetal_portgroup_name),) self.assertEqual(datalist, tuple(data)) def test_baremetal_portgroup_list_address(self): arglist = ['--address', baremetal_fakes.baremetal_portgroup_address] verifylist = [('address', baremetal_fakes.baremetal_portgroup_address)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) kwargs = { 'address': baremetal_fakes.baremetal_portgroup_address, 'marker': None, 'limit': None} self.baremetal_mock.portgroup.list.assert_called_with(**kwargs) collist = ( "UUID", "Address", "Name") self.assertEqual(collist, columns) datalist = ((baremetal_fakes.baremetal_portgroup_uuid, baremetal_fakes.baremetal_portgroup_address, baremetal_fakes.baremetal_portgroup_name),) self.assertEqual(datalist, tuple(data)) def test_baremetal_portgroup_list_node(self): arglist = ['--node', baremetal_fakes.baremetal_uuid] verifylist = [('node', baremetal_fakes.baremetal_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) kwargs = { 'node': baremetal_fakes.baremetal_uuid, 'marker': None, 'limit': None} self.baremetal_mock.portgroup.list.assert_called_with(**kwargs) collist = ( "UUID", "Address", "Name") self.assertEqual(collist, columns) datalist = ((baremetal_fakes.baremetal_portgroup_uuid, baremetal_fakes.baremetal_portgroup_address, baremetal_fakes.baremetal_portgroup_name),) self.assertEqual(datalist, tuple(data)) def test_baremetal_portgroup_list_long(self): arglist = ['--long'] verifylist = [('detail', True)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) kwargs = { 'detail': True, 'marker': None, 'limit': None, } self.baremetal_mock.portgroup.list.assert_called_with(**kwargs) collist = ('UUID', 'Address', 'Created At', 'Extra', 'Standalone Ports Supported', 'Node UUID', 'Name', 'Updated At', 'Internal Info', 'Mode', 'Properties') self.assertEqual(collist, columns) datalist = ((baremetal_fakes.baremetal_portgroup_uuid, baremetal_fakes.baremetal_portgroup_address, '', baremetal_fakes.baremetal_portgroup_extra, '', baremetal_fakes.baremetal_uuid, baremetal_fakes.baremetal_portgroup_name, '', '', baremetal_fakes.baremetal_portgroup_mode, baremetal_fakes.baremetal_portgroup_properties),) self.assertEqual(datalist, tuple(data)) def test_baremetal_portgroup_list_fields(self): arglist = ['--fields', 'uuid', 'address'] verifylist = [('fields', [['uuid', 'address']])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) kwargs = { 'marker': None, 'limit': None, 'detail': False, 'fields': ('uuid', 'address') } self.baremetal_mock.portgroup.list.assert_called_with(**kwargs) def test_baremetal_portgroup_list_fields_multiple(self): arglist = ['--fields', 'uuid', 'address', '--fields', 'extra'] verifylist = [('fields', [['uuid', 'address'], ['extra']])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) kwargs = { 'marker': None, 'limit': None, 'detail': False, 'fields': ('uuid', 'address', 'extra') } self.baremetal_mock.portgroup.list.assert_called_with(**kwargs) def test_baremetal_portgroup_list_invalid_fields(self): arglist = ['--fields', 'uuid', 'invalid'] verifylist = [('fields', [['uuid', 'invalid']])] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestBaremetalPortGroupDelete(TestBaremetalPortGroup): def setUp(self): super(TestBaremetalPortGroupDelete, self).setUp() self.baremetal_mock.portgroup.get.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.PORTGROUP), loaded=True)) self.cmd = baremetal_portgroup.DeleteBaremetalPortGroup(self.app, None) def test_baremetal_portgroup_delete(self): arglist = [baremetal_fakes.baremetal_portgroup_uuid] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) args = baremetal_fakes.baremetal_portgroup_uuid self.baremetal_mock.portgroup.delete.assert_called_with(args) def test_baremetal_portgroup_delete_multiple(self): arglist = [baremetal_fakes.baremetal_portgroup_uuid, baremetal_fakes.baremetal_portgroup_name] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) args = [baremetal_fakes.baremetal_portgroup_uuid, baremetal_fakes.baremetal_portgroup_name] self.baremetal_mock.portgroup.delete.has_calls( [mock.call(x) for x in args]) self.assertEqual(2, self.baremetal_mock.portgroup.delete.call_count) def test_baremetal_portgroup_delete_no_options(self): arglist = [] verifylist = [] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) class TestBaremetalPortGroupSet(TestBaremetalPortGroup): def setUp(self): super(TestBaremetalPortGroupSet, self).setUp() self.baremetal_mock.portgroup.update.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.PORTGROUP), loaded=True)) self.cmd = baremetal_portgroup.SetBaremetalPortGroup(self.app, None) def test_baremetal_portgroup_set_name(self): new_portgroup_name = 'New-Portgroup-name' arglist = [ baremetal_fakes.baremetal_portgroup_uuid, '--name', new_portgroup_name] verifylist = [ ('portgroup', baremetal_fakes.baremetal_portgroup_uuid), ('name', new_portgroup_name)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.portgroup.update.assert_called_once_with( baremetal_fakes.baremetal_portgroup_uuid, [{'path': '/name', 'value': new_portgroup_name, 'op': 'add'}]) def test_baremetal_portgroup_set_address(self): new_portgroup_address = '00:22:44:66:88:00' arglist = [ baremetal_fakes.baremetal_portgroup_uuid, '--address', new_portgroup_address] verifylist = [ ('portgroup', baremetal_fakes.baremetal_portgroup_uuid), ('address', new_portgroup_address)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.portgroup.update.assert_called_once_with( baremetal_fakes.baremetal_portgroup_uuid, [{'path': '/address', 'value': new_portgroup_address, 'op': 'add'}]) def test_baremetal_portgroup_set_mode(self): new_portgroup_mode = '802.3ad' arglist = [ baremetal_fakes.baremetal_portgroup_uuid, '--mode', new_portgroup_mode] verifylist = [ ('portgroup', baremetal_fakes.baremetal_portgroup_uuid), ('mode', new_portgroup_mode)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.portgroup.update.assert_called_once_with( baremetal_fakes.baremetal_portgroup_uuid, [{'path': '/mode', 'value': new_portgroup_mode, 'op': 'add'}]) def test_baremetal_portgroup_set_mode_int(self): new_portgroup_mode = '4' arglist = [ baremetal_fakes.baremetal_portgroup_uuid, '--mode', new_portgroup_mode] verifylist = [ ('portgroup', baremetal_fakes.baremetal_portgroup_uuid), ('mode', new_portgroup_mode)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.portgroup.update.assert_called_once_with( baremetal_fakes.baremetal_portgroup_uuid, [{'path': '/mode', 'value': new_portgroup_mode, 'op': 'add'}]) def test_baremetal_portgroup_set_node_uuid(self): new_node_uuid = 'nnnnnn-uuuuuuuu' arglist = [ baremetal_fakes.baremetal_portgroup_uuid, '--node', new_node_uuid] verifylist = [ ('portgroup', baremetal_fakes.baremetal_portgroup_uuid), ('node_uuid', new_node_uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.portgroup.update.assert_called_once_with( baremetal_fakes.baremetal_portgroup_uuid, [{'path': '/node_uuid', 'value': new_node_uuid, 'op': 'add'}]) def test_baremetal_portgroup_set_support_standalone_ports(self): arglist = [ baremetal_fakes.baremetal_portgroup_uuid, '--support-standalone-ports'] verifylist = [ ('portgroup', baremetal_fakes.baremetal_portgroup_uuid), ('support_standalone_ports', True)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.portgroup.update.assert_called_once_with( baremetal_fakes.baremetal_portgroup_uuid, [{'path': '/standalone_ports_supported', 'value': 'True', 'op': 'add'}]) def test_baremetal_portgroup_set_unsupport_standalone_ports(self): arglist = [ baremetal_fakes.baremetal_portgroup_uuid, '--unsupport-standalone-ports'] verifylist = [ ('portgroup', baremetal_fakes.baremetal_portgroup_uuid), ('unsupport_standalone_ports', True)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.portgroup.update.assert_called_once_with( baremetal_fakes.baremetal_portgroup_uuid, [{'path': '/standalone_ports_supported', 'value': 'False', 'op': 'add'}]) def test_baremetal_set_extra(self): arglist = ['portgroup', '--extra', 'foo=bar'] verifylist = [('portgroup', 'portgroup'), ('extra', ['foo=bar'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.portgroup.update.assert_called_once_with( 'portgroup', [{'path': '/extra/foo', 'value': 'bar', 'op': 'add'}]) def test_baremetal_portgroup_set_multiple_extras(self): arglist = ['portgroup', '--extra', 'key1=val1', '--extra', 'key2=val2'] verifylist = [('portgroup', 'portgroup'), ('extra', ['key1=val1', 'key2=val2'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.portgroup.update.assert_called_once_with( 'portgroup', [{'path': '/extra/key1', 'value': 'val1', 'op': 'add'}, {'path': '/extra/key2', 'value': 'val2', 'op': 'add'}]) def test_baremetal_portgroup_set_multiple_properties(self): arglist = ['portgroup', '--property', 'key3=val3', '--property', 'key4=val4'] verifylist = [('portgroup', 'portgroup'), ('properties', ['key3=val3', 'key4=val4'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.portgroup.update.assert_called_once_with( 'portgroup', [{'path': '/properties/key3', 'value': 'val3', 'op': 'add'}, {'path': '/properties/key4', 'value': 'val4', 'op': 'add'}]) def test_baremetal_portgroup_set_no_options(self): arglist = [] verifylist = [] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_portgroup_set_no_property(self): uuid = baremetal_fakes.baremetal_portgroup_uuid arglist = [uuid] verifylist = [('portgroup', uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.assertFalse(self.baremetal_mock.portgroup.update.called) class TestBaremetalPortGroupUnset(TestBaremetalPortGroup): def setUp(self): super(TestBaremetalPortGroupUnset, self).setUp() self.baremetal_mock.portgroup.update.return_value = ( baremetal_fakes.FakeBaremetalResource( None, copy.deepcopy(baremetal_fakes.PORTGROUP), loaded=True)) self.cmd = baremetal_portgroup.UnsetBaremetalPortGroup(self.app, None) def test_baremetal_portgroup_unset_extra(self): arglist = ['portgroup', '--extra', 'key1'] verifylist = [('portgroup', 'portgroup'), ('extra', ['key1'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.portgroup.update.assert_called_once_with( 'portgroup', [{'path': '/extra/key1', 'op': 'remove'}]) def test_baremetal_portgroup_unset_name(self): arglist = ['portgroup', '--name'] verifylist = [('name', True)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.portgroup.update.assert_called_once_with( 'portgroup', [{'path': '/name', 'op': 'remove'}]) def test_baremetal_portgroup_unset_address(self): arglist = ['portgroup', '--address'] verifylist = [('address', True)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.portgroup.update.assert_called_once_with( 'portgroup', [{'path': '/address', 'op': 'remove'}]) def test_baremetal_portgroup_unset_multiple_extras(self): arglist = ['portgroup', '--extra', 'key1', '--extra', 'key2'] verifylist = [('portgroup', 'portgroup'), ('extra', ['key1', 'key2'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.portgroup.update.assert_called_once_with( 'portgroup', [{'path': '/extra/key1', 'op': 'remove'}, {'path': '/extra/key2', 'op': 'remove'}]) def test_baremetal_portgroup_unset_multiple_properties(self): arglist = ['portgroup', '--property', 'key1', '--property', 'key2'] verifylist = [('portgroup', 'portgroup'), ('properties', ['key1', 'key2'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.baremetal_mock.portgroup.update.assert_called_once_with( 'portgroup', [{'path': '/properties/key1', 'op': 'remove'}, {'path': '/properties/key2', 'op': 'remove'}]) def test_baremetal_portgroup_unset_no_options(self): arglist = [] verifylist = [] self.assertRaises(osctestutils.ParserException, self.check_parser, self.cmd, arglist, verifylist) def test_baremetal_portgroup_unset_no_property(self): uuid = baremetal_fakes.baremetal_portgroup_uuid arglist = [uuid] verifylist = [('portgroup', uuid)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.assertFalse(self.baremetal_mock.portgroup.update.called) python-ironicclient-2.2.0/ironicclient/tests/unit/osc/v1/__init__.py0000666000175100017510000000000013232474343025563 0ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/tests/unit/osc/fakes.py0000666000175100017510000000346113232474343024605 0ustar zuulzuul00000000000000# # Copyright 2015 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import sys AUTH_TOKEN = "foobar" AUTH_URL = "http://0.0.0.0" API_VERSION = '1.6' class FakeApp(object): def __init__(self): _stdout = None self.client_manager = None self.stdin = sys.stdin self.stdout = _stdout or sys.stdout self.stderr = sys.stderr self.restapi = None class FakeClientManager(object): def __init__(self): self.identity = None self.auth_ref = None self.interface = 'public' self._region_name = 'RegionOne' self.session = 'fake session' self._api_version = {'baremetal': API_VERSION} class FakeResource(object): def __init__(self, manager, info, loaded=False): self.__name__ = type(self).__name__ self.manager = manager self._info = info self._add_details(info) self._loaded = loaded def _add_details(self, info): for (k, v) in info.items(): setattr(self, k, v) def __repr__(self): reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and k != 'manager') info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) return "<%s %s>" % (self.__class__.__name__, info) python-ironicclient-2.2.0/ironicclient/tests/unit/osc/test_plugin.py0000666000175100017510000001612413232474343026051 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 argparse import mock import testtools from ironicclient.osc import plugin from ironicclient.tests.unit.osc import fakes from ironicclient.v1 import client class MakeClientTest(testtools.TestCase): @mock.patch.object(plugin, 'OS_BAREMETAL_API_LATEST', new=False) @mock.patch.object(client, 'Client', autospec=True) def test_make_client_explicit_version(self, mock_client): instance = fakes.FakeClientManager() instance.get_endpoint_for_service_type = mock.Mock( return_value='endpoint') plugin.make_client(instance) mock_client.assert_called_once_with( os_ironic_api_version=fakes.API_VERSION, allow_api_version_downgrade=False, session=instance.session, region_name=instance._region_name, endpoint='endpoint') instance.get_endpoint_for_service_type.assert_called_once_with( 'baremetal', region_name=instance._region_name, interface=instance.interface) @mock.patch.object(plugin, 'OS_BAREMETAL_API_LATEST', new=True) @mock.patch.object(client, 'Client', autospec=True) def test_make_client_latest(self, mock_client): instance = fakes.FakeClientManager() instance.get_endpoint_for_service_type = mock.Mock( return_value='endpoint') instance._api_version = {'baremetal': plugin.LATEST_VERSION} plugin.make_client(instance) mock_client.assert_called_once_with( # NOTE(dtantsur): "latest" is changed to an actual version before # make_client is called. os_ironic_api_version=plugin.LATEST_VERSION, allow_api_version_downgrade=True, session=instance.session, region_name=instance._region_name, endpoint='endpoint') instance.get_endpoint_for_service_type.assert_called_once_with( 'baremetal', region_name=instance._region_name, interface=instance.interface) @mock.patch.object(plugin, 'OS_BAREMETAL_API_LATEST', new=False) @mock.patch.object(client, 'Client', autospec=True) def test_make_client_v1(self, mock_client): instance = fakes.FakeClientManager() instance.get_endpoint_for_service_type = mock.Mock( return_value='endpoint') instance._api_version = {'baremetal': '1'} plugin.make_client(instance) mock_client.assert_called_once_with( os_ironic_api_version=plugin.LATEST_VERSION, allow_api_version_downgrade=True, session=instance.session, region_name=instance._region_name, endpoint='endpoint') instance.get_endpoint_for_service_type.assert_called_once_with( 'baremetal', region_name=instance._region_name, interface=instance.interface) @mock.patch.object(plugin, 'OS_BAREMETAL_API_LATEST', new=True) @mock.patch.object(argparse.ArgumentParser, 'add_argument', autospec=True) class BuildOptionParserTest(testtools.TestCase): @mock.patch.object(plugin.utils, 'env', lambda x: None) def test_build_option_parser(self, mock_add_argument): parser = argparse.ArgumentParser() mock_add_argument.reset_mock() plugin.build_option_parser(parser) version_list = ['1'] + ['1.%d' % i for i in range( 1, plugin.LAST_KNOWN_API_VERSION + 1)] + ['latest'] mock_add_argument.assert_called_once_with( mock.ANY, '--os-baremetal-api-version', action=plugin.ReplaceLatestVersion, choices=version_list, default=plugin.LATEST_VERSION, help=mock.ANY, metavar='') self.assertTrue(plugin.OS_BAREMETAL_API_LATEST) @mock.patch.object(plugin.utils, 'env', lambda x: "latest") def test_build_option_parser_env_latest(self, mock_add_argument): parser = argparse.ArgumentParser() mock_add_argument.reset_mock() plugin.build_option_parser(parser) version_list = ['1'] + ['1.%d' % i for i in range( 1, plugin.LAST_KNOWN_API_VERSION + 1)] + ['latest'] mock_add_argument.assert_called_once_with( mock.ANY, '--os-baremetal-api-version', action=plugin.ReplaceLatestVersion, choices=version_list, default=plugin.LATEST_VERSION, help=mock.ANY, metavar='') self.assertTrue(plugin.OS_BAREMETAL_API_LATEST) @mock.patch.object(plugin.utils, 'env', lambda x: "1.1") def test_build_option_parser_env(self, mock_add_argument): parser = argparse.ArgumentParser() mock_add_argument.reset_mock() plugin.build_option_parser(parser) version_list = ['1'] + ['1.%d' % i for i in range( 1, plugin.LAST_KNOWN_API_VERSION + 1)] + ['latest'] mock_add_argument.assert_called_once_with( mock.ANY, '--os-baremetal-api-version', action=plugin.ReplaceLatestVersion, choices=version_list, default='1.1', help=mock.ANY, metavar='') self.assertFalse(plugin.OS_BAREMETAL_API_LATEST) @mock.patch.object(plugin.utils, 'env', lambda x: None) class ReplaceLatestVersionTest(testtools.TestCase): @mock.patch.object(plugin, 'OS_BAREMETAL_API_LATEST', new=False) def test___call___latest(self): parser = argparse.ArgumentParser() plugin.build_option_parser(parser) namespace = argparse.Namespace() parser.parse_known_args(['--os-baremetal-api-version', 'latest'], namespace) self.assertEqual(plugin.LATEST_VERSION, namespace.os_baremetal_api_version) self.assertTrue(plugin.OS_BAREMETAL_API_LATEST) @mock.patch.object(plugin, 'OS_BAREMETAL_API_LATEST', new=True) def test___call___specific_version(self): parser = argparse.ArgumentParser() plugin.build_option_parser(parser) namespace = argparse.Namespace() parser.parse_known_args(['--os-baremetal-api-version', '1.4'], namespace) self.assertEqual('1.4', namespace.os_baremetal_api_version) self.assertFalse(plugin.OS_BAREMETAL_API_LATEST) @mock.patch.object(plugin, 'OS_BAREMETAL_API_LATEST', new=True) def test___call___default(self): parser = argparse.ArgumentParser() plugin.build_option_parser(parser) namespace = argparse.Namespace() parser.parse_known_args([], namespace) self.assertEqual(plugin.LATEST_VERSION, namespace.os_baremetal_api_version) self.assertTrue(plugin.OS_BAREMETAL_API_LATEST) python-ironicclient-2.2.0/ironicclient/tests/unit/osc/__init__.py0000666000175100017510000000000013232474343025235 0ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/tests/unit/test_exc.py0000666000175100017510000000505713232474343024551 0ustar zuulzuul00000000000000# Copyright 2015 Red Hat, Inc. # 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 mock from six.moves import http_client from ironicclient.common.apiclient import exceptions from ironicclient import exc from ironicclient.tests.unit import utils as test_utils @mock.patch.object(exceptions, 'from_response', autospec=True) class ExcTest(test_utils.BaseTestCase): def setUp(self): super(ExcTest, self).setUp() self.message = 'SpongeBob SquarePants' self.traceback = 'Foo Traceback' self.method = 'call_spongebob' self.url = 'http://foo.bar' self.expected_json = {'error': {'message': self.message, 'details': self.traceback}} def test_from_response(self, mock_apiclient): fake_response = mock.Mock(status_code=http_client.BAD_REQUEST) exc.from_response(fake_response, message=self.message, traceback=self.traceback, method=self.method, url=self.url) self.assertEqual(http_client.BAD_REQUEST, fake_response.status_code) self.assertEqual(self.expected_json, fake_response.json()) mock_apiclient.assert_called_once_with( fake_response, method=self.method, url=self.url) def test_from_response_status(self, mock_apiclient): fake_response = mock.Mock(status=http_client.BAD_REQUEST) fake_response.getheader.return_value = 'fake-header' delattr(fake_response, 'status_code') exc.from_response(fake_response, message=self.message, traceback=self.traceback, method=self.method, url=self.url) expected_header = {'Content-Type': 'fake-header'} self.assertEqual(expected_header, fake_response.headers) self.assertEqual(http_client.BAD_REQUEST, fake_response.status_code) self.assertEqual(self.expected_json, fake_response.json()) mock_apiclient.assert_called_once_with( fake_response, method=self.method, url=self.url) python-ironicclient-2.2.0/ironicclient/tests/unit/test_client.py0000666000175100017510000003666513232474343025261 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 mock from keystoneauth1 import loading as kaloading from ironicclient import client as iroclient from ironicclient.common import filecache from ironicclient.common import http from ironicclient import exc from ironicclient.tests.unit import utils from ironicclient.v1 import client as v1 class ClientTest(utils.BaseTestCase): def test_get_client_with_auth_token_ironic_url(self): kwargs = { 'ironic_url': 'http://ironic.example.org:6385/', 'os_auth_token': 'USER_AUTH_TOKEN', } client = iroclient.get_client('1', **kwargs) self.assertEqual('USER_AUTH_TOKEN', client.http_client.auth_token) self.assertEqual('http://ironic.example.org:6385/', client.http_client.endpoint) @mock.patch.object(filecache, 'retrieve_data', autospec=True) @mock.patch.object(kaloading.session, 'Session', autospec=True) @mock.patch.object(kaloading, 'get_plugin_loader', autospec=True) def _test_get_client(self, mock_ks_loader, mock_ks_session, mock_retrieve_data, version=None, auth='password', **kwargs): session = mock_ks_session.return_value.load_from_options.return_value session.get_endpoint.return_value = 'http://localhost:6385/v1/f14b4123' mock_ks_loader.return_value.load_from_options.return_value = 'auth' mock_retrieve_data.return_value = version client = iroclient.get_client('1', **kwargs) mock_ks_loader.assert_called_once_with(auth) mock_ks_session.return_value.load_from_options.assert_called_once_with( auth='auth', timeout=kwargs.get('timeout'), insecure=kwargs.get('insecure'), cert=kwargs.get('cert'), cacert=kwargs.get('cacert'), key=kwargs.get('key')) session.get_endpoint.assert_called_once_with( service_type=kwargs.get('os_service_type') or 'baremetal', interface=kwargs.get('os_endpoint_type') or 'publicURL', region_name=kwargs.get('os_region_name')) if 'os_ironic_api_version' in kwargs: # NOTE(TheJulia): This does not test the negotiation logic # as a request must be triggered in order for any verison # negotiation actions to occur. self.assertEqual(0, mock_retrieve_data.call_count) self.assertEqual(kwargs['os_ironic_api_version'], client.current_api_version) self.assertFalse(client.is_api_version_negotiated) else: mock_retrieve_data.assert_called_once_with( host='localhost', port='6385') self.assertEqual(version or v1.DEFAULT_VER, client.http_client.os_ironic_api_version) def test_get_client_no_auth_token(self): kwargs = { 'os_project_name': 'PROJECT_NAME', 'os_username': 'USERNAME', 'os_password': 'PASSWORD', 'os_auth_url': 'http://localhost:35357/v2.0', 'os_auth_token': '', } self._test_get_client(**kwargs) def test_get_client_service_and_endpoint_type_defaults(self): kwargs = { 'os_project_name': 'PROJECT_NAME', 'os_username': 'USERNAME', 'os_password': 'PASSWORD', 'os_auth_url': 'http://localhost:35357/v2.0', 'os_auth_token': '', 'os_service_type': '', 'os_endpoint_type': '' } self._test_get_client(**kwargs) def test_get_client_with_region_no_auth_token(self): kwargs = { 'os_project_name': 'PROJECT_NAME', 'os_username': 'USERNAME', 'os_password': 'PASSWORD', 'os_region_name': 'REGIONONE', 'os_auth_url': 'http://localhost:35357/v2.0', 'os_auth_token': '', } self._test_get_client(**kwargs) def test_get_client_no_url(self): kwargs = { 'os_project_name': 'PROJECT_NAME', 'os_username': 'USERNAME', 'os_password': 'PASSWORD', 'os_auth_url': '', } self.assertRaises(exc.AmbiguousAuthSystem, iroclient.get_client, '1', **kwargs) # test the alias as well to ensure backwards compatibility self.assertRaises(exc.AmbigiousAuthSystem, iroclient.get_client, '1', **kwargs) def test_get_client_incorrect_auth_params(self): kwargs = { 'os_project_name': 'PROJECT_NAME', 'os_username': 'USERNAME', 'os_auth_url': 'http://localhost:35357/v2.0', } self.assertRaises(exc.AmbiguousAuthSystem, iroclient.get_client, '1', **kwargs) def test_get_client_with_api_version_latest(self): kwargs = { 'os_project_name': 'PROJECT_NAME', 'os_username': 'USERNAME', 'os_password': 'PASSWORD', 'os_auth_url': 'http://localhost:35357/v2.0', 'os_auth_token': '', 'os_ironic_api_version': "latest", } self._test_get_client(**kwargs) def test_get_client_with_api_version_list(self): kwargs = { 'os_project_name': 'PROJECT_NAME', 'os_username': 'USERNAME', 'os_password': 'PASSWORD', 'os_auth_url': 'http://localhost:35357/v2.0', 'os_auth_token': '', 'os_ironic_api_version': ['1.1', '1.99'], } self._test_get_client(**kwargs) def test_get_client_with_api_version_numeric(self): kwargs = { 'os_project_name': 'PROJECT_NAME', 'os_username': 'USERNAME', 'os_password': 'PASSWORD', 'os_auth_url': 'http://localhost:35357/v2.0', 'os_auth_token': '', 'os_ironic_api_version': "1.4", } self._test_get_client(**kwargs) def test_get_client_default_version_set_cached(self): version = '1.3' # Make sure we don't coincidentally succeed self.assertNotEqual(v1.DEFAULT_VER, version) kwargs = { 'os_project_name': 'PROJECT_NAME', 'os_username': 'USERNAME', 'os_password': 'PASSWORD', 'os_auth_url': 'http://localhost:35357/v2.0', 'os_auth_token': '', } self._test_get_client(version=version, **kwargs) def test_get_client_with_auth_token(self): kwargs = { 'os_auth_url': 'http://localhost:35357/v2.0', 'os_auth_token': 'USER_AUTH_TOKEN', } self._test_get_client(auth='token', **kwargs) def test_get_client_with_region_name_auth_token(self): kwargs = { 'os_auth_url': 'http://localhost:35357/v2.0', 'os_region_name': 'REGIONONE', 'os_auth_token': 'USER_AUTH_TOKEN', } self._test_get_client(auth='token', **kwargs) def test_get_client_only_session_passed(self): session = mock.Mock() session.get_endpoint.return_value = 'http://localhost:35357/v2.0' kwargs = { 'session': session, } iroclient.get_client('1', **kwargs) session.get_endpoint.assert_called_once_with(service_type='baremetal', interface='publicURL', region_name=None) def test_get_client_incorrect_session_passed(self): session = mock.Mock() session.get_endpoint.side_effect = Exception('boo') kwargs = { 'session': session, } self.assertRaises(exc.AmbiguousAuthSystem, iroclient.get_client, '1', **kwargs) @mock.patch.object(kaloading.session, 'Session', autospec=True) @mock.patch.object(kaloading, 'get_plugin_loader', autospec=True) def _test_loader_arguments_passed_correctly( self, mock_ks_loader, mock_ks_session, passed_kwargs, expected_kwargs): session = mock_ks_session.return_value.load_from_options.return_value session.get_endpoint.return_value = 'http://localhost:6385/v1/f14b4123' mock_ks_loader.return_value.load_from_options.return_value = 'auth' iroclient.get_client('1', **passed_kwargs) mock_ks_loader.return_value.load_from_options.assert_called_once_with( **expected_kwargs) mock_ks_session.return_value.load_from_options.assert_called_once_with( auth='auth', timeout=passed_kwargs.get('timeout'), insecure=passed_kwargs.get('insecure'), cert=passed_kwargs.get('cert'), cacert=passed_kwargs.get('cacert'), key=passed_kwargs.get('key')) session.get_endpoint.assert_called_once_with( service_type=passed_kwargs.get('os_service_type') or 'baremetal', interface=passed_kwargs.get('os_endpoint_type') or 'publicURL', region_name=passed_kwargs.get('os_region_name')) def test_loader_arguments_token(self): passed_kwargs = { 'os_auth_url': 'http://localhost:35357/v3', 'os_region_name': 'REGIONONE', 'os_auth_token': 'USER_AUTH_TOKEN', } expected_kwargs = { 'auth_url': 'http://localhost:35357/v3', 'project_id': None, 'project_name': None, 'user_domain_id': None, 'user_domain_name': None, 'project_domain_id': None, 'project_domain_name': None, 'token': 'USER_AUTH_TOKEN' } self._test_loader_arguments_passed_correctly( passed_kwargs=passed_kwargs, expected_kwargs=expected_kwargs) def test_loader_arguments_password_tenant_name(self): passed_kwargs = { 'os_auth_url': 'http://localhost:35357/v3', 'os_region_name': 'REGIONONE', 'os_project_name': 'PROJECT', 'os_username': 'user', 'os_password': '1234', 'os_project_domain_id': 'DEFAULT', 'os_user_domain_id': 'DEFAULT' } expected_kwargs = { 'auth_url': 'http://localhost:35357/v3', 'project_id': None, 'project_name': 'PROJECT', 'user_domain_id': 'DEFAULT', 'user_domain_name': None, 'project_domain_id': 'DEFAULT', 'project_domain_name': None, 'username': 'user', 'password': '1234' } self._test_loader_arguments_passed_correctly( passed_kwargs=passed_kwargs, expected_kwargs=expected_kwargs) def test_loader_arguments_password_project_id(self): passed_kwargs = { 'os_auth_url': 'http://localhost:35357/v3', 'os_region_name': 'REGIONONE', 'os_project_id': '1000', 'os_username': 'user', 'os_password': '1234', 'os_project_domain_name': 'domain1', 'os_user_domain_name': 'domain1' } expected_kwargs = { 'auth_url': 'http://localhost:35357/v3', 'project_id': '1000', 'project_name': None, 'user_domain_id': None, 'user_domain_name': 'domain1', 'project_domain_id': None, 'project_domain_name': 'domain1', 'username': 'user', 'password': '1234' } self._test_loader_arguments_passed_correctly( passed_kwargs=passed_kwargs, expected_kwargs=expected_kwargs) @mock.patch.object(iroclient, 'Client', autospec=True) @mock.patch.object(kaloading.session, 'Session', autospec=True) def test_correct_arguments_passed_to_client_constructor_noauth_mode( self, mock_ks_session, mock_client): kwargs = { 'ironic_url': 'http://ironic.example.org:6385/', 'os_auth_token': 'USER_AUTH_TOKEN', 'os_ironic_api_version': 'latest', 'insecure': True, 'max_retries': 10, 'retry_interval': 10, 'os_cacert': 'data' } iroclient.get_client('1', **kwargs) mock_client.assert_called_once_with( '1', 'http://ironic.example.org:6385/', **{ 'os_ironic_api_version': 'latest', 'max_retries': 10, 'retry_interval': 10, 'token': 'USER_AUTH_TOKEN', 'insecure': True, 'ca_file': 'data', 'cert_file': None, 'key_file': None, 'timeout': None, 'session': None } ) self.assertFalse(mock_ks_session.called) @mock.patch.object(iroclient, 'Client', autospec=True) @mock.patch.object(kaloading.session, 'Session', autospec=True) def test_correct_arguments_passed_to_client_constructor_session_created( self, mock_ks_session, mock_client): session = mock_ks_session.return_value.load_from_options.return_value kwargs = { 'os_auth_url': 'http://localhost:35357/v3', 'os_region_name': 'REGIONONE', 'os_project_id': '1000', 'os_username': 'user', 'os_password': '1234', 'os_project_domain_name': 'domain1', 'os_user_domain_name': 'domain1' } iroclient.get_client('1', **kwargs) mock_client.assert_called_once_with( '1', session.get_endpoint.return_value, **{ 'os_ironic_api_version': None, 'max_retries': None, 'retry_interval': None, 'session': session, } ) @mock.patch.object(iroclient, 'Client', autospec=True) @mock.patch.object(kaloading.session, 'Session', autospec=True) def test_correct_arguments_passed_to_client_constructor_session_passed( self, mock_ks_session, mock_client): session = mock.Mock() kwargs = { 'session': session, } iroclient.get_client('1', **kwargs) mock_client.assert_called_once_with( '1', session.get_endpoint.return_value, **{ 'os_ironic_api_version': None, 'max_retries': None, 'retry_interval': None, 'session': session, } ) self.assertFalse(mock_ks_session.called) def test_safe_header_with_auth_token(self): (name, value) = ('X-Auth-Token', u'3b640e2e64d946ac8f55615aff221dc1') expected_header = (u'X-Auth-Token', '{SHA1}6de9fb3b0b89099030a54abfeb468e7b1b1f0f2b') client = http.HTTPClient('http://localhost/') header_redact = client._process_header(name, value) self.assertEqual(expected_header, header_redact) def test_safe_header_with_no_auth_token(self): name, value = ('Accept', 'application/json') header = ('Accept', 'application/json') client = http.HTTPClient('http://localhost/') header_redact = client._process_header(name, value) self.assertEqual(header, header_redact) python-ironicclient-2.2.0/ironicclient/tests/unit/test_import.py0000666000175100017510000000256113232474343025301 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 ironicclient.tests.unit import utils module_str = 'ironicclient' class ImportTest(utils.BaseTestCase): def check_exported_symbols(self, exported_symbols): self.assertIn('client', exported_symbols) self.assertIn('exc', exported_symbols) self.assertIn('exceptions', exported_symbols) def test_import_objects(self): module = __import__(module_str) exported_symbols = dir(module) self.check_exported_symbols(exported_symbols) def test_default_import(self): default_imports = __import__(module_str, globals(), locals(), ['*']) exported_symbols = dir(default_imports) self.check_exported_symbols(exported_symbols) def test_import__all__(self): module = __import__(module_str) self.check_exported_symbols(module.__all__) python-ironicclient-2.2.0/ironicclient/tests/unit/common/0000775000175100017510000000000013232474761023644 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/tests/unit/common/test_filecache.py0000666000175100017510000002031513232474343027157 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 os import dogpile.cache import mock from ironicclient.common import filecache from ironicclient.tests.unit import utils class FileCacheTest(utils.BaseTestCase): def test__build_key_ok(self): result = filecache._build_key('localhost', '5000') self.assertEqual('localhost:5000', result) def test__build_key_none(self): result = filecache._build_key(None, None) self.assertEqual('None:None', result) @mock.patch.object(filecache, 'CACHE', None) @mock.patch.object(os.environ, 'get', autospec=True) @mock.patch.object(os.path, 'exists', autospec=True) @mock.patch.object(os, 'makedirs', autospec=True) @mock.patch.object(dogpile.cache, 'make_region', autospec=True) def test__get_cache_mkdir(self, mock_makeregion, mock_makedirs, mock_exists, mock_get): cache_val = 6 # If not present in the env, get will return the defaulted value mock_get.return_value = filecache.DEFAULT_EXPIRY mock_exists.return_value = False cache_region = mock.Mock(spec=dogpile.cache.region.CacheRegion) cache_region.configure.return_value = cache_val mock_makeregion.return_value = cache_region self.assertEqual(cache_val, filecache._get_cache()) self.assertEqual(cache_val, filecache.CACHE) mock_exists.assert_called_once_with(filecache.CACHE_DIR) mock_makedirs.assert_called_once_with(filecache.CACHE_DIR) mock_get.assert_called_once_with(filecache.CACHE_EXPIRY_ENV_VAR, mock.ANY) cache_region.configure.assert_called_once_with( mock.ANY, arguments=mock.ANY, expiration_time=filecache.DEFAULT_EXPIRY) @mock.patch.object(filecache, 'CACHE', None) @mock.patch.object(os.environ, 'get', autospec=True) @mock.patch.object(os.path, 'exists', autospec=True) @mock.patch.object(os, 'makedirs', autospec=True) @mock.patch.object(dogpile.cache, 'make_region', autospec=True) def test__get_cache_expiry_set(self, mock_makeregion, mock_makedirs, mock_exists, mock_get): cache_val = 5643 cache_expiry = '78' mock_get.return_value = cache_expiry mock_exists.return_value = False cache_region = mock.Mock(spec=dogpile.cache.region.CacheRegion) cache_region.configure.return_value = cache_val mock_makeregion.return_value = cache_region self.assertEqual(cache_val, filecache._get_cache()) self.assertEqual(cache_val, filecache.CACHE) mock_get.assert_called_once_with(filecache.CACHE_EXPIRY_ENV_VAR, mock.ANY) cache_region.configure.assert_called_once_with( mock.ANY, arguments=mock.ANY, expiration_time=int(cache_expiry)) @mock.patch.object(filecache, 'CACHE', None) @mock.patch.object(filecache.LOG, 'warning', autospec=True) @mock.patch.object(os.environ, 'get', autospec=True) @mock.patch.object(os.path, 'exists', autospec=True) @mock.patch.object(os, 'makedirs', autospec=True) @mock.patch.object(dogpile.cache, 'make_region', autospec=True) def test__get_cache_expiry_set_invalid(self, mock_makeregion, mock_makedirs, mock_exists, mock_get, mock_log): cache_val = 5643 cache_expiry = 'Rollenhagen' mock_get.return_value = cache_expiry mock_exists.return_value = False cache_region = mock.Mock(spec=dogpile.cache.region.CacheRegion) cache_region.configure.return_value = cache_val mock_makeregion.return_value = cache_region self.assertEqual(cache_val, filecache._get_cache()) self.assertEqual(cache_val, filecache.CACHE) mock_get.assert_called_once_with(filecache.CACHE_EXPIRY_ENV_VAR, mock.ANY) cache_region.configure.assert_called_once_with( mock.ANY, arguments=mock.ANY, expiration_time=filecache.DEFAULT_EXPIRY) log_dict = {'curr_val': cache_expiry, 'default': filecache.DEFAULT_EXPIRY, 'env_var': filecache.CACHE_EXPIRY_ENV_VAR} mock_log.assert_called_once_with(mock.ANY, log_dict) @mock.patch.object(filecache, 'CACHE', 5552368) @mock.patch.object(os.path, 'exists', autospec=True) @mock.patch.object(os, 'makedirs', autospec=True) def test__get_cache_dir_already_exists(self, mock_makedirs, mock_exists): mock_exists.return_value = True self.assertEqual(5552368, filecache._get_cache()) self.assertEqual(5552368, filecache.CACHE) self.assertEqual(0, mock_exists.call_count) self.assertEqual(0, mock_makedirs.call_count) @mock.patch.object(dogpile.cache.region, 'CacheRegion', autospec=True) @mock.patch.object(filecache, '_get_cache', autospec=True) def test_save_data_ok(self, mock_get_cache, mock_cache): mock_get_cache.return_value = mock_cache host = 'fred' port = '1234' hostport = '%s:%s' % (host, port) data = 'some random data' filecache.save_data(host, port, data) mock_cache.set.assert_called_once_with(hostport, data) @mock.patch.object(os.path, 'isfile', autospec=True) @mock.patch.object(dogpile.cache.region, 'CacheRegion', autospec=True) @mock.patch.object(filecache, '_get_cache', autospec=True) def test_retrieve_data_ok(self, mock_get_cache, mock_cache, mock_isfile): s = 'spam' mock_isfile.return_value = True mock_cache.get.return_value = s mock_get_cache.return_value = mock_cache host = 'fred' port = '1234' hostport = '%s:%s' % (host, port) result = filecache.retrieve_data(host, port) mock_cache.get.assert_called_once_with(hostport, expiration_time=None) self.assertEqual(s, result) @mock.patch.object(os.path, 'isfile', autospec=True) @mock.patch.object(dogpile.cache.region, 'CacheRegion', autospec=True) @mock.patch.object(filecache, '_get_cache', autospec=True) def test_retrieve_data_ok_with_expiry(self, mock_get_cache, mock_cache, mock_isfile): s = 'spam' mock_isfile.return_value = True mock_cache.get.return_value = s mock_get_cache.return_value = mock_cache host = 'fred' port = '1234' expiry = '987' hostport = '%s:%s' % (host, port) result = filecache.retrieve_data(host, port, expiry) mock_cache.get.assert_called_once_with(hostport, expiration_time=expiry) self.assertEqual(s, result) @mock.patch.object(os.path, 'isfile', autospec=True) @mock.patch.object(dogpile.cache.region, 'CacheRegion', autospec=True) @mock.patch.object(filecache, '_get_cache', autospec=True) def test_retrieve_data_not_found(self, mock_get_cache, mock_cache, mock_isfile): mock_isfile.return_value = True mock_cache.get.return_value = dogpile.cache.api.NO_VALUE mock_get_cache.return_value = mock_cache host = 'fred' port = '1234' hostport = '%s:%s' % (host, port) result = filecache.retrieve_data(host, port) mock_cache.get.assert_called_once_with(hostport, expiration_time=None) self.assertIsNone(result) @mock.patch.object(os.path, 'isfile', autospec=True) def test_retrieve_data_no_cache_file(self, mock_isfile): mock_isfile.return_value = False self.assertIsNone(filecache.retrieve_data(host='spam', port='eggs')) python-ironicclient-2.2.0/ironicclient/tests/unit/common/test_cliutils.py0000666000175100017510000006431613232474343027115 0ustar zuulzuul00000000000000# Copyright 2012 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import sys import fixtures import mock from oslotest import base as test_base import six from ironicclient.common import cliutils class ValidateArgsTest(test_base.BaseTestCase): def test_lambda_no_args(self): cliutils.validate_args(lambda: None) def _test_lambda_with_args(self, *args, **kwargs): cliutils.validate_args(lambda x, y: None, *args, **kwargs) def test_lambda_positional_args(self): self._test_lambda_with_args(1, 2) def test_lambda_kwargs(self): self._test_lambda_with_args(x=1, y=2) def test_lambda_mixed_kwargs(self): self._test_lambda_with_args(1, y=2) def test_lambda_missing_args1(self): self.assertRaises(cliutils.MissingArgs, self._test_lambda_with_args) def test_lambda_missing_args2(self): self.assertRaises(cliutils.MissingArgs, self._test_lambda_with_args, 1) def test_lambda_missing_args3(self): self.assertRaises(cliutils.MissingArgs, self._test_lambda_with_args, y=2) def _test_lambda_with_default(self, *args, **kwargs): cliutils.validate_args(lambda x, y, z=3: None, *args, **kwargs) def test_lambda_positional_args_with_default(self): self._test_lambda_with_default(1, 2) def test_lambda_kwargs_with_default(self): self._test_lambda_with_default(x=1, y=2) def test_lambda_mixed_kwargs_with_default(self): self._test_lambda_with_default(1, y=2) def test_lambda_positional_args_all_with_default(self): self._test_lambda_with_default(1, 2, 3) def test_lambda_kwargs_all_with_default(self): self._test_lambda_with_default(x=1, y=2, z=3) def test_lambda_mixed_kwargs_all_with_default(self): self._test_lambda_with_default(1, y=2, z=3) def test_lambda_with_default_missing_args1(self): self.assertRaises(cliutils.MissingArgs, self._test_lambda_with_default) def test_lambda_with_default_missing_args2(self): self.assertRaises(cliutils.MissingArgs, self._test_lambda_with_default, 1) def test_lambda_with_default_missing_args3(self): self.assertRaises(cliutils.MissingArgs, self._test_lambda_with_default, y=2) def test_lambda_with_default_missing_args4(self): self.assertRaises(cliutils.MissingArgs, self._test_lambda_with_default, y=2, z=3) def test_function_no_args(self): def func(): pass cliutils.validate_args(func) def _test_function_with_args(self, *args, **kwargs): def func(x, y): pass cliutils.validate_args(func, *args, **kwargs) def test_function_positional_args(self): self._test_function_with_args(1, 2) def test_function_kwargs(self): self._test_function_with_args(x=1, y=2) def test_function_mixed_kwargs(self): self._test_function_with_args(1, y=2) def test_function_missing_args1(self): self.assertRaises(cliutils.MissingArgs, self._test_function_with_args) def test_function_missing_args2(self): self.assertRaises(cliutils.MissingArgs, self._test_function_with_args, 1) def test_function_missing_args3(self): self.assertRaises(cliutils.MissingArgs, self._test_function_with_args, y=2) def _test_function_with_default(self, *args, **kwargs): def func(x, y, z=3): pass cliutils.validate_args(func, *args, **kwargs) def test_function_positional_args_with_default(self): self._test_function_with_default(1, 2) def test_function_kwargs_with_default(self): self._test_function_with_default(x=1, y=2) def test_function_mixed_kwargs_with_default(self): self._test_function_with_default(1, y=2) def test_function_positional_args_all_with_default(self): self._test_function_with_default(1, 2, 3) def test_function_kwargs_all_with_default(self): self._test_function_with_default(x=1, y=2, z=3) def test_function_mixed_kwargs_all_with_default(self): self._test_function_with_default(1, y=2, z=3) def test_function_with_default_missing_args1(self): self.assertRaises(cliutils.MissingArgs, self._test_function_with_default) def test_function_with_default_missing_args2(self): self.assertRaises(cliutils.MissingArgs, self._test_function_with_default, 1) def test_function_with_default_missing_args3(self): self.assertRaises(cliutils.MissingArgs, self._test_function_with_default, y=2) def test_function_with_default_missing_args4(self): self.assertRaises(cliutils.MissingArgs, self._test_function_with_default, y=2, z=3) def test_bound_method_no_args(self): class Foo(object): def bar(self): pass cliutils.validate_args(Foo().bar) def _test_bound_method_with_args(self, *args, **kwargs): class Foo(object): def bar(self, x, y): pass cliutils.validate_args(Foo().bar, *args, **kwargs) def test_bound_method_positional_args(self): self._test_bound_method_with_args(1, 2) def test_bound_method_kwargs(self): self._test_bound_method_with_args(x=1, y=2) def test_bound_method_mixed_kwargs(self): self._test_bound_method_with_args(1, y=2) def test_bound_method_missing_args1(self): self.assertRaises(cliutils.MissingArgs, self._test_bound_method_with_args) def test_bound_method_missing_args2(self): self.assertRaises(cliutils.MissingArgs, self._test_bound_method_with_args, 1) def test_bound_method_missing_args3(self): self.assertRaises(cliutils.MissingArgs, self._test_bound_method_with_args, y=2) def _test_bound_method_with_default(self, *args, **kwargs): class Foo(object): def bar(self, x, y, z=3): pass cliutils.validate_args(Foo().bar, *args, **kwargs) def test_bound_method_positional_args_with_default(self): self._test_bound_method_with_default(1, 2) def test_bound_method_kwargs_with_default(self): self._test_bound_method_with_default(x=1, y=2) def test_bound_method_mixed_kwargs_with_default(self): self._test_bound_method_with_default(1, y=2) def test_bound_method_positional_args_all_with_default(self): self._test_bound_method_with_default(1, 2, 3) def test_bound_method_kwargs_all_with_default(self): self._test_bound_method_with_default(x=1, y=2, z=3) def test_bound_method_mixed_kwargs_all_with_default(self): self._test_bound_method_with_default(1, y=2, z=3) def test_bound_method_with_default_missing_args1(self): self.assertRaises(cliutils.MissingArgs, self._test_bound_method_with_default) def test_bound_method_with_default_missing_args2(self): self.assertRaises(cliutils.MissingArgs, self._test_bound_method_with_default, 1) def test_bound_method_with_default_missing_args3(self): self.assertRaises(cliutils.MissingArgs, self._test_bound_method_with_default, y=2) def test_bound_method_with_default_missing_args4(self): self.assertRaises(cliutils.MissingArgs, self._test_bound_method_with_default, y=2, z=3) def test_unbound_method_no_args(self): class Foo(object): def bar(self): pass cliutils.validate_args(Foo.bar, Foo()) def _test_unbound_method_with_args(self, *args, **kwargs): class Foo(object): def bar(self, x, y): pass cliutils.validate_args(Foo.bar, Foo(), *args, **kwargs) def test_unbound_method_positional_args(self): self._test_unbound_method_with_args(1, 2) def test_unbound_method_kwargs(self): self._test_unbound_method_with_args(x=1, y=2) def test_unbound_method_mixed_kwargs(self): self._test_unbound_method_with_args(1, y=2) def test_unbound_method_missing_args1(self): self.assertRaises(cliutils.MissingArgs, self._test_unbound_method_with_args) def test_unbound_method_missing_args2(self): self.assertRaises(cliutils.MissingArgs, self._test_unbound_method_with_args, 1) def test_unbound_method_missing_args3(self): self.assertRaises(cliutils.MissingArgs, self._test_unbound_method_with_args, y=2) def _test_unbound_method_with_default(self, *args, **kwargs): class Foo(object): def bar(self, x, y, z=3): pass cliutils.validate_args(Foo.bar, Foo(), *args, **kwargs) def test_unbound_method_positional_args_with_default(self): self._test_unbound_method_with_default(1, 2) def test_unbound_method_kwargs_with_default(self): self._test_unbound_method_with_default(x=1, y=2) def test_unbound_method_mixed_kwargs_with_default(self): self._test_unbound_method_with_default(1, y=2) def test_unbound_method_with_default_missing_args1(self): self.assertRaises(cliutils.MissingArgs, self._test_unbound_method_with_default) def test_unbound_method_with_default_missing_args2(self): self.assertRaises(cliutils.MissingArgs, self._test_unbound_method_with_default, 1) def test_unbound_method_with_default_missing_args3(self): self.assertRaises(cliutils.MissingArgs, self._test_unbound_method_with_default, y=2) def test_unbound_method_with_default_missing_args4(self): self.assertRaises(cliutils.MissingArgs, self._test_unbound_method_with_default, y=2, z=3) def test_class_method_no_args(self): class Foo(object): @classmethod def bar(cls): pass cliutils.validate_args(Foo.bar) def _test_class_method_with_args(self, *args, **kwargs): class Foo(object): @classmethod def bar(cls, x, y): pass cliutils.validate_args(Foo.bar, *args, **kwargs) def test_class_method_positional_args(self): self._test_class_method_with_args(1, 2) def test_class_method_kwargs(self): self._test_class_method_with_args(x=1, y=2) def test_class_method_mixed_kwargs(self): self._test_class_method_with_args(1, y=2) def test_class_method_missing_args1(self): self.assertRaises(cliutils.MissingArgs, self._test_class_method_with_args) def test_class_method_missing_args2(self): self.assertRaises(cliutils.MissingArgs, self._test_class_method_with_args, 1) def test_class_method_missing_args3(self): self.assertRaises(cliutils.MissingArgs, self._test_class_method_with_args, y=2) def _test_class_method_with_default(self, *args, **kwargs): class Foo(object): @classmethod def bar(cls, x, y, z=3): pass cliutils.validate_args(Foo.bar, *args, **kwargs) def test_class_method_positional_args_with_default(self): self._test_class_method_with_default(1, 2) def test_class_method_kwargs_with_default(self): self._test_class_method_with_default(x=1, y=2) def test_class_method_mixed_kwargs_with_default(self): self._test_class_method_with_default(1, y=2) def test_class_method_with_default_missing_args1(self): self.assertRaises(cliutils.MissingArgs, self._test_class_method_with_default) def test_class_method_with_default_missing_args2(self): self.assertRaises(cliutils.MissingArgs, self._test_class_method_with_default, 1) def test_class_method_with_default_missing_args3(self): self.assertRaises(cliutils.MissingArgs, self._test_class_method_with_default, y=2) def test_class_method_with_default_missing_args4(self): self.assertRaises(cliutils.MissingArgs, self._test_class_method_with_default, y=2, z=3) def test_static_method_no_args(self): class Foo(object): @staticmethod def bar(): pass cliutils.validate_args(Foo.bar) def _test_static_method_with_args(self, *args, **kwargs): class Foo(object): @staticmethod def bar(x, y): pass cliutils.validate_args(Foo.bar, *args, **kwargs) def test_static_method_positional_args(self): self._test_static_method_with_args(1, 2) def test_static_method_kwargs(self): self._test_static_method_with_args(x=1, y=2) def test_static_method_mixed_kwargs(self): self._test_static_method_with_args(1, y=2) def test_static_method_missing_args1(self): self.assertRaises(cliutils.MissingArgs, self._test_static_method_with_args) def test_static_method_missing_args2(self): self.assertRaises(cliutils.MissingArgs, self._test_static_method_with_args, 1) def test_static_method_missing_args3(self): self.assertRaises(cliutils.MissingArgs, self._test_static_method_with_args, y=2) def _test_static_method_with_default(self, *args, **kwargs): class Foo(object): @staticmethod def bar(x, y, z=3): pass cliutils.validate_args(Foo.bar, *args, **kwargs) def test_static_method_positional_args_with_default(self): self._test_static_method_with_default(1, 2) def test_static_method_kwargs_with_default(self): self._test_static_method_with_default(x=1, y=2) def test_static_method_mixed_kwargs_with_default(self): self._test_static_method_with_default(1, y=2) def test_static_method_with_default_missing_args1(self): self.assertRaises(cliutils.MissingArgs, self._test_static_method_with_default) def test_static_method_with_default_missing_args2(self): self.assertRaises(cliutils.MissingArgs, self._test_static_method_with_default, 1) def test_static_method_with_default_missing_args3(self): self.assertRaises(cliutils.MissingArgs, self._test_static_method_with_default, y=2) def test_static_method_with_default_missing_args4(self): self.assertRaises(cliutils.MissingArgs, self._test_static_method_with_default, y=2, z=3) class _FakeResult(object): def __init__(self, name, value): self.name = name self.value = value class PrintResultTestCase(test_base.BaseTestCase): def setUp(self): super(PrintResultTestCase, self).setUp() self.mock_add_row = mock.MagicMock() self.useFixture(fixtures.MonkeyPatch( "prettytable.PrettyTable.add_row", self.mock_add_row)) self.mock_get_string = mock.MagicMock(return_value="") self.useFixture(fixtures.MonkeyPatch( "prettytable.PrettyTable.get_string", self.mock_get_string)) self.mock_init = mock.MagicMock(return_value=None) self.useFixture(fixtures.MonkeyPatch( "prettytable.PrettyTable.__init__", self.mock_init)) # NOTE(dtantsur): won't work with mocked __init__ self.useFixture(fixtures.MonkeyPatch( "prettytable.PrettyTable.align", mock.MagicMock())) def test_print_list_sort_by_str(self): objs = [_FakeResult("k1", 1), _FakeResult("k3", 2), _FakeResult("k2", 3)] cliutils.print_list(objs, ["Name", "Value"], sortby_index=0) self.assertEqual(self.mock_add_row.call_args_list, [mock.call(["k1", 1]), mock.call(["k3", 2]), mock.call(["k2", 3])]) self.mock_get_string.assert_called_with(sortby="Name") self.mock_init.assert_called_once_with(["Name", "Value"]) def test_print_list_sort_by_integer(self): objs = [_FakeResult("k1", 1), _FakeResult("k2", 3), _FakeResult("k3", 2)] cliutils.print_list(objs, ["Name", "Value"], sortby_index=1) self.assertEqual(self.mock_add_row.call_args_list, [mock.call(["k1", 1]), mock.call(["k2", 3]), mock.call(["k3", 2])]) self.mock_get_string.assert_called_with(sortby="Value") self.mock_init.assert_called_once_with(["Name", "Value"]) def test_print_list_sort_by_none(self): objs = [_FakeResult("k1", 1), _FakeResult("k3", 3), _FakeResult("k2", 2)] cliutils.print_list(objs, ["Name", "Value"], sortby_index=None) self.assertEqual(self.mock_add_row.call_args_list, [mock.call(["k1", 1]), mock.call(["k3", 3]), mock.call(["k2", 2])]) self.mock_get_string.assert_called_with() self.mock_init.assert_called_once_with(["Name", "Value"]) def test_print_list_dict(self): objs = [{'name': 'k1', 'value': 1}, {'name': 'k2', 'value': 2}] cliutils.print_list(objs, ["Name", "Value"], sortby_index=None) self.assertEqual(self.mock_add_row.call_args_list, [mock.call(["k1", 1]), mock.call(["k2", 2])]) self.mock_get_string.assert_called_with() self.mock_init.assert_called_once_with(["Name", "Value"]) def test_print_dict(self): cliutils.print_dict({"K": "k", "Key": "Value"}) cliutils.print_dict({"K": "k", "Key": "Long\\nValue"}) self.mock_add_row.assert_has_calls([ mock.call(["K", "k"]), mock.call(["Key", "Value"]), mock.call(["K", "k"]), mock.call(["Key", "Long"]), mock.call(["", "Value"])], any_order=True) def test_print_list_field_labels(self): objs = [_FakeResult("k1", 1), _FakeResult("k3", 3), _FakeResult("k2", 2)] field_labels = ["Another Name", "Another Value"] cliutils.print_list(objs, ["Name", "Value"], sortby_index=None, field_labels=field_labels) self.assertEqual(self.mock_add_row.call_args_list, [mock.call(["k1", 1]), mock.call(["k3", 3]), mock.call(["k2", 2])]) self.mock_init.assert_called_once_with(field_labels) def test_print_list_field_labels_sort(self): objs = [_FakeResult("k1", 1), _FakeResult("k3", 3), _FakeResult("k2", 2)] field_labels = ["Another Name", "Another Value"] cliutils.print_list(objs, ["Name", "Value"], sortby_index=0, field_labels=field_labels) self.assertEqual(self.mock_add_row.call_args_list, [mock.call(["k1", 1]), mock.call(["k3", 3]), mock.call(["k2", 2])]) self.mock_init.assert_called_once_with(field_labels) self.mock_get_string.assert_called_with(sortby="Another Name") def test_print_list_field_labels_too_many(self): objs = [_FakeResult("k1", 1), _FakeResult("k3", 3), _FakeResult("k2", 2)] field_labels = ["Another Name", "Another Value", "Redundant"] self.assertRaises(ValueError, cliutils.print_list, objs, ["Name", "Value"], sortby_index=None, field_labels=field_labels) class PrintResultStringTestCase(test_base.BaseTestCase): def test_print_list_string(self): objs = [_FakeResult("k1", 1)] field_labels = ["Another Name", "Another Value"] orig = sys.stdout sys.stdout = six.StringIO() cliutils.print_list(objs, ["Name", "Value"], sortby_index=0, field_labels=field_labels) out = sys.stdout.getvalue() sys.stdout.close() sys.stdout = orig expected = '''\ +--------------+---------------+ | Another Name | Another Value | +--------------+---------------+ | k1 | 1 | +--------------+---------------+ ''' self.assertEqual(expected, out) def test_print_list_string_json(self): objs = [_FakeResult("k1", 1)] field_labels = ["Another Name", "Another Value"] orig = sys.stdout sys.stdout = six.StringIO() cliutils.print_list(objs, ["Name", "Value"], sortby_index=0, field_labels=field_labels, json_flag=True) out = sys.stdout.getvalue() sys.stdout.close() sys.stdout = orig expected = [{"name": "k1", "value": 1}] self.assertEqual(expected, json.loads(out)) def test_print_dict_string(self): orig = sys.stdout sys.stdout = six.StringIO() cliutils.print_dict({"K": "k", "Key": "Value"}) out = sys.stdout.getvalue() sys.stdout.close() sys.stdout = orig expected = '''\ +----------+-------+ | Property | Value | +----------+-------+ | K | k | | Key | Value | +----------+-------+ ''' self.assertEqual(expected, out) def test_print_dict_string_json(self): orig = sys.stdout sys.stdout = six.StringIO() cliutils.print_dict({"K": "k", "Key": "Value"}, json_flag=True) out = sys.stdout.getvalue() sys.stdout.close() sys.stdout = orig expected = {"K": "k", "Key": "Value"} self.assertEqual(expected, json.loads(out)) def test_print_dict_string_custom_headers(self): orig = sys.stdout sys.stdout = six.StringIO() cliutils.print_dict({"K": "k", "Key": "Value"}, dict_property='Foo', dict_value='Bar') out = sys.stdout.getvalue() sys.stdout.close() sys.stdout = orig expected = '''\ +-----+-------+ | Foo | Bar | +-----+-------+ | K | k | | Key | Value | +-----+-------+ ''' self.assertEqual(expected, out) def test_print_dict_string_sorted(self): orig = sys.stdout sys.stdout = six.StringIO() cliutils.print_dict({"Foo": "k", "Bar": "Value"}) out = sys.stdout.getvalue() sys.stdout.close() sys.stdout = orig expected = '''\ +----------+-------+ | Property | Value | +----------+-------+ | Bar | Value | | Foo | k | +----------+-------+ ''' self.assertEqual(expected, out) def test_print_dict_negative_wrap(self): dct = {"K": "k", "Key": "Value"} self.assertRaises(ValueError, cliutils.print_dict, dct, wrap=-10) class DecoratorsTestCase(test_base.BaseTestCase): def test_arg(self): func_args = [("--image", ), ("--flavor", )] func_kwargs = [dict(default=None, metavar=""), dict(default=None, metavar="")] @cliutils.arg(*func_args[1], **func_kwargs[1]) @cliutils.arg(*func_args[0], **func_kwargs[0]) def dummy_func(): pass self.assertTrue(hasattr(dummy_func, "arguments")) self.assertEqual(len(dummy_func.arguments), 2) for args_kwargs in zip(func_args, func_kwargs): self.assertIn(args_kwargs, dummy_func.arguments) def test_unauthenticated(self): def dummy_func(): pass self.assertFalse(cliutils.isunauthenticated(dummy_func)) dummy_func = cliutils.unauthenticated(dummy_func) self.assertTrue(cliutils.isunauthenticated(dummy_func)) class EnvTestCase(test_base.BaseTestCase): def test_env(self): env = {"alpha": "a", "beta": "b"} self.useFixture(fixtures.MonkeyPatch("os.environ", env)) self.assertEqual(env["beta"], cliutils.env("beta")) self.assertEqual(env["beta"], cliutils.env("beta", "alpha")) self.assertEqual(env["alpha"], cliutils.env("alpha", "beta")) self.assertEqual(env["beta"], cliutils.env("gamma", "beta")) self.assertEqual("", cliutils.env("gamma")) self.assertEqual("c", cliutils.env("gamma", default="c")) class GetPasswordTestCase(test_base.BaseTestCase): def setUp(self): super(GetPasswordTestCase, self).setUp() class FakeFile(object): def isatty(self): return True self.useFixture(fixtures.MonkeyPatch("sys.stdin", FakeFile())) def test_get_password(self): self.useFixture(fixtures.MonkeyPatch("getpass.getpass", lambda prompt: "mellon")) self.assertEqual("mellon", cliutils.get_password()) def test_get_password_verify(self): env = {"OS_VERIFY_PASSWORD": "True"} self.useFixture(fixtures.MonkeyPatch("os.environ", env)) self.useFixture(fixtures.MonkeyPatch("getpass.getpass", lambda prompt: "mellon")) self.assertEqual("mellon", cliutils.get_password()) def test_get_password_verify_failure(self): env = {"OS_VERIFY_PASSWORD": "True"} self.useFixture(fixtures.MonkeyPatch("os.environ", env)) self.useFixture(fixtures.MonkeyPatch("getpass.getpass", lambda prompt: prompt)) self.assertIsNone(cliutils.get_password()) python-ironicclient-2.2.0/ironicclient/tests/unit/common/apiclient/0000775000175100017510000000000013232474761025614 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/tests/unit/common/apiclient/test_exceptions.py0000666000175100017510000001243013232474343031404 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack Foundation # 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. from oslotest import base as test_base import six from six.moves import http_client from ironicclient.common.apiclient import exceptions class FakeResponse(object): json_data = {} def __init__(self, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) def json(self): return self.json_data class ExceptionsArgsTest(test_base.BaseTestCase): def assert_exception(self, ex_cls, method, url, status_code, json_data, error_msg=None, error_details=None, check_description=True): ex = exceptions.from_response( FakeResponse(status_code=status_code, headers={"Content-Type": "application/json"}, json_data=json_data), method, url) self.assertIsInstance(ex, ex_cls) if check_description: expected_msg = error_msg or json_data["error"]["message"] expected_details = error_details or json_data["error"]["details"] self.assertEqual(expected_msg, ex.message) self.assertEqual(expected_details, ex.details) self.assertEqual(method, ex.method) self.assertEqual(url, ex.url) self.assertEqual(status_code, ex.http_status) def test_from_response_known(self): method = "GET" url = "/fake" status_code = http_client.BAD_REQUEST json_data = {"error": {"message": "fake message", "details": "fake details"}} self.assert_exception( exceptions.BadRequest, method, url, status_code, json_data) def test_from_response_unknown(self): method = "POST" url = "/fake-unknown" status_code = 499 json_data = {"error": {"message": "fake unknown message", "details": "fake unknown details"}} self.assert_exception( exceptions.HTTPClientError, method, url, status_code, json_data) status_code = 600 self.assert_exception( exceptions.HttpError, method, url, status_code, json_data) def test_from_response_non_openstack(self): method = "POST" url = "/fake-unknown" status_code = http_client.BAD_REQUEST json_data = {"alien": 123} self.assert_exception( exceptions.BadRequest, method, url, status_code, json_data, check_description=False) def test_from_response_with_different_response_format(self): method = "GET" url = "/fake-wsme" status_code = http_client.BAD_REQUEST json_data1 = {"error_message": {"debuginfo": None, "faultcode": "Client", "faultstring": "fake message"}} message = six.text_type( json_data1["error_message"]["faultstring"]) details = six.text_type(json_data1) self.assert_exception( exceptions.BadRequest, method, url, status_code, json_data1, message, details) json_data2 = {"badRequest": {"message": "fake message", "code": http_client.BAD_REQUEST}} message = six.text_type(json_data2["badRequest"]["message"]) details = six.text_type(json_data2) self.assert_exception( exceptions.BadRequest, method, url, status_code, json_data2, message, details) def test_from_response_with_text_response_format(self): method = "GET" url = "/fake-wsme" status_code = http_client.BAD_REQUEST text_data1 = "error_message: fake message" ex = exceptions.from_response( FakeResponse(status_code=status_code, headers={"Content-Type": "text/html"}, text=text_data1), method, url) self.assertIsInstance(ex, exceptions.BadRequest) self.assertEqual(text_data1, ex.details) self.assertEqual(method, ex.method) self.assertEqual(url, ex.url) self.assertEqual(status_code, ex.http_status) def test_from_response_with_text_response_format_with_no_body(self): method = "GET" url = "/fake-wsme" status_code = http_client.UNAUTHORIZED ex = exceptions.from_response( FakeResponse(status_code=status_code, headers={"Content-Type": "text/html"}), method, url) self.assertIsInstance(ex, exceptions.Unauthorized) self.assertEqual('', ex.details) self.assertEqual(method, ex.method) self.assertEqual(url, ex.url) self.assertEqual(status_code, ex.http_status) python-ironicclient-2.2.0/ironicclient/tests/unit/common/apiclient/test_base.py0000666000175100017510000001577613232474343030155 0ustar zuulzuul00000000000000# Copyright 2013 OpenStack Foundation # 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 mock from oslotest import base as test_base from ironicclient.common.apiclient import base class HumanResource(base.Resource): HUMAN_ID = True class HumanResourceManager(base.ManagerWithFind): resource_class = HumanResource def list(self): return self._list("/human_resources", "human_resources") def get(self, human_resource): return self._get( "/human_resources/%s" % base.getid(human_resource), "human_resource") def update(self, human_resource, name): body = { "human_resource": { "name": name, }, } return self._put( "/human_resources/%s" % base.getid(human_resource), body, "human_resource") class CrudResource(base.Resource): pass class CrudResourceManager(base.CrudManager): """Manager class for manipulating Identity crud_resources.""" resource_class = CrudResource collection_key = 'crud_resources' key = 'crud_resource' def get(self, crud_resource): return super(CrudResourceManager, self).get( crud_resource_id=base.getid(crud_resource)) class ResourceTest(test_base.BaseTestCase): def test_resource_repr(self): r = base.Resource(None, dict(foo="bar", baz="spam")) self.assertEqual("", repr(r)) def test_getid(self): class TmpObject(base.Resource): id = "4" self.assertEqual("4", base.getid(TmpObject(None, {}))) def test_human_id(self): r = base.Resource(None, {"name": "1"}) self.assertIsNone(r.human_id) r = HumanResource(None, {"name": "1"}) self.assertEqual("1", r.human_id) r = HumanResource(None, {"name": None}) self.assertIsNone(r.human_id) def test_two_resources_with_same_id_are_not_equal(self): # Two resources with same ID: never equal if their info is not equal r1 = base.Resource(None, {'id': 1, 'name': 'hi'}) r2 = base.Resource(None, {'id': 1, 'name': 'hello'}) self.assertNotEqual(r1, r2) def test_two_resources_with_same_id_and_info_are_equal(self): # Two resources with same ID: equal if their info is equal r1 = base.Resource(None, {'id': 1, 'name': 'hello'}) r2 = base.Resource(None, {'id': 1, 'name': 'hello'}) self.assertEqual(r1, r2) def test_two_resources_with_diff_type_are_not_equal(self): # Two resoruces of different types: never equal r1 = base.Resource(None, {'id': 1}) r2 = HumanResource(None, {'id': 1}) self.assertNotEqual(r1, r2) def test_two_resources_with_no_id_are_equal(self): # Two resources with no ID: equal if their info is equal r1 = base.Resource(None, {'name': 'joe', 'age': 12}) r2 = base.Resource(None, {'name': 'joe', 'age': 12}) self.assertEqual(r1, r2) class BaseManagerTestCase(test_base.BaseTestCase): def setUp(self): super(BaseManagerTestCase, self).setUp() self.response = mock.MagicMock() self.http_client = mock.MagicMock() self.http_client.get.return_value = self.response self.http_client.post.return_value = self.response self.manager = base.BaseManager(self.http_client) self.manager.resource_class = HumanResource def test_list(self): self.response.json.return_value = {'human_resources': [{'id': 42}]} expected = [HumanResource(self.manager, {'id': 42}, loaded=True)] result = self.manager._list("/human_resources", "human_resources") self.assertEqual(expected, result) def test_list_no_response_key(self): self.response.json.return_value = [{'id': 42}] expected = [HumanResource(self.manager, {'id': 42}, loaded=True)] result = self.manager._list("/human_resources") self.assertEqual(expected, result) def test_list_get(self): self.manager._list("/human_resources", "human_resources") self.manager.client.get.assert_called_with("/human_resources") def test_list_post(self): self.manager._list("/human_resources", "human_resources", json={'id': 42}) self.manager.client.post.assert_called_with("/human_resources", json={'id': 42}) def test_get(self): self.response.json.return_value = {'human_resources': {'id': 42}} expected = HumanResource(self.manager, {'id': 42}, loaded=True) result = self.manager._get("/human_resources/42", "human_resources") self.manager.client.get.assert_called_with("/human_resources/42") self.assertEqual(expected, result) def test_get_no_response_key(self): self.response.json.return_value = {'id': 42} expected = HumanResource(self.manager, {'id': 42}, loaded=True) result = self.manager._get("/human_resources/42") self.manager.client.get.assert_called_with("/human_resources/42") self.assertEqual(expected, result) def test_post(self): self.response.json.return_value = {'human_resources': {'id': 42}} expected = HumanResource(self.manager, {'id': 42}, loaded=True) result = self.manager._post("/human_resources", response_key="human_resources", json={'id': 42}) self.manager.client.post.assert_called_with("/human_resources", json={'id': 42}) self.assertEqual(expected, result) def test_post_return_raw(self): self.response.json.return_value = {'human_resources': {'id': 42}} result = self.manager._post("/human_resources", response_key="human_resources", json={'id': 42}, return_raw=True) self.manager.client.post.assert_called_with("/human_resources", json={'id': 42}) self.assertEqual({'id': 42}, result) def test_post_no_response_key(self): self.response.json.return_value = {'id': 42} expected = HumanResource(self.manager, {'id': 42}, loaded=True) result = self.manager._post("/human_resources", json={'id': 42}) self.manager.client.post.assert_called_with("/human_resources", json={'id': 42}) self.assertEqual(expected, result) python-ironicclient-2.2.0/ironicclient/tests/unit/common/apiclient/__init__.py0000666000175100017510000000000013232474343027711 0ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/tests/unit/common/test_http.py0000666000175100017510000011660313232474343026241 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack LLC. # 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 time import mock from oslo_serialization import jsonutils import requests import six from six.moves import http_client from keystoneauth1 import exceptions as kexc from ironicclient.common import filecache from ironicclient.common import http from ironicclient import exc from ironicclient.tests.unit import utils DEFAULT_TIMEOUT = 600 DEFAULT_HOST = 'localhost' DEFAULT_PORT = '1234' def _get_error_body(faultstring=None, debuginfo=None, description=None): if description: error_body = {'description': description} else: error_body = { 'faultstring': faultstring, 'debuginfo': debuginfo } raw_error_body = jsonutils.dump_as_bytes(error_body) body = {'error_message': raw_error_body} return jsonutils.dumps(body) def _session_client(**kwargs): return http.SessionClient(os_ironic_api_version='1.6', api_version_select_state='default', max_retries=5, retry_interval=2, auth=None, interface='publicURL', service_type='baremetal', region_name='', endpoint='http://%s:%s' % (DEFAULT_HOST, DEFAULT_PORT), **kwargs) class VersionNegotiationMixinTest(utils.BaseTestCase): def setUp(self): super(VersionNegotiationMixinTest, self).setUp() self.test_object = http.VersionNegotiationMixin() self.test_object.os_ironic_api_version = '1.6' self.test_object.api_version_select_state = 'default' self.test_object.endpoint = "http://localhost:1234" self.mock_mcu = mock.MagicMock() self.test_object._make_connection_url = self.mock_mcu self.response = utils.FakeResponse( {}, status=http_client.NOT_ACCEPTABLE) self.test_object.get_server = mock.MagicMock( return_value=('localhost', '1234')) def test__generic_parse_version_headers_has_headers(self): response = {'X-OpenStack-Ironic-API-Minimum-Version': '1.1', 'X-OpenStack-Ironic-API-Maximum-Version': '1.6', } expected = ('1.1', '1.6') result = self.test_object._generic_parse_version_headers(response.get) self.assertEqual(expected, result) def test__generic_parse_version_headers_missing_headers(self): response = {} expected = (None, None) result = self.test_object._generic_parse_version_headers(response.get) self.assertEqual(expected, result) @mock.patch.object(filecache, 'save_data', autospec=True) def test_negotiate_version_bad_state(self, mock_save_data): # Test if bad api_version_select_state value self.test_object.api_version_select_state = 'word of the day: augur' self.assertRaises( RuntimeError, self.test_object.negotiate_version, None, None) self.assertEqual(0, mock_save_data.call_count) @mock.patch.object(filecache, 'save_data', autospec=True) @mock.patch.object(http.VersionNegotiationMixin, '_parse_version_headers', autospec=True) def test_negotiate_version_server_older(self, mock_pvh, mock_save_data): # Test newer client and older server latest_ver = '1.5' mock_pvh.return_value = ('1.1', latest_ver) mock_conn = mock.MagicMock() result = self.test_object.negotiate_version(mock_conn, self.response) self.assertEqual(latest_ver, result) self.assertEqual(1, mock_pvh.call_count) host, port = http.get_server(self.test_object.endpoint) mock_save_data.assert_called_once_with(host=host, port=port, data=latest_ver) @mock.patch.object(filecache, 'save_data', autospec=True) @mock.patch.object(http.VersionNegotiationMixin, '_parse_version_headers', autospec=True) def test_negotiate_version_server_newer(self, mock_pvh, mock_save_data): # Test newer server and older client mock_pvh.return_value = ('1.1', '1.10') mock_conn = mock.MagicMock() result = self.test_object.negotiate_version(mock_conn, self.response) self.assertEqual('1.6', result) self.assertEqual(1, mock_pvh.call_count) mock_save_data.assert_called_once_with(host=DEFAULT_HOST, port=DEFAULT_PORT, data='1.6') @mock.patch.object(filecache, 'save_data', autospec=True) @mock.patch.object(http.VersionNegotiationMixin, '_make_simple_request', autospec=True) @mock.patch.object(http.VersionNegotiationMixin, '_parse_version_headers', autospec=True) def test_negotiate_version_server_no_version_on_error( self, mock_pvh, mock_msr, mock_save_data): # Test older Ironic version which errored with no version number and # have to retry with simple get mock_pvh.side_effect = iter([(None, None), ('1.1', '1.2')]) mock_conn = mock.MagicMock() result = self.test_object.negotiate_version(mock_conn, self.response) self.assertEqual('1.2', result) self.assertTrue(mock_msr.called) self.assertEqual(2, mock_pvh.call_count) self.assertEqual(1, mock_save_data.call_count) @mock.patch.object(filecache, 'save_data', autospec=True) @mock.patch.object(http.VersionNegotiationMixin, '_parse_version_headers', autospec=True) def test_negotiate_version_server_explicit_too_high(self, mock_pvh, mock_save_data): # requested version is not supported because it is too large mock_pvh.return_value = ('1.1', '1.6') mock_conn = mock.MagicMock() self.test_object.api_version_select_state = 'user' self.test_object.os_ironic_api_version = '99.99' self.assertRaises( exc.UnsupportedVersion, self.test_object.negotiate_version, mock_conn, self.response) self.assertEqual(1, mock_pvh.call_count) self.assertEqual(0, mock_save_data.call_count) @mock.patch.object(filecache, 'save_data', autospec=True) @mock.patch.object(http.VersionNegotiationMixin, '_parse_version_headers', autospec=True) def test_negotiate_version_server_explicit_not_supported(self, mock_pvh, mock_save_data): # requested version is supported by the server but the server returned # 406 because the requested operation is not supported with the # requested version mock_pvh.return_value = ('1.1', '1.6') mock_conn = mock.MagicMock() self.test_object.api_version_select_state = 'negotiated' self.test_object.os_ironic_api_version = '1.5' self.assertRaises( exc.UnsupportedVersion, self.test_object.negotiate_version, mock_conn, self.response) self.assertEqual(1, mock_pvh.call_count) self.assertEqual(0, mock_save_data.call_count) @mock.patch.object(filecache, 'save_data', autospec=True) @mock.patch.object(http.VersionNegotiationMixin, '_parse_version_headers', autospec=True) def test_negotiate_version_strict_version_comparison(self, mock_pvh, mock_save_data): # Test version comparison with StrictVersion max_ver = '1.10' mock_pvh.return_value = ('1.2', max_ver) mock_conn = mock.MagicMock() self.test_object.os_ironic_api_version = '1.10' result = self.test_object.negotiate_version(mock_conn, self.response) self.assertEqual(max_ver, result) self.assertEqual(1, mock_pvh.call_count) host, port = http.get_server(self.test_object.endpoint) mock_save_data.assert_called_once_with(host=host, port=port, data=max_ver) @mock.patch.object(filecache, 'save_data', autospec=True) @mock.patch.object(http.VersionNegotiationMixin, '_make_simple_request', autospec=True) @mock.patch.object(http.VersionNegotiationMixin, '_parse_version_headers', autospec=True) def test_negotiate_version_server_user_latest( self, mock_pvh, mock_msr, mock_save_data): # have to retry with simple get mock_pvh.side_effect = iter([(None, None), ('1.1', '1.99')]) mock_conn = mock.MagicMock() self.test_object.api_version_select_state = 'user' self.test_object.os_ironic_api_version = 'latest' result = self.test_object.negotiate_version(mock_conn, None) self.assertEqual(http.LATEST_VERSION, result) self.assertEqual('negotiated', self.test_object.api_version_select_state) self.assertEqual(http.LATEST_VERSION, self.test_object.os_ironic_api_version) self.assertTrue(mock_msr.called) self.assertEqual(2, mock_pvh.call_count) self.assertEqual(1, mock_save_data.call_count) @mock.patch.object(filecache, 'save_data', autospec=True) @mock.patch.object(http.VersionNegotiationMixin, '_make_simple_request', autospec=True) @mock.patch.object(http.VersionNegotiationMixin, '_parse_version_headers', autospec=True) def test_negotiate_version_server_user_list( self, mock_pvh, mock_msr, mock_save_data): # have to retry with simple get mock_pvh.side_effect = iter([(None, None), ('1.1', '1.26')]) mock_conn = mock.MagicMock() self.test_object.api_version_select_state = 'user' self.test_object.os_ironic_api_version = ['1.1', '1.6', '1.25', '1.26', '1.26.1', '1.27', '1.30'] result = self.test_object.negotiate_version(mock_conn, self.response) self.assertEqual('1.26', result) self.assertEqual('negotiated', self.test_object.api_version_select_state) self.assertEqual('1.26', self.test_object.os_ironic_api_version) self.assertTrue(mock_msr.called) self.assertEqual(2, mock_pvh.call_count) self.assertEqual(1, mock_save_data.call_count) @mock.patch.object(filecache, 'save_data', autospec=True) @mock.patch.object(http.VersionNegotiationMixin, '_make_simple_request', autospec=True) @mock.patch.object(http.VersionNegotiationMixin, '_parse_version_headers', autospec=True) def test_negotiate_version_server_user_list_fails_nomatch( self, mock_pvh, mock_msr, mock_save_data): # have to retry with simple get mock_pvh.side_effect = iter([(None, None), ('1.2', '1.26')]) mock_conn = mock.MagicMock() self.test_object.api_version_select_state = 'user' self.test_object.os_ironic_api_version = ['1.39', '1.1'] self.assertRaises( exc.UnsupportedVersion, self.test_object.negotiate_version, mock_conn, self.response) self.assertEqual('user', self.test_object.api_version_select_state) self.assertEqual(['1.39', '1.1'], self.test_object.os_ironic_api_version) self.assertEqual(2, mock_pvh.call_count) self.assertEqual(0, mock_save_data.call_count) @mock.patch.object(filecache, 'save_data', autospec=True) @mock.patch.object(http.VersionNegotiationMixin, '_make_simple_request', autospec=True) @mock.patch.object(http.VersionNegotiationMixin, '_parse_version_headers', autospec=True) def test_negotiate_version_server_user_list_single_value( self, mock_pvh, mock_msr, mock_save_data): # have to retry with simple get mock_pvh.side_effect = iter([(None, None), ('1.1', '1.26')]) mock_conn = mock.MagicMock() self.test_object.api_version_select_state = 'user' # NOTE(TheJulia): Lets test this value explicitly because the # minor number is actually the same. self.test_object.os_ironic_api_version = ['1.01'] result = self.test_object.negotiate_version(mock_conn, None) self.assertEqual('1.1', result) self.assertEqual('negotiated', self.test_object.api_version_select_state) self.assertEqual('1.1', self.test_object.os_ironic_api_version) self.assertTrue(mock_msr.called) self.assertEqual(2, mock_pvh.call_count) self.assertEqual(1, mock_save_data.call_count) @mock.patch.object(filecache, 'save_data', autospec=True) @mock.patch.object(http.VersionNegotiationMixin, '_make_simple_request', autospec=True) @mock.patch.object(http.VersionNegotiationMixin, '_parse_version_headers', autospec=True) def test_negotiate_version_server_user_list_fails_latest( self, mock_pvh, mock_msr, mock_save_data): # have to retry with simple get mock_pvh.side_effect = iter([(None, None), ('1.1', '1.2')]) mock_conn = mock.MagicMock() self.test_object.api_version_select_state = 'user' self.test_object.os_ironic_api_version = ['1.01', 'latest'] self.assertRaises( ValueError, self.test_object.negotiate_version, mock_conn, self.response) self.assertEqual('user', self.test_object.api_version_select_state) self.assertEqual(['1.01', 'latest'], self.test_object.os_ironic_api_version) self.assertEqual(2, mock_pvh.call_count) self.assertEqual(0, mock_save_data.call_count) def test_get_server(self): host = 'ironic-host' port = '6385' endpoint = 'http://%s:%s/ironic/v1/' % (host, port) self.assertEqual((host, port), http.get_server(endpoint)) class HttpClientTest(utils.BaseTestCase): def test_url_generation_trailing_slash_in_base(self): client = http.HTTPClient('http://localhost/') url = client._make_connection_url('/v1/resources') self.assertEqual('http://localhost/v1/resources', url) def test_url_generation_without_trailing_slash_in_base(self): client = http.HTTPClient('http://localhost') url = client._make_connection_url('/v1/resources') self.assertEqual('http://localhost/v1/resources', url) def test_url_generation_without_prefix_slash_in_path(self): client = http.HTTPClient('http://localhost') url = client._make_connection_url('v1/resources') self.assertEqual('http://localhost/v1/resources', url) def test_server_https_request_with_application_octet_stream(self): client = http.HTTPClient('https://localhost/') client.session = utils.mockSession( {'Content-Type': 'application/octet-stream'}, "Body", version=1, status_code=http_client.OK) response, body = client.json_request('GET', '/v1/resources') self.assertEqual(client.session.request.return_value, response) self.assertIsNone(body) def test_server_exception_empty_body(self): error_body = _get_error_body() client = http.HTTPClient('http://localhost/') client.session = utils.mockSession( {'Content-Type': 'application/json'}, error_body, version=1, status_code=http_client.INTERNAL_SERVER_ERROR) self.assertRaises(exc.InternalServerError, client.json_request, 'GET', '/v1/resources') def test_server_exception_msg_only(self): error_msg = 'test error msg' error_body = _get_error_body(error_msg) client = http.HTTPClient('http://localhost/') client.session = utils.mockSession( {'Content-Type': 'application/json'}, error_body, version=1, status_code=http_client.INTERNAL_SERVER_ERROR) self.assertRaises(exc.InternalServerError, client.json_request, 'GET', '/v1/resources') def test_server_exception_description_only(self): error_msg = 'test error msg' error_body = _get_error_body(description=error_msg) client = http.HTTPClient('http://localhost/') client.session = utils.mockSession( {'Content-Type': 'application/json'}, error_body, version=1, status_code=http_client.BAD_REQUEST) self.assertRaisesRegex(exc.BadRequest, 'test error msg', client.json_request, 'GET', '/v1/resources') def test_server_https_request_ok(self): client = http.HTTPClient('https://localhost/') client.session = utils.mockSession( {'Content-Type': 'application/json'}, "Body", version=1, status_code=http_client.OK) client.json_request('GET', '/v1/resources') def test_server_https_empty_body(self): error_body = _get_error_body() client = http.HTTPClient('https://localhost/') client.session = utils.mockSession( {'Content-Type': 'application/json'}, error_body, version=1, status_code=http_client.INTERNAL_SERVER_ERROR) self.assertRaises(exc.InternalServerError, client.json_request, 'GET', '/v1/resources') def test_401_unauthorized_exception(self): error_body = _get_error_body() client = http.HTTPClient('http://localhost/') client.session = utils.mockSession( {'Content-Type': 'text/plain'}, error_body, version=1, status_code=http_client.UNAUTHORIZED) self.assertRaises(exc.Unauthorized, client.json_request, 'GET', '/v1/resources') def test_http_request_not_valid_request(self): client = http.HTTPClient('http://localhost/') client.session.request = mock.Mock( side_effect=http.requests.exceptions.InvalidSchema) self.assertRaises(exc.ValidationError, client._http_request, 'http://localhost/', 'GET') def test__parse_version_headers(self): # Test parsing of version headers from HTTPClient error_body = _get_error_body() expected_result = ('1.1', '1.6') client = http.HTTPClient('http://localhost/') fake_resp = utils.mockSessionResponse( {'X-OpenStack-Ironic-API-Minimum-Version': '1.1', 'X-OpenStack-Ironic-API-Maximum-Version': '1.6', 'Content-Type': 'text/plain', }, error_body, version=1, status_code=http_client.NOT_ACCEPTABLE) result = client._parse_version_headers(fake_resp) self.assertEqual(expected_result, result) @mock.patch.object(filecache, 'save_data', autospec=True) def test__http_request_client_fallback_fail(self, mock_save_data): # Test when fallback to a supported version fails host, port, latest_ver = 'localhost', '1234', '1.6' error_body = _get_error_body() client = http.HTTPClient('http://%s:%s/' % (host, port)) client.session = utils.mockSession( {'X-OpenStack-Ironic-API-Minimum-Version': '1.1', 'X-OpenStack-Ironic-API-Maximum-Version': latest_ver, 'content-type': 'text/plain', }, error_body, version=1, status_code=http_client.NOT_ACCEPTABLE) self.assertRaises( exc.UnsupportedVersion, client._http_request, '/v1/resources', 'GET') mock_save_data.assert_called_once_with(host=host, data=latest_ver, port=port) @mock.patch.object(http.VersionNegotiationMixin, 'negotiate_version', autospec=False) def test__http_request_client_fallback_success(self, mock_negotiate): # Test when fallback to a supported version succeeds mock_negotiate.return_value = '1.6' error_body = _get_error_body() bad_resp = utils.mockSessionResponse( {'X-OpenStack-Ironic-API-Minimum-Version': '1.1', 'X-OpenStack-Ironic-API-Maximum-Version': '1.6', 'content-type': 'text/plain', }, error_body, version=1, status_code=http_client.NOT_ACCEPTABLE) good_resp = utils.mockSessionResponse( {'X-OpenStack-Ironic-API-Minimum-Version': '1.1', 'X-OpenStack-Ironic-API-Maximum-Version': '1.6', 'content-type': 'text/plain', }, "We got some text", version=1, status_code=http_client.OK) client = http.HTTPClient('http://localhost/') with mock.patch.object(client, 'session', autospec=True) as mock_session: mock_session.request.side_effect = iter([bad_resp, good_resp]) response, body_iter = client._http_request('/v1/resources', 'GET') self.assertEqual(http_client.OK, response.status_code) self.assertEqual(1, mock_negotiate.call_count) @mock.patch.object(http.LOG, 'debug', autospec=True) def test_log_curl_request_mask_password(self, mock_log): client = http.HTTPClient('http://localhost/') kwargs = {'headers': {'foo-header': 'bar-header'}, 'body': '{"password": "foo"}'} client.log_curl_request('foo', '/v1/nodes', kwargs) expected_log = ("curl -i -X foo -H 'foo-header: bar-header' " "-d '{\"password\": \"***\"}' " "http://localhost/v1/nodes") mock_log.assert_called_once_with(expected_log) @mock.patch.object(http.LOG, 'debug', autospec=True) def test_log_http_response_mask_password(self, mock_log): client = http.HTTPClient('http://localhost/') fake_response = utils.FakeResponse({}, version=1, reason='foo', status=200) body = '{"password": "foo"}' client.log_http_response(fake_response, body=body) expected_log = ("\nHTTP/0.1 200 foo\n\n{\"password\": \"***\"}\n") mock_log.assert_called_once_with(expected_log) def test__https_init_ssl_args_insecure(self): client = http.HTTPClient('https://localhost/', insecure=True) self.assertEqual(False, client.session.verify) def test__https_init_ssl_args_secure(self): client = http.HTTPClient('https://localhost/', ca_file='test_ca', key_file='test_key', cert_file='test_cert') self.assertEqual('test_ca', client.session.verify) self.assertEqual(('test_cert', 'test_key'), client.session.cert) @mock.patch.object(http.LOG, 'debug', autospec=True) def test_log_curl_request_with_body_and_header(self, mock_log): client = http.HTTPClient('http://test') headers = {'header1': 'value1'} body = 'example body' client.log_curl_request('GET', '/v1/nodes', {'headers': headers, 'body': body}) self.assertTrue(mock_log.called) self.assertTrue(mock_log.call_args[0]) self.assertEqual("curl -i -X GET -H 'header1: value1'" " -d 'example body' http://test/v1/nodes", mock_log.call_args[0][0]) @mock.patch.object(http.LOG, 'debug', autospec=True) def test_log_curl_request_with_certs(self, mock_log): headers = {'header1': 'value1'} client = http.HTTPClient('https://test', key_file='key', cert_file='cert', cacert='cacert', token='fake-token') client.log_curl_request('GET', '/v1/test', {'headers': headers}) self.assertTrue(mock_log.called) self.assertTrue(mock_log.call_args[0]) self.assertEqual("curl -i -X GET -H 'header1: value1' " "--cert cert --key key https://test/v1/test", mock_log.call_args[0][0]) @mock.patch.object(http.LOG, 'debug', autospec=True) def test_log_curl_request_with_insecure_param(self, mock_log): headers = {'header1': 'value1'} http_client_object = http.HTTPClient('https://test', insecure=True, token='fake-token') http_client_object.log_curl_request('GET', '/v1/test', {'headers': headers}) self.assertTrue(mock_log.called) self.assertTrue(mock_log.call_args[0]) self.assertEqual("curl -i -X GET -H 'header1: value1' -k " "--cert None --key None https://test/v1/test", mock_log.call_args[0][0]) class SessionClientTest(utils.BaseTestCase): def test_server_exception_empty_body(self): error_body = _get_error_body() fake_session = utils.mockSession({'Content-Type': 'application/json'}, error_body, http_client.INTERNAL_SERVER_ERROR) client = _session_client(session=fake_session) self.assertRaises(exc.InternalServerError, client.json_request, 'GET', '/v1/resources') def test_server_exception_description_only(self): error_msg = 'test error msg' error_body = _get_error_body(description=error_msg) fake_session = utils.mockSession( {'Content-Type': 'application/json'}, error_body, status_code=http_client.BAD_REQUEST) client = _session_client(session=fake_session) self.assertRaisesRegex(exc.BadRequest, 'test error msg', client.json_request, 'GET', '/v1/resources') def test__parse_version_headers(self): # Test parsing of version headers from SessionClient fake_session = utils.mockSession( {'X-OpenStack-Ironic-API-Minimum-Version': '1.1', 'X-OpenStack-Ironic-API-Maximum-Version': '1.6', 'content-type': 'text/plain', }, None, http_client.HTTP_VERSION_NOT_SUPPORTED) expected_result = ('1.1', '1.6') client = _session_client(session=fake_session) result = client._parse_version_headers(fake_session.request()) self.assertEqual(expected_result, result) def _test_endpoint_override(self, endpoint): fake_session = utils.mockSession({'content-type': 'application/json'}, status_code=http_client.NO_CONTENT) request_mock = mock.Mock() fake_session.request = request_mock request_mock.return_value = utils.mockSessionResponse( headers={'content-type': 'application/json'}, status_code=http_client.NO_CONTENT) client = _session_client(session=fake_session, endpoint_override=endpoint) client.json_request('DELETE', '/v1/nodes/aa/maintenance') expected_args_dict = { 'headers': { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-OpenStack-Ironic-API-Version': '1.6' }, 'auth': None, 'user_agent': 'python-ironicclient', 'endpoint_filter': { 'interface': 'publicURL', 'service_type': 'baremetal', 'region_name': '' } } if isinstance(endpoint, six.string_types): trimmed = http._trim_endpoint_api_version(endpoint) expected_args_dict['endpoint_override'] = trimmed request_mock.assert_called_once_with( '/v1/nodes/aa/maintenance', 'DELETE', raise_exc=False, **expected_args_dict ) def test_endpoint_override(self): self._test_endpoint_override('http://1.0.0.1:6385') def test_endpoint_override_with_version(self): self._test_endpoint_override('http://1.0.0.1:6385/v1') def test_endpoint_override_not_valid(self): self._test_endpoint_override(True) def test_make_simple_request(self): session = mock.Mock(spec=['request']) client = _session_client(session=session, endpoint_override='http://127.0.0.1') res = client._make_simple_request(session, 'GET', 'url') session.request.assert_called_once_with( 'url', 'GET', raise_exc=False, endpoint_filter={ 'interface': 'publicURL', 'service_type': 'baremetal', 'region_name': '' }, user_agent=http.USER_AGENT) self.assertEqual(res, session.request.return_value) @mock.patch.object(time, 'sleep', lambda *_: None) class RetriesTestCase(utils.BaseTestCase): def test_http_no_retry(self): error_body = _get_error_body() bad_resp = utils.mockSessionResponse( {'Content-Type': 'text/plain'}, error_body, version=1, status_code=http_client.CONFLICT) client = http.HTTPClient('http://localhost/', max_retries=0) with mock.patch.object(client.session, 'request', autospec=True, return_value=bad_resp) as mock_request: self.assertRaises(exc.Conflict, client._http_request, '/v1/resources', 'GET') self.assertEqual(1, mock_request.call_count) def test_http_retry(self): error_body = _get_error_body() bad_resp = utils.mockSessionResponse( {'Content-Type': 'text/plain'}, error_body, version=1, status_code=http_client.CONFLICT) good_resp = utils.mockSessionResponse( {'Content-Type': 'text/plain'}, "meow", version=1, status_code=http_client.OK) client = http.HTTPClient('http://localhost/') with mock.patch.object(client, 'session', autospec=True) as mock_session: mock_session.request.side_effect = iter([bad_resp, good_resp]) response, body_iter = client._http_request('/v1/resources', 'GET') self.assertEqual(http_client.OK, response.status_code) self.assertEqual(2, mock_session.request.call_count) def test_http_retry_503(self): error_body = _get_error_body() bad_resp = utils.mockSessionResponse( {'Content-Type': 'text/plain'}, error_body, version=1, status_code=http_client.SERVICE_UNAVAILABLE) good_resp = utils.mockSessionResponse( {'Content-Type': 'text/plain'}, "meow", version=1, status_code=http_client.OK) client = http.HTTPClient('http://localhost/') with mock.patch.object(client, 'session', autospec=True) as mock_session: mock_session.request.side_effect = iter([bad_resp, good_resp]) response, body_iter = client._http_request('/v1/resources', 'GET') self.assertEqual(http_client.OK, response.status_code) self.assertEqual(2, mock_session.request.call_count) def test_http_retry_connection_refused(self): good_resp = utils.mockSessionResponse( {'content-type': 'text/plain'}, "meow", version=1, status_code=http_client.OK) client = http.HTTPClient('http://localhost/') with mock.patch.object(client, 'session', autospec=True) as mock_session: mock_session.request.side_effect = iter([exc.ConnectionRefused(), good_resp]) response, body_iter = client._http_request('/v1/resources', 'GET') self.assertEqual(http_client.OK, response.status_code) self.assertEqual(2, mock_session.request.call_count) def test_http_failed_retry(self): error_body = _get_error_body() bad_resp = utils.mockSessionResponse( {'content-type': 'text/plain'}, error_body, version=1, status_code=http_client.CONFLICT) client = http.HTTPClient('http://localhost/') with mock.patch.object(client, 'session', autospec=True) as mock_session: mock_session.request.return_value = bad_resp self.assertRaises(exc.Conflict, client._http_request, '/v1/resources', 'GET') self.assertEqual(http.DEFAULT_MAX_RETRIES + 1, mock_session.request.call_count) def test_http_max_retries_none(self): error_body = _get_error_body() bad_resp = utils.mockSessionResponse( {'content-type': 'text/plain'}, error_body, version=1, status_code=http_client.CONFLICT) client = http.HTTPClient('http://localhost/', max_retries=None) with mock.patch.object(client, 'session', autospec=True) as mock_session: mock_session.request.return_value = bad_resp self.assertRaises(exc.Conflict, client._http_request, '/v1/resources', 'GET') self.assertEqual(http.DEFAULT_MAX_RETRIES + 1, mock_session.request.call_count) def test_http_change_max_retries(self): error_body = _get_error_body() bad_resp = utils.mockSessionResponse( {'content-type': 'text/plain'}, error_body, version=1, status_code=http_client.CONFLICT) client = http.HTTPClient('http://localhost/', max_retries=http.DEFAULT_MAX_RETRIES + 1) with mock.patch.object(client, 'session', autospec=True) as mock_session: mock_session.request.return_value = bad_resp self.assertRaises(exc.Conflict, client._http_request, '/v1/resources', 'GET') self.assertEqual(http.DEFAULT_MAX_RETRIES + 2, mock_session.request.call_count) def test_session_retry(self): error_body = _get_error_body() fake_resp = utils.mockSessionResponse( {'Content-Type': 'application/json'}, error_body, http_client.CONFLICT) ok_resp = utils.mockSessionResponse( {'Content-Type': 'application/json'}, b"OK", http_client.OK) fake_session = mock.Mock(spec=requests.Session) fake_session.request.side_effect = iter((fake_resp, ok_resp)) client = _session_client(session=fake_session) client.json_request('GET', '/v1/resources') self.assertEqual(2, fake_session.request.call_count) def test_session_retry_503(self): error_body = _get_error_body() fake_resp = utils.mockSessionResponse( {'Content-Type': 'application/json'}, error_body, http_client.SERVICE_UNAVAILABLE) ok_resp = utils.mockSessionResponse( {'Content-Type': 'application/json'}, b"OK", http_client.OK) fake_session = mock.Mock(spec=requests.Session) fake_session.request.side_effect = iter((fake_resp, ok_resp)) client = _session_client(session=fake_session) client.json_request('GET', '/v1/resources') self.assertEqual(2, fake_session.request.call_count) def test_session_retry_connection_refused(self): ok_resp = utils.mockSessionResponse( {'Content-Type': 'application/json'}, b"OK", http_client.OK) fake_session = mock.Mock(spec=requests.Session) fake_session.request.side_effect = iter((exc.ConnectionRefused(), ok_resp)) client = _session_client(session=fake_session) client.json_request('GET', '/v1/resources') self.assertEqual(2, fake_session.request.call_count) def test_session_retry_retriable_connection_failure(self): ok_resp = utils.mockSessionResponse( {'Content-Type': 'application/json'}, b"OK", http_client.OK) fake_session = mock.Mock(spec=requests.Session) fake_session.request.side_effect = iter( (kexc.RetriableConnectionFailure(), ok_resp)) client = _session_client(session=fake_session) client.json_request('GET', '/v1/resources') self.assertEqual(2, fake_session.request.call_count) def test_session_retry_fail(self): error_body = _get_error_body() fake_resp = utils.mockSessionResponse( {'Content-Type': 'application/json'}, error_body, http_client.CONFLICT) fake_session = mock.Mock(spec=requests.Session) fake_session.request.return_value = fake_resp client = _session_client(session=fake_session) self.assertRaises(exc.Conflict, client.json_request, 'GET', '/v1/resources') self.assertEqual(http.DEFAULT_MAX_RETRIES + 1, fake_session.request.call_count) def test_session_max_retries_none(self): error_body = _get_error_body() fake_resp = utils.mockSessionResponse( {'Content-Type': 'application/json'}, error_body, http_client.CONFLICT) fake_session = mock.Mock(spec=requests.Session) fake_session.request.return_value = fake_resp client = _session_client(session=fake_session) client.conflict_max_retries = None self.assertRaises(exc.Conflict, client.json_request, 'GET', '/v1/resources') self.assertEqual(http.DEFAULT_MAX_RETRIES + 1, fake_session.request.call_count) def test_session_change_max_retries(self): error_body = _get_error_body() fake_resp = utils.mockSessionResponse( {'Content-Type': 'application/json'}, error_body, http_client.CONFLICT) fake_session = mock.Mock(spec=requests.Session) fake_session.request.return_value = fake_resp client = _session_client(session=fake_session) client.conflict_max_retries = http.DEFAULT_MAX_RETRIES + 1 self.assertRaises(exc.Conflict, client.json_request, 'GET', '/v1/resources') self.assertEqual(http.DEFAULT_MAX_RETRIES + 2, fake_session.request.call_count) python-ironicclient-2.2.0/ironicclient/tests/unit/common/test_base.py0000666000175100017510000001412413232474343026167 0ustar zuulzuul00000000000000# Copyright 2013 OpenStack Foundation # 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 copy import mock import testtools from ironicclient.common import base from ironicclient import exc from ironicclient.tests.unit import utils TESTABLE_RESOURCE = { 'uuid': '11111111-2222-3333-4444-555555555555', 'attribute1': '1', 'attribute2': '2', } CREATE_TESTABLE_RESOURCE = copy.deepcopy(TESTABLE_RESOURCE) del CREATE_TESTABLE_RESOURCE['uuid'] INVALID_ATTRIBUTE_TESTABLE_RESOURCE = { 'non-existent-attribute': 'blablabla', 'attribute1': '1', 'attribute2': '2', } UPDATED_TESTABLE_RESOURCE = copy.deepcopy(TESTABLE_RESOURCE) NEW_ATTRIBUTE_VALUE = 'brand-new-attribute-value' UPDATED_TESTABLE_RESOURCE['attribute1'] = NEW_ATTRIBUTE_VALUE fake_responses = { '/v1/testableresources': { 'GET': ( {}, {"testableresources": [TESTABLE_RESOURCE]}, ), 'POST': ( {}, CREATE_TESTABLE_RESOURCE, ), }, '/v1/testableresources/%s' % TESTABLE_RESOURCE['uuid']: { 'GET': ( {}, TESTABLE_RESOURCE, ), 'DELETE': ( {}, None, ), 'PATCH': ( {}, UPDATED_TESTABLE_RESOURCE, ), }, } class TestableResource(base.Resource): def __repr__(self): return "" % self._info class TestableManager(base.CreateManager): resource_class = TestableResource _creation_attributes = ['attribute1', 'attribute2'] _resource_name = 'testableresources' def _path(self, id=None): return ('/v1/testableresources/%s' % id if id else '/v1/testableresources') def get(self, testable_resource_id, fields=None): return self._get(resource_id=testable_resource_id, fields=fields) def delete(self, testable_resource_id): return self._delete(resource_id=testable_resource_id) def update(self, testable_resource_id, patch): return self._update(resource_id=testable_resource_id, patch=patch) class ManagerTestCase(testtools.TestCase): def setUp(self): super(ManagerTestCase, self).setUp() self.api = utils.FakeAPI(fake_responses) self.manager = TestableManager(self.api) def test_create(self): resource = self.manager.create(**CREATE_TESTABLE_RESOURCE) expect = [ ('POST', '/v1/testableresources', {}, CREATE_TESTABLE_RESOURCE), ] self.assertEqual(expect, self.api.calls) self.assertTrue(resource) self.assertIsInstance(resource, TestableResource) def test_create_with_invalid_attribute(self): self.assertRaisesRegex(exc.InvalidAttribute, "non-existent-attribute", self.manager.create, **INVALID_ATTRIBUTE_TESTABLE_RESOURCE) def test__get(self): resource_id = TESTABLE_RESOURCE['uuid'] resource = self.manager._get(resource_id) expect = [ ('GET', '/v1/testableresources/%s' % resource_id, {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(resource_id, resource.uuid) self.assertEqual(TESTABLE_RESOURCE['attribute1'], resource.attribute1) def test__get_invalid_resource_id_raises(self): resource_ids = [[], {}, False, '', 0, None, ()] for resource_id in resource_ids: self.assertRaises(exc.ValidationError, self.manager._get, resource_id=resource_id) def test__get_as_dict(self): resource_id = TESTABLE_RESOURCE['uuid'] resource = self.manager._get_as_dict(resource_id) expect = [ ('GET', '/v1/testableresources/%s' % resource_id, {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(TESTABLE_RESOURCE, resource) @mock.patch.object(base.Manager, '_get', autospec=True) def test__get_as_dict_empty(self, mock_get): mock_get.return_value = None resource_id = TESTABLE_RESOURCE['uuid'] resource = self.manager._get_as_dict(resource_id) mock_get.assert_called_once_with(mock.ANY, resource_id, fields=None) self.assertEqual({}, resource) def test_get(self): resource = self.manager.get(TESTABLE_RESOURCE['uuid']) expect = [ ('GET', '/v1/testableresources/%s' % TESTABLE_RESOURCE['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertEqual(TESTABLE_RESOURCE['uuid'], resource.uuid) self.assertEqual(TESTABLE_RESOURCE['attribute1'], resource.attribute1) def test_update(self): patch = {'op': 'replace', 'value': NEW_ATTRIBUTE_VALUE, 'path': '/attribute1'} resource = self.manager.update( testable_resource_id=TESTABLE_RESOURCE['uuid'], patch=patch ) expect = [ ('PATCH', '/v1/testableresources/%s' % TESTABLE_RESOURCE['uuid'], {}, patch), ] self.assertEqual(expect, self.api.calls) self.assertEqual(NEW_ATTRIBUTE_VALUE, resource.attribute1) def test_delete(self): resource = self.manager.delete( testable_resource_id=TESTABLE_RESOURCE['uuid'] ) expect = [ ('DELETE', '/v1/testableresources/%s' % TESTABLE_RESOURCE['uuid'], {}, None), ] self.assertEqual(expect, self.api.calls) self.assertIsNone(resource) python-ironicclient-2.2.0/ironicclient/tests/unit/common/test_utils.py0000666000175100017510000003600313232474343026415 0ustar zuulzuul00000000000000# Copyright 2013 OpenStack LLC. # 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 json import os import subprocess import sys import tempfile import mock import six.moves.builtins as __builtin__ from ironicclient.common import utils from ironicclient import exc from ironicclient.tests.unit import utils as test_utils class UtilsTest(test_utils.BaseTestCase): def test_key_value_pairs_to_dict(self): kv_list = ['str=foo', 'int=1', 'bool=true', 'list=[1, 2, 3]', 'dict={"foo": "bar"}'] d = utils.key_value_pairs_to_dict(kv_list) self.assertEqual( {'str': 'foo', 'int': 1, 'bool': True, 'list': [1, 2, 3], 'dict': {'foo': 'bar'}}, d) def test_key_value_pairs_to_dict_nothing(self): self.assertEqual({}, utils.key_value_pairs_to_dict(None)) self.assertEqual({}, utils.key_value_pairs_to_dict([])) def test_args_array_to_dict(self): my_args = { 'matching_metadata': ['str=foo', 'int=1', 'bool=true', 'list=[1, 2, 3]', 'dict={"foo": "bar"}'], 'other': 'value' } cleaned_dict = utils.args_array_to_dict(my_args, "matching_metadata") self.assertEqual({ 'matching_metadata': {'str': 'foo', 'int': 1, 'bool': True, 'list': [1, 2, 3], 'dict': {'foo': 'bar'}}, 'other': 'value' }, cleaned_dict) def test_args_array_to_patch(self): my_args = { 'attributes': ['str=foo', 'int=1', 'bool=true', 'list=[1, 2, 3]', 'dict={"foo": "bar"}'], 'op': 'add', } patch = utils.args_array_to_patch(my_args['op'], my_args['attributes']) self.assertEqual([{'op': 'add', 'value': 'foo', 'path': '/str'}, {'op': 'add', 'value': 1, 'path': '/int'}, {'op': 'add', 'value': True, 'path': '/bool'}, {'op': 'add', 'value': [1, 2, 3], 'path': '/list'}, {'op': 'add', 'value': {"foo": "bar"}, 'path': '/dict'}], patch) def test_args_array_to_patch_format_error(self): my_args = { 'attributes': ['foobar'], 'op': 'add', } self.assertRaises(exc.CommandError, utils.args_array_to_patch, my_args['op'], my_args['attributes']) def test_args_array_to_patch_remove(self): my_args = { 'attributes': ['/foo', 'extra/bar'], 'op': 'remove', } patch = utils.args_array_to_patch(my_args['op'], my_args['attributes']) self.assertEqual([{'op': 'remove', 'path': '/foo'}, {'op': 'remove', 'path': '/extra/bar'}], patch) def test_split_and_deserialize(self): ret = utils.split_and_deserialize('str=foo') self.assertEqual(('str', 'foo'), ret) ret = utils.split_and_deserialize('int=1') self.assertEqual(('int', 1), ret) ret = utils.split_and_deserialize('bool=false') self.assertEqual(('bool', False), ret) ret = utils.split_and_deserialize('list=[1, "foo", 2]') self.assertEqual(('list', [1, "foo", 2]), ret) ret = utils.split_and_deserialize('dict={"foo": 1}') self.assertEqual(('dict', {"foo": 1}), ret) ret = utils.split_and_deserialize('str_int="1"') self.assertEqual(('str_int', "1"), ret) def test_split_and_deserialize_fail(self): self.assertRaises(exc.CommandError, utils.split_and_deserialize, 'foo:bar') def test_bool_arg_value(self): self.assertTrue(utils.bool_argument_value('arg', 'y', strict=True)) self.assertTrue(utils.bool_argument_value('arg', 'TrUe', strict=True)) self.assertTrue(utils.bool_argument_value('arg', '1', strict=True)) self.assertTrue(utils.bool_argument_value('arg', 1, strict=True)) self.assertFalse(utils.bool_argument_value('arg', '0', strict=True)) self.assertFalse(utils.bool_argument_value('arg', 'No', strict=True)) self.assertRaises(exc.CommandError, utils.bool_argument_value, 'arg', 'ee', strict=True) self.assertFalse(utils.bool_argument_value('arg', 'ee', strict=False)) self.assertTrue(utils.bool_argument_value('arg', 'ee', strict=False, default=True)) # No check that default is a Boolean... self.assertEqual('foo', utils.bool_argument_value('arg', 'ee', strict=False, default='foo')) def test_check_for_invalid_fields(self): self.assertIsNone(utils.check_for_invalid_fields( ['a', 'b'], ['a', 'b', 'c'])) # 'd' is not a valid field self.assertRaises(exc.CommandError, utils.check_for_invalid_fields, ['a', 'd'], ['a', 'b', 'c']) def test_convert_list_props_to_comma_separated_strings(self): data = {'prop1': 'val1', 'prop2': ['item1', 'item2', 'item3']} result = utils.convert_list_props_to_comma_separated(data) self.assertEqual('val1', result['prop1']) self.assertEqual('item1, item2, item3', result['prop2']) def test_convert_list_props_to_comma_separated_mix(self): data = {'prop1': 'val1', 'prop2': [1, 2.5, 'item3']} result = utils.convert_list_props_to_comma_separated(data) self.assertEqual('val1', result['prop1']) self.assertEqual('1, 2.5, item3', result['prop2']) def test_convert_list_props_to_comma_separated_partial(self): data = {'prop1': [1, 2, 3], 'prop2': [1, 2.5, 'item3']} result = utils.convert_list_props_to_comma_separated( data, props=['prop2']) self.assertEqual([1, 2, 3], result['prop1']) self.assertEqual('1, 2.5, item3', result['prop2']) class CommonParamsForListTest(test_utils.BaseTestCase): def setUp(self): super(CommonParamsForListTest, self).setUp() self.args = mock.Mock(marker=None, limit=None, sort_key=None, sort_dir=None, detail=False, fields=None, spec=True) self.expected_params = {'detail': False} def test_nothing_set(self): self.assertEqual(self.expected_params, utils.common_params_for_list(self.args, [], [])) def test_marker_and_limit(self): self.args.marker = 'foo' self.args.limit = 42 self.expected_params.update({'marker': 'foo', 'limit': 42}) self.assertEqual(self.expected_params, utils.common_params_for_list(self.args, [], [])) def test_invalid_limit(self): self.args.limit = -42 self.assertRaises(exc.CommandError, utils.common_params_for_list, self.args, [], []) def test_sort_key_and_sort_dir(self): self.args.sort_key = 'field' self.args.sort_dir = 'desc' self.expected_params.update({'sort_key': 'field', 'sort_dir': 'desc'}) self.assertEqual(self.expected_params, utils.common_params_for_list(self.args, ['field'], [])) def test_sort_key_allows_label(self): self.args.sort_key = 'Label' self.expected_params.update({'sort_key': 'field'}) self.assertEqual(self.expected_params, utils.common_params_for_list(self.args, ['field', 'field2'], ['Label', 'Label2'])) def test_sort_key_invalid(self): self.args.sort_key = 'something' self.assertRaises(exc.CommandError, utils.common_params_for_list, self.args, ['field', 'field2'], []) def test_sort_dir_invalid(self): self.args.sort_dir = 'something' self.assertRaises(exc.CommandError, utils.common_params_for_list, self.args, [], []) def test_detail(self): self.args.detail = True self.expected_params['detail'] = True self.assertEqual(self.expected_params, utils.common_params_for_list(self.args, [], [])) def test_fields(self): self.args.fields = [['a', 'b', 'c']] self.expected_params.update({'fields': ['a', 'b', 'c']}) self.assertEqual(self.expected_params, utils.common_params_for_list(self.args, [], [])) class CommonFiltersTest(test_utils.BaseTestCase): def test_limit(self): result = utils.common_filters(limit=42) self.assertEqual(['limit=42'], result) def test_limit_0(self): result = utils.common_filters(limit=0) self.assertEqual([], result) def test_other(self): for key in ('marker', 'sort_key', 'sort_dir'): result = utils.common_filters(**{key: 'test'}) self.assertEqual(['%s=test' % key], result) def test_fields(self): result = utils.common_filters(fields=['a', 'b', 'c']) self.assertEqual(['fields=a,b,c'], result) @mock.patch.object(subprocess, 'Popen', autospec=True) class MakeConfigDriveTest(test_utils.BaseTestCase): def setUp(self): super(MakeConfigDriveTest, self).setUp() # expected genisoimage cmd self.genisoimage_cmd = ['genisoimage', '-o', mock.ANY, '-ldots', '-allow-lowercase', '-allow-multidot', '-l', '-publisher', 'ironicclient-configdrive 0.1', '-quiet', '-J', '-r', '-V', 'config-2', mock.ANY] def test_make_configdrive(self, mock_popen): fake_process = mock.Mock(returncode=0) fake_process.communicate.return_value = ('', '') mock_popen.return_value = fake_process with utils.tempdir() as dirname: utils.make_configdrive(dirname) mock_popen.assert_called_once_with(self.genisoimage_cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE) fake_process.communicate.assert_called_once_with() @mock.patch.object(os, 'access', autospec=True) def test_make_configdrive_non_readable_dir(self, mock_access, mock_popen): mock_access.return_value = False self.assertRaises(exc.CommandError, utils.make_configdrive, 'fake-dir') mock_access.assert_called_once_with('fake-dir', os.R_OK) self.assertFalse(mock_popen.called) @mock.patch.object(os, 'access', autospec=True) def test_make_configdrive_oserror(self, mock_access, mock_popen): mock_access.return_value = True mock_popen.side_effect = OSError('boom') self.assertRaises(exc.CommandError, utils.make_configdrive, 'fake-dir') mock_access.assert_called_once_with('fake-dir', os.R_OK) mock_popen.assert_called_once_with(self.genisoimage_cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE) @mock.patch.object(os, 'access', autospec=True) def test_make_configdrive_non_zero_returncode(self, mock_access, mock_popen): fake_process = mock.Mock(returncode=123) fake_process.communicate.return_value = ('', '') mock_popen.return_value = fake_process self.assertRaises(exc.CommandError, utils.make_configdrive, 'fake-dir') mock_access.assert_called_once_with('fake-dir', os.R_OK) mock_popen.assert_called_once_with(self.genisoimage_cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE) fake_process.communicate.assert_called_once_with() class GetFromStdinTest(test_utils.BaseTestCase): @mock.patch.object(sys, 'stdin', autospec=True) def test_get_from_stdin(self, mock_stdin): contents = '[{"step": "upgrade", "interface": "deploy"}]' mock_stdin.read.return_value = contents desc = 'something' info = utils.get_from_stdin(desc) self.assertEqual(info, contents) mock_stdin.read.assert_called_once_with() @mock.patch.object(sys, 'stdin', autospec=True) def test_get_from_stdin_fail(self, mock_stdin): mock_stdin.read.side_effect = IOError desc = 'something' self.assertRaises(exc.InvalidAttribute, utils.get_from_stdin, desc) mock_stdin.read.assert_called_once_with() class HandleJsonFileTest(test_utils.BaseTestCase): def test_handle_json_or_file_arg(self): cleansteps = '[{"step": "upgrade", "interface": "deploy"}]' steps = utils.handle_json_or_file_arg(cleansteps) self.assertEqual(json.loads(cleansteps), steps) def test_handle_json_or_file_arg_bad_json(self): cleansteps = 'foo' self.assertRaisesRegex(exc.InvalidAttribute, 'For JSON', utils.handle_json_or_file_arg, cleansteps) def test_handle_json_or_file_arg_file(self): contents = '[{"step": "upgrade", "interface": "deploy"}]' with tempfile.NamedTemporaryFile(mode='w') as f: f.write(contents) f.flush() steps = utils.handle_json_or_file_arg(f.name) self.assertEqual(json.loads(contents), steps) @mock.patch.object(__builtin__, 'open', autospec=True) def test_handle_json_or_file_arg_file_fail(self, mock_open): mock_file_object = mock.MagicMock() mock_file_handle = mock.MagicMock() mock_file_handle.__enter__.return_value = mock_file_object mock_open.return_value = mock_file_handle mock_file_object.read.side_effect = IOError with tempfile.NamedTemporaryFile(mode='w') as f: self.assertRaisesRegex(exc.InvalidAttribute, "from file", utils.handle_json_or_file_arg, f.name) mock_open.assert_called_once_with(f.name, 'r') mock_file_object.read.assert_called_once_with() python-ironicclient-2.2.0/ironicclient/tests/unit/common/__init__.py0000666000175100017510000000000013232474343025741 0ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/tests/unit/__init__.py0000666000175100017510000000000013232474343024451 0ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/tests/unit/test_shell.py0000666000175100017510000005110213232474343025071 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 re import sys import fixtures from keystoneauth1 import exceptions as keystone_exc from keystoneauth1 import fixture as ks_fixture import mock from oslo_utils import uuidutils import requests_mock import six import testtools from testtools import matchers from ironicclient import client from ironicclient.common.apiclient import exceptions from ironicclient.common import http from ironicclient import exc from ironicclient import shell as ironic_shell from ironicclient.tests.unit import utils BASE_URL = 'http://no.where:5000' V2_URL = BASE_URL + '/v2.0' V3_URL = BASE_URL + '/v3' FAKE_ENV = {'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_PROJECT_NAME': 'project_name', 'OS_AUTH_URL': V2_URL} FAKE_ENV_KEYSTONE_V2 = { 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_PROJECT_NAME': 'project_name', 'OS_AUTH_URL': V2_URL } FAKE_ENV_KEYSTONE_V3 = { 'OS_USERNAME': 'username', 'OS_PASSWORD': 'password', 'OS_PROJECT_NAME': 'project_name', 'OS_AUTH_URL': V3_URL, 'OS_USER_DOMAIN_ID': 'default', 'OS_PROJECT_DOMAIN_ID': 'default', } FAKE_ENV_KEYSTONE_V2_TOKEN = { 'OS_AUTH_TOKEN': 'admin_token', 'OS_PROJECT_NAME': 'project_name', 'OS_AUTH_URL': V2_URL } class ShellTest(utils.BaseTestCase): re_options = re.DOTALL | re.MULTILINE # Patch os.environ to avoid required auth info. def make_env(self, exclude=None, environ_dict=FAKE_ENV): env = dict((k, v) for k, v in environ_dict.items() if k != exclude) self.useFixture(fixtures.MonkeyPatch('os.environ', env)) def setUp(self): super(ShellTest, self).setUp() def shell(self, argstr): with mock.patch.object(sys, 'stdout', six.StringIO()): with mock.patch.object(sys, 'stderr', six.StringIO()): try: _shell = ironic_shell.IronicShell() _shell.main(argstr.split()) except SystemExit: exc_type, exc_value, exc_traceback = sys.exc_info() self.assertEqual(0, exc_value.code) finally: out = sys.stdout.getvalue() err = sys.stderr.getvalue() return out, err def test_help_unknown_command(self): self.assertRaises(exc.CommandError, self.shell, 'help foofoo') def test_help(self): required = [ '.*?^usage: ironic', '.*?^ +bash-completion', '.*?^See "ironic help COMMAND" ' 'for help on a specific command', ] for argstr in ['--help', 'help']: help_text = self.shell(argstr)[0] for r in required: self.assertThat(help_text, matchers.MatchesRegex(r, self.re_options)) def test_help_on_subcommand(self): required = [ ".*?^usage: ironic chassis-show", ".*?^Show detailed information about a chassis", ] argstrings = [ 'help chassis-show', ] for argstr in argstrings: help_text = self.shell(argstr)[0] for r in required: self.assertThat(help_text, matchers.MatchesRegex(r, self.re_options)) def test_required_args_on_node_create_help(self): required = [ ".*?^usage: ironic node-create", ".*?^Register a new node with the Ironic service", ".*?^Required arguments:", ] argstrings = [ 'help node-create', ] for argstr in argstrings: help_text = self.shell(argstr)[0] for r in required: self.assertThat(help_text, matchers.MatchesRegex(r, self.re_options)) def test_required_args_on_port_create_help(self): required = [ ".*?^usage: ironic port-create", ".*?^Create a new port", ".*?^Required arguments:", ] argstrings = [ 'help port-create', ] for argstr in argstrings: help_text = self.shell(argstr)[0] for r in required: self.assertThat(help_text, matchers.MatchesRegex(r, self.re_options)) def test_auth_param(self): self.make_env(exclude='OS_USERNAME') self.test_help() @mock.patch.object(client, 'get_client', autospec=True, side_effect=keystone_exc.ConnectFailure) @mock.patch('sys.stdin', side_effect=mock.MagicMock, autospec=True) @mock.patch('getpass.getpass', return_value='password', autospec=True) def test_password_prompted(self, mock_getpass, mock_stdin, mock_client): self.make_env(exclude='OS_PASSWORD') # We will get a ConnectFailure because there is no keystone. self.assertRaises(keystone_exc.ConnectFailure, self.shell, 'node-list') expected_kwargs = { 'ironic_url': '', 'os_auth_url': FAKE_ENV['OS_AUTH_URL'], 'os_tenant_id': '', 'os_tenant_name': '', 'os_username': FAKE_ENV['OS_USERNAME'], 'os_user_domain_id': '', 'os_user_domain_name': '', 'os_password': FAKE_ENV['OS_PASSWORD'], 'os_auth_token': '', 'os_project_id': '', 'os_project_name': FAKE_ENV['OS_PROJECT_NAME'], 'os_project_domain_id': '', 'os_project_domain_name': '', 'os_region_name': '', 'os_service_type': '', 'os_endpoint_type': '', 'os_cacert': None, 'os_cert': None, 'os_key': None, 'max_retries': http.DEFAULT_MAX_RETRIES, 'retry_interval': http.DEFAULT_RETRY_INTERVAL, 'os_ironic_api_version': ironic_shell.LATEST_VERSION, 'timeout': 600, 'insecure': False } mock_client.assert_called_once_with(1, **expected_kwargs) # Make sure we are actually prompted. mock_getpass.assert_called_with('OpenStack Password: ') @mock.patch.object(client, 'get_client', autospec=True, side_effect=keystone_exc.ConnectFailure) @mock.patch('getpass.getpass', return_value='password', autospec=True) def test_token_auth(self, mock_getpass, mock_client): self.make_env(environ_dict=FAKE_ENV_KEYSTONE_V2_TOKEN) # We will get a ConnectFailure because there is no keystone. self.assertRaises(keystone_exc.ConnectFailure, self.shell, 'node-list') expected_kwargs = { 'ironic_url': '', 'os_auth_url': FAKE_ENV_KEYSTONE_V2_TOKEN['OS_AUTH_URL'], 'os_tenant_id': '', 'os_tenant_name': '', 'os_username': '', 'os_user_domain_id': '', 'os_user_domain_name': '', 'os_password': '', 'os_auth_token': FAKE_ENV_KEYSTONE_V2_TOKEN['OS_AUTH_TOKEN'], 'os_project_id': '', 'os_project_name': FAKE_ENV_KEYSTONE_V2_TOKEN['OS_PROJECT_NAME'], 'os_project_domain_id': '', 'os_project_domain_name': '', 'os_region_name': '', 'os_service_type': '', 'os_endpoint_type': '', 'os_cacert': None, 'os_cert': None, 'os_key': None, 'max_retries': http.DEFAULT_MAX_RETRIES, 'retry_interval': http.DEFAULT_RETRY_INTERVAL, 'os_ironic_api_version': ironic_shell.LATEST_VERSION, 'timeout': 600, 'insecure': False } mock_client.assert_called_once_with(1, **expected_kwargs) self.assertFalse(mock_getpass.called) @mock.patch('sys.stdin', side_effect=mock.MagicMock, autospec=True) @mock.patch('getpass.getpass', side_effect=EOFError, autospec=True) def test_password_prompted_ctrlD(self, mock_getpass, mock_stdin): self.make_env(exclude='OS_PASSWORD') # We should get Command Error because we mock Ctl-D. self.assertRaises(exc.CommandError, self.shell, 'node-list') # Make sure we are actually prompted. mock_getpass.assert_called_with('OpenStack Password: ') @mock.patch('sys.stdin', autospec=True) def test_no_password_no_tty(self, mock_stdin): # delete the isatty attribute so that we do not get # prompted when manually running the tests del mock_stdin.isatty required = ('You must provide a password' ' via either --os-password, env[OS_PASSWORD],' ' or prompted response',) self.make_env(exclude='OS_PASSWORD') try: self.shell('node-list') except exc.CommandError as message: self.assertEqual(required, message.args) else: self.fail('CommandError not raised') def test_bash_completion(self): stdout = self.shell('bash-completion')[0] # just check we have some output required = [ '.*--driver_info', '.*--chassis_uuid', '.*help', '.*node-create', '.*chassis-create'] for r in required: self.assertThat(stdout, matchers.MatchesRegex(r, self.re_options)) def test_ironic_api_version(self): err = self.shell('--ironic-api-version 1.2 help')[1] self.assertIn('The "ironic" CLI is deprecated', err) err = self.shell('--ironic-api-version latest help')[1] self.assertIn('The "ironic" CLI is deprecated', err) err = self.shell('--ironic-api-version 1 help')[1] self.assertIn('The "ironic" CLI is deprecated', err) def test_invalid_ironic_api_version(self): self.assertRaises(exceptions.UnsupportedVersion, self.shell, '--ironic-api-version 0.8 help') self.assertRaises(exc.CommandError, self.shell, '--ironic-api-version 1.2.1 help') @mock.patch.object(client, 'get_client', autospec=True, side_effect=keystone_exc.ConnectFailure) def test_api_version_in_env(self, mock_client): env = dict(IRONIC_API_VERSION='1.10', **FAKE_ENV) self.make_env(environ_dict=env) # We will get a ConnectFailure because there is no keystone. self.assertRaises(keystone_exc.ConnectFailure, self.shell, 'node-list') expected_kwargs = { 'ironic_url': '', 'os_auth_url': FAKE_ENV['OS_AUTH_URL'], 'os_tenant_id': '', 'os_tenant_name': '', 'os_username': FAKE_ENV['OS_USERNAME'], 'os_user_domain_id': '', 'os_user_domain_name': '', 'os_password': FAKE_ENV['OS_PASSWORD'], 'os_auth_token': '', 'os_project_id': '', 'os_project_name': FAKE_ENV['OS_PROJECT_NAME'], 'os_project_domain_id': '', 'os_project_domain_name': '', 'os_region_name': '', 'os_service_type': '', 'os_endpoint_type': '', 'os_cacert': None, 'os_cert': None, 'os_key': None, 'max_retries': http.DEFAULT_MAX_RETRIES, 'retry_interval': http.DEFAULT_RETRY_INTERVAL, 'os_ironic_api_version': '1.10', 'timeout': 600, 'insecure': False } mock_client.assert_called_once_with(1, **expected_kwargs) @mock.patch.object(client, 'get_client', autospec=True, side_effect=keystone_exc.ConnectFailure) def test_api_version_v1_in_env(self, mock_client): env = dict(IRONIC_API_VERSION='1', **FAKE_ENV) self.make_env(environ_dict=env) # We will get a ConnectFailure because there is no keystone. self.assertRaises(keystone_exc.ConnectFailure, self.shell, 'node-list') expected_kwargs = { 'ironic_url': '', 'os_auth_url': FAKE_ENV['OS_AUTH_URL'], 'os_tenant_id': '', 'os_tenant_name': '', 'os_username': FAKE_ENV['OS_USERNAME'], 'os_user_domain_id': '', 'os_user_domain_name': '', 'os_password': FAKE_ENV['OS_PASSWORD'], 'os_auth_token': '', 'os_project_id': '', 'os_project_name': FAKE_ENV['OS_PROJECT_NAME'], 'os_project_domain_id': '', 'os_project_domain_name': '', 'os_region_name': '', 'os_service_type': '', 'os_endpoint_type': '', 'os_cacert': None, 'os_cert': None, 'os_key': None, 'max_retries': http.DEFAULT_MAX_RETRIES, 'retry_interval': http.DEFAULT_RETRY_INTERVAL, 'os_ironic_api_version': ironic_shell.LATEST_VERSION, 'timeout': 600, 'insecure': False } mock_client.assert_called_once_with(1, **expected_kwargs) @mock.patch.object(client, 'get_client', autospec=True, side_effect=keystone_exc.ConnectFailure) def test_api_version_in_args(self, mock_client): env = dict(IRONIC_API_VERSION='1.10', **FAKE_ENV) self.make_env(environ_dict=env) # We will get a ConnectFailure because there is no keystone. self.assertRaises(keystone_exc.ConnectFailure, self.shell, '--ironic-api-version 1.11 node-list') expected_kwargs = { 'ironic_url': '', 'os_auth_url': FAKE_ENV['OS_AUTH_URL'], 'os_tenant_id': '', 'os_tenant_name': '', 'os_username': FAKE_ENV['OS_USERNAME'], 'os_user_domain_id': '', 'os_user_domain_name': '', 'os_password': FAKE_ENV['OS_PASSWORD'], 'os_auth_token': '', 'os_project_id': '', 'os_project_name': FAKE_ENV['OS_PROJECT_NAME'], 'os_project_domain_id': '', 'os_project_domain_name': '', 'os_region_name': '', 'os_service_type': '', 'os_endpoint_type': '', 'os_cacert': None, 'os_cert': None, 'os_key': None, 'max_retries': http.DEFAULT_MAX_RETRIES, 'retry_interval': http.DEFAULT_RETRY_INTERVAL, 'os_ironic_api_version': '1.11', 'timeout': 600, 'insecure': False } mock_client.assert_called_once_with(1, **expected_kwargs) @mock.patch.object(client, 'get_client', autospec=True, side_effect=keystone_exc.ConnectFailure) def test_api_version_v1_in_args(self, mock_client): env = dict(IRONIC_API_VERSION='1.10', **FAKE_ENV) self.make_env(environ_dict=env) # We will get a ConnectFailure because there is no keystone. self.assertRaises(keystone_exc.ConnectFailure, self.shell, '--ironic-api-version 1 node-list') expected_kwargs = { 'ironic_url': '', 'os_auth_url': FAKE_ENV['OS_AUTH_URL'], 'os_tenant_id': '', 'os_tenant_name': '', 'os_username': FAKE_ENV['OS_USERNAME'], 'os_user_domain_id': '', 'os_user_domain_name': '', 'os_password': FAKE_ENV['OS_PASSWORD'], 'os_auth_token': '', 'os_project_id': '', 'os_project_name': FAKE_ENV['OS_PROJECT_NAME'], 'os_project_domain_id': '', 'os_project_domain_name': '', 'os_region_name': '', 'os_service_type': '', 'os_endpoint_type': '', 'os_cacert': None, 'os_cert': None, 'os_key': None, 'max_retries': http.DEFAULT_MAX_RETRIES, 'retry_interval': http.DEFAULT_RETRY_INTERVAL, 'os_ironic_api_version': ironic_shell.LATEST_VERSION, 'timeout': 600, 'insecure': False } mock_client.assert_called_once_with(1, **expected_kwargs) class TestCase(testtools.TestCase): def set_fake_env(self, fake_env): client_env = ('OS_USERNAME', 'OS_PASSWORD', 'OS_PROJECT_ID', 'OS_PROJECT_NAME', 'OS_AUTH_URL', 'OS_REGION_NAME', 'OS_AUTH_TOKEN', 'OS_NO_CLIENT_AUTH', 'OS_SERVICE_TYPE', 'OS_ENDPOINT_TYPE', 'OS_CACERT', 'OS_CERT', 'OS_KEY') for key in client_env: self.useFixture( fixtures.EnvironmentVariable(key, fake_env.get(key))) def register_keystone_v2_token_fixture(self, request_mocker): v2_token = ks_fixture.V2Token() service = v2_token.add_service('baremetal') service.add_endpoint('http://ironic.example.com', region='RegionOne') request_mocker.post('%s/tokens' % V2_URL, json=v2_token) def register_keystone_v3_token_fixture(self, request_mocker): v3_token = ks_fixture.V3Token() service = v3_token.add_service('baremetal') service.add_standard_endpoints(public='http://ironic.example.com') request_mocker.post( '%s/auth/tokens' % V3_URL, json=v3_token, headers={'X-Subject-Token': uuidutils.generate_uuid()}) def register_keystone_auth_fixture(self, request_mocker): self.register_keystone_v2_token_fixture(request_mocker) self.register_keystone_v3_token_fixture(request_mocker) request_mocker.get(V2_URL, json=ks_fixture.V2Discovery(V2_URL)) request_mocker.get(V3_URL, json=ks_fixture.V3Discovery(V3_URL)) request_mocker.get(BASE_URL, json=ks_fixture.DiscoveryList(BASE_URL)) class ShellTestNoMox(TestCase): def setUp(self): super(ShellTestNoMox, self).setUp() self.set_fake_env(FAKE_ENV_KEYSTONE_V2) def shell(self, argstr): orig = sys.stdout try: sys.stdout = six.StringIO() _shell = ironic_shell.IronicShell() _shell.main(argstr.split()) self.subcommands = _shell.subcommands.keys() except SystemExit: exc_type, exc_value, exc_traceback = sys.exc_info() self.assertEqual(0, exc_value.code) finally: out = sys.stdout.getvalue() sys.stdout.close() sys.stdout = orig return out @requests_mock.mock() def test_node_list(self, request_mocker): self.register_keystone_auth_fixture(request_mocker) resp_dict = {"nodes": [ {"instance_uuid": "null", "uuid": "351a82d6-9f04-4c36-b79a-a38b9e98ff71", "links": [{"href": "http://ironic.example.com:6385/" "v1/nodes/foo", "rel": "self"}, {"href": "http://ironic.example.com:6385/" "nodes/foo", "rel": "bookmark"}], "maintenance": "false", "provision_state": "null", "power_state": "power off"}, {"instance_uuid": "null", "uuid": "66fbba13-29e8-4b8a-9e80-c655096a40d3", "links": [{"href": "http://ironic.example.com:6385/" "v1/nodes/foo2", "rel": "self"}, {"href": "http://ironic.example.com:6385/" "nodes/foo2", "rel": "bookmark"}], "maintenance": "false", "provision_state": "null", "power_state": "power off"}]} headers = {'Content-Type': 'application/json; charset=UTF-8'} request_mocker.get('http://ironic.example.com/v1/nodes', headers=headers, json=resp_dict) event_list_text = self.shell('node-list') required = [ '351a82d6-9f04-4c36-b79a-a38b9e98ff71', '66fbba13-29e8-4b8a-9e80-c655096a40d3', ] for r in required: self.assertRegex(event_list_text, r) class ShellTestNoMoxV3(ShellTestNoMox): def _set_fake_env(self): self.set_fake_env(FAKE_ENV_KEYSTONE_V3) class ShellParserTest(TestCase): def test_deprecated_defaults(self): cert_env = {} cert_env['OS_CACERT'] = '/fake/cacert.pem' cert_env['OS_CERT'] = '/fake/cert.pem' cert_env['OS_KEY'] = '/fake/key.pem' self.set_fake_env(cert_env) parser = ironic_shell.IronicShell().get_base_parser() options, _ = parser.parse_known_args([]) self.assertEqual(cert_env['OS_CACERT'], options.os_cacert) self.assertEqual(cert_env['OS_CERT'], options.os_cert) self.assertEqual(cert_env['OS_KEY'], options.os_key) python-ironicclient-2.2.0/ironicclient/tests/__init__.py0000666000175100017510000000000013232474343023472 0ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/common/0000775000175100017510000000000013232474761021523 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/common/utils.py0000666000175100017510000003325413232474343023242 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack LLC. # 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. from __future__ import print_function import argparse import contextlib import gzip import json import os import shutil import subprocess import sys import tempfile from oslo_serialization import base64 from oslo_utils import strutils import six from ironicclient.common.i18n import _ from ironicclient import exc class HelpFormatter(argparse.HelpFormatter): def start_section(self, heading): super(HelpFormatter, self).start_section(heading.capitalize()) def define_command(subparsers, command, callback, cmd_mapper): """Define a command in the subparsers collection. :param subparsers: subparsers collection where the command will go :param command: command name :param callback: function that will be used to process the command """ desc = callback.__doc__ or '' help = desc.strip().split('\n')[0] arguments = getattr(callback, 'arguments', []) subparser = subparsers.add_parser(command, help=help, description=desc, add_help=False, formatter_class=HelpFormatter) subparser.add_argument('-h', '--help', action='help', help=argparse.SUPPRESS) cmd_mapper[command] = subparser required_args = subparser.add_argument_group(_("Required arguments")) for (args, kwargs) in arguments: if kwargs.get('required'): required_args.add_argument(*args, **kwargs) else: subparser.add_argument(*args, **kwargs) subparser.set_defaults(func=callback) def define_commands_from_module(subparsers, command_module, cmd_mapper): """Add *do_* methods in a module and add as commands into a subparsers.""" for method_name in (a for a in dir(command_module) if a.startswith('do_')): # Commands should be hypen-separated instead of underscores. command = method_name[3:].replace('_', '-') callback = getattr(command_module, method_name) define_command(subparsers, command, callback, cmd_mapper) def split_and_deserialize(string): """Split and try to JSON deserialize a string. Gets a string with the KEY=VALUE format, split it (using '=' as the separator) and try to JSON deserialize the VALUE. :returns: A tuple of (key, value). """ try: key, value = string.split("=", 1) except ValueError: raise exc.CommandError(_('Attributes must be a list of ' 'PATH=VALUE not "%s"') % string) try: value = json.loads(value) except ValueError: pass return (key, value) def key_value_pairs_to_dict(key_value_pairs): """Convert a list of key-value pairs to a dictionary. :param key_value_pairs: a list of strings, each string is in the form = :returns: a dictionary, possibly empty """ if key_value_pairs: return dict(split_and_deserialize(v) for v in key_value_pairs) return {} def args_array_to_dict(kwargs, key_to_convert): """Convert the value in a dictionary entry to a dictionary. From the kwargs dictionary, converts the value of the key_to_convert entry from a list of key-value pairs to a dictionary. :param kwargs: a dictionary :param key_to_convert: the key (in kwargs), whose value is expected to be a list of key=value strings. This value will be converted to a dictionary. :returns: kwargs, the (modified) dictionary """ values_to_convert = kwargs.get(key_to_convert) if values_to_convert: kwargs[key_to_convert] = key_value_pairs_to_dict(values_to_convert) return kwargs def args_array_to_patch(op, attributes): patch = [] for attr in attributes: # Sanitize if not attr.startswith('/'): attr = '/' + attr if op in ['add', 'replace']: path, value = split_and_deserialize(attr) patch.append({'op': op, 'path': path, 'value': value}) elif op == "remove": # For remove only the key is needed patch.append({'op': op, 'path': attr}) else: raise exc.CommandError(_('Unknown PATCH operation: %s') % op) return patch def convert_list_props_to_comma_separated(data, props=None): """Convert the list-type properties to comma-separated strings :param data: the input dict object. :param props: the properties whose values will be converted. Default to None to convert all list-type properties of the input. :returns: the result dict instance. """ result = dict(data) if props is None: props = data.keys() for prop in props: val = data.get(prop, None) if isinstance(val, list): result[prop] = ', '.join(map(six.text_type, val)) return result def common_params_for_list(args, fields, field_labels): """Generate 'params' dict that is common for every 'list' command. :param args: arguments from command line. :param fields: possible fields for sorting. :param field_labels: possible field labels for sorting. :returns: a dict with params to pass to the client method. """ params = {} if args.marker is not None: params['marker'] = args.marker if args.limit is not None: if args.limit < 0: raise exc.CommandError( _('Expected non-negative --limit, got %s') % args.limit) params['limit'] = args.limit if args.sort_key is not None: # Support using both heading and field name for sort_key fields_map = dict(zip(field_labels, fields)) fields_map.update(zip(fields, fields)) try: sort_key = fields_map[args.sort_key] except KeyError: raise exc.CommandError( _("%(sort_key)s is an invalid field for sorting, " "valid values for --sort-key are: %(valid)s") % {'sort_key': args.sort_key, 'valid': list(fields_map)}) params['sort_key'] = sort_key if args.sort_dir is not None: if args.sort_dir not in ('asc', 'desc'): raise exc.CommandError( _("%s is an invalid value for sort direction, " "valid values for --sort-dir are: 'asc', 'desc'") % args.sort_dir) params['sort_dir'] = args.sort_dir params['detail'] = args.detail requested_fields = args.fields[0] if args.fields else None if requested_fields is not None: params['fields'] = requested_fields return params def common_filters(marker=None, limit=None, sort_key=None, sort_dir=None, fields=None, detail=False): """Generate common filters for any list request. :param marker: entity ID from which to start returning entities. :param limit: maximum number of entities to return. :param sort_key: field to use for sorting. :param sort_dir: direction of sorting: 'asc' or 'desc'. :param fields: a list with a specified set of fields of the resource to be returned. :param detail: Boolean, True to return detailed information. This parameter can be used for resources which accept 'detail' as a URL parameter. :returns: list of string filters. """ filters = [] if isinstance(limit, int) and limit > 0: filters.append('limit=%s' % limit) if marker is not None: filters.append('marker=%s' % marker) if sort_key is not None: filters.append('sort_key=%s' % sort_key) if sort_dir is not None: filters.append('sort_dir=%s' % sort_dir) if fields is not None: filters.append('fields=%s' % ','.join(fields)) if detail: filters.append('detail=True') return filters @contextlib.contextmanager def tempdir(*args, **kwargs): dirname = tempfile.mkdtemp(*args, **kwargs) try: yield dirname finally: shutil.rmtree(dirname) def make_configdrive(path): """Make the config drive file. :param path: The directory containing the config drive files. :returns: A gzipped and base64 encoded configdrive string. """ # Make sure path it's readable if not os.access(path, os.R_OK): raise exc.CommandError(_('The directory "%s" is not readable') % path) with tempfile.NamedTemporaryFile() as tmpfile: with tempfile.NamedTemporaryFile() as tmpzipfile: publisher = 'ironicclient-configdrive 0.1' try: p = subprocess.Popen(['genisoimage', '-o', tmpfile.name, '-ldots', '-allow-lowercase', '-allow-multidot', '-l', '-publisher', publisher, '-quiet', '-J', '-r', '-V', 'config-2', path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) except OSError as e: raise exc.CommandError( _('Error generating the config drive. Make sure the ' '"genisoimage" tool is installed. Error: %s') % e) stdout, stderr = p.communicate() if p.returncode != 0: raise exc.CommandError( _('Error generating the config drive.' 'Stdout: "%(stdout)s". Stderr: %(stderr)s') % {'stdout': stdout, 'stderr': stderr}) # Compress file tmpfile.seek(0) g = gzip.GzipFile(fileobj=tmpzipfile, mode='wb') shutil.copyfileobj(tmpfile, g) g.close() tmpzipfile.seek(0) return base64.encode_as_bytes(tmpzipfile.read()) def check_empty_arg(arg, arg_descriptor): if not arg.strip(): raise exc.CommandError(_('%(arg)s cannot be empty or only have blank' ' spaces') % {'arg': arg_descriptor}) def bool_argument_value(arg_name, bool_str, strict=True, default=False): """Returns the Boolean represented by bool_str. Returns the Boolean value for the argument named arg_name. The value is represented by the string bool_str. If the string is an invalid Boolean string: if strict is True, a CommandError exception is raised; otherwise the default value is returned. :param arg_name: The name of the argument :param bool_str: The string representing a Boolean value :param strict: Used if the string is invalid. If True, raises an exception. If False, returns the default value. :param default: The default value to return if the string is invalid and not strict :returns: the Boolean value represented by bool_str or the default value if bool_str is invalid and strict is False :raises CommandError: if bool_str is an invalid Boolean string """ try: val = strutils.bool_from_string(bool_str, strict, default) except ValueError as e: raise exc.CommandError(_("argument %(arg)s: %(err)s.") % {'arg': arg_name, 'err': e}) return val def check_for_invalid_fields(fields, valid_fields): """Check for invalid fields. :param fields: A list of fields specified by the user. :param valid_fields: A list of valid fields. :raises CommandError: If invalid fields were specified by the user. """ if not fields: return invalid_fields = set(fields) - set(valid_fields) if invalid_fields: raise exc.CommandError( _('Invalid field(s) requested: %(invalid)s. Valid fields ' 'are: %(valid)s.') % {'invalid': ', '.join(invalid_fields), 'valid': ', '.join(valid_fields)}) def get_from_stdin(info_desc): """Read information from stdin. :param info_desc: A string description of the desired information :raises: InvalidAttribute if there was a problem reading from stdin :returns: the string that was read from stdin """ try: info = sys.stdin.read().strip() except Exception as e: err = _("Cannot get %(desc)s from standard input. Error: %(err)s") raise exc.InvalidAttribute(err % {'desc': info_desc, 'err': e}) return info def handle_json_or_file_arg(json_arg): """Attempts to read JSON argument from file or string. :param json_arg: May be a file name containing the JSON, or a JSON string. :returns: A list or dictionary parsed from JSON. :raises: InvalidAttribute if the argument cannot be parsed. """ if os.path.isfile(json_arg): try: with open(json_arg, 'r') as f: json_arg = f.read().strip() except Exception as e: err = _("Cannot get JSON from file '%(file)s'. " "Error: %(err)s") % {'err': e, 'file': json_arg} raise exc.InvalidAttribute(err) try: json_arg = json.loads(json_arg) except ValueError as e: err = (_("For JSON: '%(string)s', error: '%(err)s'") % {'err': e, 'string': json_arg}) raise exc.InvalidAttribute(err) return json_arg python-ironicclient-2.2.0/ironicclient/common/cliutils.py0000666000175100017510000002125613232474343023731 0ustar zuulzuul00000000000000# Copyright 2012 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # W0603: Using the global statement # W0621: Redefining name %s from outer scope # pylint: disable=W0603,W0621 from __future__ import print_function import getpass import inspect import json import os import sys import textwrap from oslo_utils import encodeutils from oslo_utils import strutils import prettytable import six from six import moves from ironicclient.common.i18n import _ class MissingArgs(Exception): """Supplied arguments are not sufficient for calling a function.""" def __init__(self, missing): self.missing = missing msg = _("Missing arguments: %s") % ", ".join(missing) super(MissingArgs, self).__init__(msg) def validate_args(fn, *args, **kwargs): """Check that the supplied args are sufficient for calling a function. >>> validate_args(lambda a: None) Traceback (most recent call last): ... MissingArgs: Missing argument(s): a >>> validate_args(lambda a, b, c, d: None, 0, c=1) Traceback (most recent call last): ... MissingArgs: Missing argument(s): b, d :param fn: the function to check :param args: the positional arguments supplied :param kwargs: the keyword arguments supplied """ argspec = inspect.getargspec(fn) num_defaults = len(argspec.defaults or []) required_args = argspec.args[:len(argspec.args) - num_defaults] def isbound(method): return getattr(method, '__self__', None) is not None if isbound(fn): required_args.pop(0) missing = [arg for arg in required_args if arg not in kwargs] missing = missing[len(args):] if missing: raise MissingArgs(missing) def arg(*args, **kwargs): """Decorator for CLI args. Example: >>> @arg("name", help="Name of the new entity") ... def entity_create(args): ... pass """ def _decorator(func): add_arg(func, *args, **kwargs) return func return _decorator def env(*args, **kwargs): """Returns the first environment variable set. If all are empty, defaults to '' or keyword arg `default`. """ for arg in args: value = os.environ.get(arg) if value: return value return kwargs.get('default', '') def add_arg(func, *args, **kwargs): """Bind CLI arguments to a shell.py `do_foo` function.""" if not hasattr(func, 'arguments'): func.arguments = [] # NOTE(sirp): avoid dups that can occur when the module is shared across # tests. if (args, kwargs) not in func.arguments: # Because of the semantics of decorator composition if we just append # to the options list positional options will appear to be backwards. func.arguments.insert(0, (args, kwargs)) def unauthenticated(func): """Adds 'unauthenticated' attribute to decorated function. Usage: >>> @unauthenticated ... def mymethod(f): ... pass """ func.unauthenticated = True return func def isunauthenticated(func): """Checks if the function does not require authentication. Mark such functions with the `@unauthenticated` decorator. :returns: bool """ return getattr(func, 'unauthenticated', False) def print_list(objs, fields, formatters=None, sortby_index=0, mixed_case_fields=None, field_labels=None, json_flag=False): """Print a list of objects or dict as a table, one row per object or dict. :param objs: iterable of :class:`Resource` :param fields: attributes that correspond to columns, in order :param formatters: `dict` of callables for field formatting :param sortby_index: index of the field for sorting table rows :param mixed_case_fields: fields corresponding to object attributes that have mixed case names (e.g., 'serverId') :param field_labels: Labels to use in the heading of the table, default to fields. :param json_flag: print the list as JSON instead of table """ def _get_name_and_data(field): if field in formatters: # The value of the field has to be modified. # For example, it can be used to add extra fields. return (field, formatters[field](o)) field_name = field.replace(' ', '_') if field not in mixed_case_fields: field_name = field.lower() if isinstance(o, dict): data = o.get(field_name, '') else: data = getattr(o, field_name, '') return (field_name, data) formatters = formatters or {} mixed_case_fields = mixed_case_fields or [] field_labels = field_labels or fields if len(field_labels) != len(fields): raise ValueError(_("Field labels list %(labels)s has different number " "of elements than fields list %(fields)s"), {'labels': field_labels, 'fields': fields}) if sortby_index is None: kwargs = {} else: kwargs = {'sortby': field_labels[sortby_index]} pt = prettytable.PrettyTable(field_labels) pt.align = 'l' json_array = [] for o in objs: row = [] for field in fields: row.append(_get_name_and_data(field)) if json_flag: json_array.append(dict(row)) else: pt.add_row([r[1] for r in row]) if json_flag: print(json.dumps(json_array, indent=4, separators=(',', ': '))) elif six.PY3: print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode()) else: print(encodeutils.safe_encode(pt.get_string(**kwargs))) def print_dict(dct, dict_property="Property", wrap=0, dict_value='Value', json_flag=False): """Print a `dict` as a table of two columns. :param dct: `dict` to print :param dict_property: name of the first column :param wrap: wrapping for the second column :param dict_value: header label for the value (second) column :param json_flag: print `dict` as JSON instead of table """ if json_flag: print(json.dumps(dct, indent=4, separators=(',', ': '))) return pt = prettytable.PrettyTable([dict_property, dict_value]) pt.align = 'l' for k, v in sorted(dct.items()): # convert dict to str to check length if isinstance(v, dict): v = six.text_type(v) if wrap > 0: v = textwrap.fill(six.text_type(v), wrap) elif wrap < 0: raise ValueError(_("wrap argument should be a non-negative " "integer")) # if value has a newline, add in multiple rows # e.g. fault with stacktrace if v and isinstance(v, six.string_types) and r'\n' in v: lines = v.strip().split(r'\n') col1 = k for line in lines: pt.add_row([col1, line]) col1 = '' else: pt.add_row([k, v]) if six.PY3: print(encodeutils.safe_encode(pt.get_string()).decode()) else: print(encodeutils.safe_encode(pt.get_string())) def get_password(max_password_prompts=3): """Read password from TTY.""" verify = strutils.bool_from_string(env("OS_VERIFY_PASSWORD")) pw = None if hasattr(sys.stdin, "isatty") and sys.stdin.isatty(): # Check for Ctrl-D try: for __ in moves.range(max_password_prompts): pw1 = getpass.getpass("OS Password: ") if verify: pw2 = getpass.getpass("Please verify: ") else: pw2 = pw1 if pw1 == pw2 and pw1: pw = pw1 break except EOFError: pass return pw def service_type(stype): """Adds 'service_type' attribute to decorated function. Usage: .. code-block:: python @service_type('volume') def mymethod(f): ... """ def inner(f): f.service_type = stype return f return inner def get_service_type(f): """Retrieves service type from function.""" return getattr(f, 'service_type', None) def pretty_choice_list(l): return ', '.join("'%s'" % i for i in l) def exit(msg=''): if msg: print(msg, file=sys.stderr) sys.exit(1) python-ironicclient-2.2.0/ironicclient/common/apiclient/0000775000175100017510000000000013232474761023473 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/common/apiclient/exceptions.py0000666000175100017510000003217213232474343026231 0ustar zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 Nebula, Inc. # Copyright 2013 Alessio Ababilov # Copyright 2013 OpenStack Foundation # 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. """ Exception definitions. """ import inspect import sys import six from six.moves import http_client from ironicclient.common.i18n import _ class ClientException(Exception): """The base exception class for all exceptions this library raises.""" pass class ValidationError(ClientException): """Error in validation on API client side.""" pass class UnsupportedVersion(ClientException): """User is trying to use an unsupported version of the API.""" pass class CommandError(ClientException): """Error in CLI tool.""" pass class AuthorizationFailure(ClientException): """Cannot authorize API client.""" pass class ConnectionError(ClientException): """Cannot connect to API service.""" pass class ConnectionRefused(ConnectionError): """Connection refused while trying to connect to API service.""" pass class AuthPluginOptionsMissing(AuthorizationFailure): """Auth plugin misses some options.""" def __init__(self, opt_names): super(AuthPluginOptionsMissing, self).__init__( _("Authentication failed. Missing options: %s") % ", ".join(opt_names)) self.opt_names = opt_names class AuthSystemNotFound(AuthorizationFailure): """User has specified an AuthSystem that is not installed.""" def __init__(self, auth_system): super(AuthSystemNotFound, self).__init__( _("AuthSystemNotFound: %r") % auth_system) self.auth_system = auth_system class NoUniqueMatch(ClientException): """Multiple entities found instead of one.""" pass class EndpointException(ClientException): """Something is rotten in Service Catalog.""" pass class EndpointNotFound(EndpointException): """Could not find requested endpoint in Service Catalog.""" pass class AmbiguousEndpoints(EndpointException): """Found more than one matching endpoint in Service Catalog.""" def __init__(self, endpoints=None): super(AmbiguousEndpoints, self).__init__( _("AmbiguousEndpoints: %r") % endpoints) self.endpoints = endpoints class HttpError(ClientException): """The base exception class for all HTTP exceptions.""" http_status = 0 message = _("HTTP Error") def __init__(self, message=None, details=None, response=None, request_id=None, url=None, method=None, http_status=None): self.http_status = http_status or self.http_status self.message = message or self.message self.details = details self.request_id = request_id self.response = response self.url = url self.method = method formatted_string = "%s (HTTP %s)" % (self.message, self.http_status) if request_id: formatted_string += " (Request-ID: %s)" % request_id super(HttpError, self).__init__(formatted_string) class HTTPRedirection(HttpError): """HTTP Redirection.""" message = _("HTTP Redirection") class HTTPClientError(HttpError): """Client-side HTTP error. Exception for cases in which the client seems to have erred. """ message = _("HTTP Client Error") class HttpServerError(HttpError): """Server-side HTTP error. Exception for cases in which the server is aware that it has erred or is incapable of performing the request. """ message = _("HTTP Server Error") class MultipleChoices(HTTPRedirection): """HTTP 300 - Multiple Choices. Indicates multiple options for the resource that the client may follow. """ http_status = http_client.MULTIPLE_CHOICES message = _("Multiple Choices") class BadRequest(HTTPClientError): """HTTP 400 - Bad Request. The request cannot be fulfilled due to bad syntax. """ http_status = http_client.BAD_REQUEST message = _("Bad Request") class Unauthorized(HTTPClientError): """HTTP 401 - Unauthorized. Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided. """ http_status = http_client.UNAUTHORIZED message = _("Unauthorized") class PaymentRequired(HTTPClientError): """HTTP 402 - Payment Required. Reserved for future use. """ http_status = http_client.PAYMENT_REQUIRED message = _("Payment Required") class Forbidden(HTTPClientError): """HTTP 403 - Forbidden. The request was a valid request, but the server is refusing to respond to it. """ http_status = http_client.FORBIDDEN message = _("Forbidden") class NotFound(HTTPClientError): """HTTP 404 - Not Found. The requested resource could not be found but may be available again in the future. """ http_status = http_client.NOT_FOUND message = _("Not Found") class MethodNotAllowed(HTTPClientError): """HTTP 405 - Method Not Allowed. A request was made of a resource using a request method not supported by that resource. """ http_status = http_client.METHOD_NOT_ALLOWED message = _("Method Not Allowed") class NotAcceptable(HTTPClientError): """HTTP 406 - Not Acceptable. The requested resource is only capable of generating content not acceptable according to the Accept headers sent in the request. """ http_status = http_client.NOT_ACCEPTABLE message = _("Not Acceptable") class ProxyAuthenticationRequired(HTTPClientError): """HTTP 407 - Proxy Authentication Required. The client must first authenticate itself with the proxy. """ http_status = http_client.PROXY_AUTHENTICATION_REQUIRED message = _("Proxy Authentication Required") class RequestTimeout(HTTPClientError): """HTTP 408 - Request Timeout. The server timed out waiting for the request. """ http_status = http_client.REQUEST_TIMEOUT message = _("Request Timeout") class Conflict(HTTPClientError): """HTTP 409 - Conflict. Indicates that the request could not be processed because of conflict in the request, such as an edit conflict. """ http_status = http_client.CONFLICT message = _("Conflict") class Gone(HTTPClientError): """HTTP 410 - Gone. Indicates that the resource requested is no longer available and will not be available again. """ http_status = http_client.GONE message = _("Gone") class LengthRequired(HTTPClientError): """HTTP 411 - Length Required. The request did not specify the length of its content, which is required by the requested resource. """ http_status = http_client.LENGTH_REQUIRED message = _("Length Required") class PreconditionFailed(HTTPClientError): """HTTP 412 - Precondition Failed. The server does not meet one of the preconditions that the requester put on the request. """ http_status = http_client.PRECONDITION_FAILED message = _("Precondition Failed") class RequestEntityTooLarge(HTTPClientError): """HTTP 413 - Request Entity Too Large. The request is larger than the server is willing or able to process. """ http_status = http_client.REQUEST_ENTITY_TOO_LARGE message = _("Request Entity Too Large") def __init__(self, *args, **kwargs): try: self.retry_after = int(kwargs.pop('retry_after')) except (KeyError, ValueError): self.retry_after = 0 super(RequestEntityTooLarge, self).__init__(*args, **kwargs) class RequestUriTooLong(HTTPClientError): """HTTP 414 - Request-URI Too Long. The URI provided was too long for the server to process. """ http_status = http_client.REQUEST_URI_TOO_LONG message = _("Request-URI Too Long") class UnsupportedMediaType(HTTPClientError): """HTTP 415 - Unsupported Media Type. The request entity has a media type which the server or resource does not support. """ http_status = http_client.UNSUPPORTED_MEDIA_TYPE message = _("Unsupported Media Type") class RequestedRangeNotSatisfiable(HTTPClientError): """HTTP 416 - Requested Range Not Satisfiable. The client has asked for a portion of the file, but the server cannot supply that portion. """ http_status = http_client.REQUESTED_RANGE_NOT_SATISFIABLE message = _("Requested Range Not Satisfiable") class ExpectationFailed(HTTPClientError): """HTTP 417 - Expectation Failed. The server cannot meet the requirements of the Expect request-header field. """ http_status = http_client.EXPECTATION_FAILED message = _("Expectation Failed") class UnprocessableEntity(HTTPClientError): """HTTP 422 - Unprocessable Entity. The request was well-formed but was unable to be followed due to semantic errors. """ http_status = http_client.UNPROCESSABLE_ENTITY message = _("Unprocessable Entity") class InternalServerError(HttpServerError): """HTTP 500 - Internal Server Error. A generic error message, given when no more specific message is suitable. """ http_status = http_client.INTERNAL_SERVER_ERROR message = _("Internal Server Error") # NotImplemented is a python keyword. class HttpNotImplemented(HttpServerError): """HTTP 501 - Not Implemented. The server either does not recognize the request method, or it lacks the ability to fulfill the request. """ http_status = http_client.NOT_IMPLEMENTED message = _("Not Implemented") class BadGateway(HttpServerError): """HTTP 502 - Bad Gateway. The server was acting as a gateway or proxy and received an invalid response from the upstream server. """ http_status = http_client.BAD_GATEWAY message = _("Bad Gateway") class ServiceUnavailable(HttpServerError): """HTTP 503 - Service Unavailable. The server is currently unavailable. """ http_status = http_client.SERVICE_UNAVAILABLE message = _("Service Unavailable") class GatewayTimeout(HttpServerError): """HTTP 504 - Gateway Timeout. The server was acting as a gateway or proxy and did not receive a timely response from the upstream server. """ http_status = http_client.GATEWAY_TIMEOUT message = _("Gateway Timeout") class HttpVersionNotSupported(HttpServerError): """HTTP 505 - HttpVersion Not Supported. The server does not support the HTTP protocol version used in the request. """ http_status = http_client.HTTP_VERSION_NOT_SUPPORTED message = _("HTTP Version Not Supported") # _code_map contains all the classes that have http_status attribute. _code_map = dict( (getattr(obj, 'http_status', None), obj) for name, obj in vars(sys.modules[__name__]).items() if inspect.isclass(obj) and getattr(obj, 'http_status', False) ) def from_response(response, method, url): """Returns an instance of :class:`HttpError` or subclass based on response. :param response: instance of `requests.Response` class :param method: HTTP method used for request :param url: URL used for request """ req_id = response.headers.get("x-openstack-request-id") # NOTE(hdd) true for older versions of nova and cinder if not req_id: req_id = response.headers.get("x-compute-request-id") kwargs = { "http_status": response.status_code, "response": response, "method": method, "url": url, "request_id": req_id, } if "retry-after" in response.headers: kwargs["retry_after"] = response.headers["retry-after"] content_type = response.headers.get("Content-Type", "") if content_type.startswith("application/json"): try: body = response.json() except ValueError: pass else: if isinstance(body, dict): error = body.get(list(body)[0]) if isinstance(error, dict): kwargs["message"] = (error.get("message") or error.get("faultstring")) kwargs["details"] = (error.get("details") or six.text_type(body)) elif content_type.startswith("text/"): kwargs["details"] = getattr(response, 'text', '') try: cls = _code_map[response.status_code] except KeyError: # 5XX status codes are server errors if response.status_code >= http_client.INTERNAL_SERVER_ERROR: cls = HttpServerError # 4XX status codes are client request errors elif (http_client.BAD_REQUEST <= response.status_code < http_client.INTERNAL_SERVER_ERROR): cls = HTTPClientError else: cls = HttpError return cls(**kwargs) python-ironicclient-2.2.0/ironicclient/common/apiclient/__init__.py0000666000175100017510000000000013232474343025570 0ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/common/apiclient/base.py0000666000175100017510000004104713232474343024763 0ustar zuulzuul00000000000000# Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 OpenStack Foundation # Copyright 2012 Grid Dynamics # Copyright 2013 OpenStack Foundation # 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. """ Base utilities to build API operation managers and objects on top of. """ # E1102: %s is not callable # pylint: disable=E1102 import abc import copy from oslo_utils import strutils import six from six.moves import http_client from six.moves.urllib import parse from ironicclient.common.apiclient import exceptions from ironicclient.common.i18n import _ def getid(obj): """Return id if argument is a Resource. Abstracts the common pattern of allowing both an object or an object's ID (UUID) as a parameter when dealing with relationships. """ try: if obj.uuid: return obj.uuid except AttributeError: pass try: return obj.id except AttributeError: return obj # TODO(aababilov): call run_hooks() in HookableMixin's child classes class HookableMixin(object): """Mixin so classes can register and run hooks.""" _hooks_map = {} @classmethod def add_hook(cls, hook_type, hook_func): """Add a new hook of specified type. :param cls: class that registers hooks :param hook_type: hook type, e.g., '__pre_parse_args__' :param hook_func: hook function """ if hook_type not in cls._hooks_map: cls._hooks_map[hook_type] = [] cls._hooks_map[hook_type].append(hook_func) @classmethod def run_hooks(cls, hook_type, *args, **kwargs): """Run all hooks of specified type. :param cls: class that registers hooks :param hook_type: hook type, e.g., '__pre_parse_args__' :param args: args to be passed to every hook function :param kwargs: kwargs to be passed to every hook function """ hook_funcs = cls._hooks_map.get(hook_type) or [] for hook_func in hook_funcs: hook_func(*args, **kwargs) class BaseManager(HookableMixin): """Basic manager type providing common operations. Managers interact with a particular type of API (servers, flavors, images, etc.) and provide CRUD operations for them. """ resource_class = None def __init__(self, client): """Initializes BaseManager with `client`. :param client: instance of BaseClient descendant for HTTP requests """ super(BaseManager, self).__init__() self.client = client def _list(self, url, response_key=None, obj_class=None, json=None): """List the collection. :param url: a partial URL, e.g., '/servers' :param response_key: the key to be looked up in response dictionary, e.g., 'servers'. If response_key is None - all response body will be used. :param obj_class: class for constructing the returned objects (self.resource_class will be used by default) :param json: data that will be encoded as JSON and passed in POST request (GET will be sent by default) """ if json: body = self.client.post(url, json=json).json() else: body = self.client.get(url).json() if obj_class is None: obj_class = self.resource_class data = body[response_key] if response_key is not None else body # NOTE(ja): keystone returns values as list as {'values': [ ... ]} # unlike other services which just return the list... try: data = data['values'] except (KeyError, TypeError): pass return [obj_class(self, res, loaded=True) for res in data if res] def _get(self, url, response_key=None): """Get an object from collection. :param url: a partial URL, e.g., '/servers' :param response_key: the key to be looked up in response dictionary, e.g., 'server'. If response_key is None - all response body will be used. """ body = self.client.get(url).json() data = body[response_key] if response_key is not None else body return self.resource_class(self, data, loaded=True) def _head(self, url): """Retrieve request headers for an object. :param url: a partial URL, e.g., '/servers' """ resp = self.client.head(url) return resp.status_code == http_client.NO_CONTENT def _post(self, url, json, response_key=None, return_raw=False): """Create an object. :param url: a partial URL, e.g., '/servers' :param json: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param response_key: the key to be looked up in response dictionary, e.g., 'server'. If response_key is None - all response body will be used. :param return_raw: flag to force returning raw JSON instead of Python object of self.resource_class """ body = self.client.post(url, json=json).json() data = body[response_key] if response_key is not None else body if return_raw: return data return self.resource_class(self, data) def _put(self, url, json=None, response_key=None): """Update an object with PUT method. :param url: a partial URL, e.g., '/servers' :param json: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param response_key: the key to be looked up in response dictionary, e.g., 'servers'. If response_key is None - all response body will be used. """ resp = self.client.put(url, json=json) # PUT requests may not return a body if resp.content: body = resp.json() if response_key is not None: return self.resource_class(self, body[response_key]) else: return self.resource_class(self, body) def _patch(self, url, json=None, response_key=None): """Update an object with PATCH method. :param url: a partial URL, e.g., '/servers' :param json: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param response_key: the key to be looked up in response dictionary, e.g., 'servers'. If response_key is None - all response body will be used. """ body = self.client.patch(url, json=json).json() if response_key is not None: return self.resource_class(self, body[response_key]) else: return self.resource_class(self, body) def _delete(self, url): """Delete an object. :param url: a partial URL, e.g., '/servers/my-server' """ return self.client.delete(url) @six.add_metaclass(abc.ABCMeta) class ManagerWithFind(BaseManager): """Manager with additional `find()`/`findall()` methods.""" @abc.abstractmethod def list(self): pass def find(self, **kwargs): """Find a single item with attributes matching ``**kwargs``. This isn't very efficient: it loads the entire list then filters on the Python side. """ matches = self.findall(**kwargs) num_matches = len(matches) if num_matches == 0: msg = _("No %(name)s matching %(args)s.") % { 'name': self.resource_class.__name__, 'args': kwargs } raise exceptions.NotFound(msg) elif num_matches > 1: raise exceptions.NoUniqueMatch() else: return matches[0] def findall(self, **kwargs): """Find all items with attributes matching ``**kwargs``. This isn't very efficient: it loads the entire list then filters on the Python side. """ found = [] searches = kwargs.items() for obj in self.list(): try: if all(getattr(obj, attr) == value for (attr, value) in searches): found.append(obj) except AttributeError: continue return found class CrudManager(BaseManager): """Base manager class for manipulating entities. Children of this class are expected to define a `collection_key` and `key`. - `collection_key`: Usually a plural noun by convention (e.g. `entities`); used to refer collections in both URL's (e.g. `/v3/entities`) and JSON objects containing a list of member resources (e.g. `{'entities': [{}, {}, {}]}`). - `key`: Usually a singular noun by convention (e.g. `entity`); used to refer to an individual member of the collection. """ collection_key = None key = None def build_url(self, base_url=None, **kwargs): """Builds a resource URL for the given kwargs. Given an example collection where `collection_key = 'entities'` and `key = 'entity'`, the following URL's could be generated. By default, the URL will represent a collection of entities, e.g.:: /entities If kwargs contains an `entity_id`, then the URL will represent a specific member, e.g.:: /entities/{entity_id} :param base_url: if provided, the generated URL will be appended to it """ url = base_url if base_url is not None else '' url += '/%s' % self.collection_key # do we have a specific entity? entity_id = kwargs.get('%s_id' % self.key) if entity_id is not None: url += '/%s' % entity_id return url def _filter_kwargs(self, kwargs): """Drop null values and handle ids.""" for key, ref in kwargs.copy().items(): if ref is None: kwargs.pop(key) else: if isinstance(ref, Resource): kwargs.pop(key) kwargs['%s_id' % key] = getid(ref) return kwargs def create(self, **kwargs): kwargs = self._filter_kwargs(kwargs) return self._post( self.build_url(**kwargs), {self.key: kwargs}, self.key) def get(self, **kwargs): kwargs = self._filter_kwargs(kwargs) return self._get( self.build_url(**kwargs), self.key) def head(self, **kwargs): kwargs = self._filter_kwargs(kwargs) return self._head(self.build_url(**kwargs)) def list(self, base_url=None, **kwargs): """List the collection. :param base_url: if provided, the generated URL will be appended to it """ kwargs = self._filter_kwargs(kwargs) return self._list( '%(base_url)s%(query)s' % { 'base_url': self.build_url(base_url=base_url, **kwargs), 'query': '?%s' % parse.urlencode(kwargs) if kwargs else '', }, self.collection_key) def put(self, base_url=None, **kwargs): """Update an element. :param base_url: if provided, the generated URL will be appended to it """ kwargs = self._filter_kwargs(kwargs) return self._put(self.build_url(base_url=base_url, **kwargs)) def update(self, **kwargs): kwargs = self._filter_kwargs(kwargs) params = kwargs.copy() params.pop('%s_id' % self.key) return self._patch( self.build_url(**kwargs), {self.key: params}, self.key) def delete(self, **kwargs): kwargs = self._filter_kwargs(kwargs) return self._delete( self.build_url(**kwargs)) def find(self, base_url=None, **kwargs): """Find a single item with attributes matching ``**kwargs``. :param base_url: if provided, the generated URL will be appended to it """ kwargs = self._filter_kwargs(kwargs) rl = self._list( '%(base_url)s%(query)s' % { 'base_url': self.build_url(base_url=base_url, **kwargs), 'query': '?%s' % parse.urlencode(kwargs) if kwargs else '', }, self.collection_key) num = len(rl) if num == 0: msg = _("No %(name)s matching %(args)s.") % { 'name': self.resource_class.__name__, 'args': kwargs } raise exceptions.NotFound(msg) elif num > 1: raise exceptions.NoUniqueMatch else: return rl[0] class Extension(HookableMixin): """Extension descriptor.""" SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__') manager_class = None def __init__(self, name, module): super(Extension, self).__init__() self.name = name self.module = module self._parse_extension_module() def _parse_extension_module(self): self.manager_class = None for attr_name, attr_value in self.module.__dict__.items(): if attr_name in self.SUPPORTED_HOOKS: self.add_hook(attr_name, attr_value) else: try: if issubclass(attr_value, BaseManager): self.manager_class = attr_value except TypeError: pass def __repr__(self): return "" % self.name class Resource(object): """Base class for OpenStack resources (tenant, user, etc.). This is pretty much just a bag for attributes. """ HUMAN_ID = False NAME_ATTR = 'name' def __init__(self, manager, info, loaded=False): """Populate and bind to a manager. :param manager: BaseManager object :param info: dictionary representing resource attributes :param loaded: prevent lazy-loading if set to True """ self.manager = manager self._info = info self._add_details(info) self._loaded = loaded def __repr__(self): reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and k != 'manager') info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) return "<%s %s>" % (self.__class__.__name__, info) @property def human_id(self): """Human-readable ID which can be used for bash completion.""" if self.HUMAN_ID: name = getattr(self, self.NAME_ATTR, None) if name is not None: return strutils.to_slug(name) return None def _add_details(self, info): for (k, v) in info.items(): try: setattr(self, k, v) self._info[k] = v except AttributeError: # In this case we already defined the attribute on the class pass def __getattr__(self, k): if k not in self.__dict__: # NOTE(bcwaldon): disallow lazy-loading if already loaded once if not self.is_loaded(): self.get() return self.__getattr__(k) raise AttributeError(k) else: return self.__dict__[k] def get(self): """Support for lazy loading details. Some clients, such as novaclient have the option to lazy load the details, details which can be loaded with this function. """ # set_loaded() first ... so if we have to bail, we know we tried. self.set_loaded(True) if not hasattr(self.manager, 'get'): return new = self.manager.get(self.id) if new: self._add_details(new._info) self._add_details( {'x_request_id': self.manager.client.last_request_id}) def __eq__(self, other): if not isinstance(other, Resource): return NotImplemented # two resources of different types are not equal if not isinstance(other, self.__class__): return False return self._info == other._info def is_loaded(self): return self._loaded def set_loaded(self, val): self._loaded = val def to_dict(self): return copy.deepcopy(self._info) python-ironicclient-2.2.0/ironicclient/common/filecache.py0000666000175100017510000000637413232474343024010 0ustar zuulzuul00000000000000# # Copyright 2015 Rackspace, Inc # 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 logging import os import appdirs import dogpile.cache LOG = logging.getLogger(__name__) AUTHOR = 'openstack' PROGNAME = 'python-ironicclient' CACHE = None CACHE_DIR = appdirs.user_cache_dir(PROGNAME, AUTHOR) CACHE_EXPIRY_ENV_VAR = 'IRONICCLIENT_CACHE_EXPIRY' # environment variable CACHE_FILENAME = os.path.join(CACHE_DIR, 'ironic-api-version.dbm') DEFAULT_EXPIRY = 300 # seconds def _get_cache(): """Configure file caching.""" global CACHE if CACHE is None: # Ensure cache directory present if not os.path.exists(CACHE_DIR): os.makedirs(CACHE_DIR) # Use the cache expiry if specified in an env var expiry_time = os.environ.get(CACHE_EXPIRY_ENV_VAR, DEFAULT_EXPIRY) try: expiry_time = int(expiry_time) except ValueError: LOG.warning("Environment variable %(env_var)s should be an " "integer (not '%(curr_val)s'). Using default " "expiry of %(default)s seconds instead.", {'env_var': CACHE_EXPIRY_ENV_VAR, 'curr_val': expiry_time, 'default': DEFAULT_EXPIRY}) expiry_time = DEFAULT_EXPIRY CACHE = dogpile.cache.make_region(key_mangler=str).configure( 'dogpile.cache.dbm', expiration_time=expiry_time, arguments={ "filename": CACHE_FILENAME, } ) return CACHE def _build_key(host, port): """Build a key based upon the hostname or address supplied.""" return "%s:%s" % (host, port) def save_data(host, port, data): """Save 'data' for a particular 'host' in the appropriate cache dir. param host: The host that we need to save data for param port: The port on the host that we need to save data for param data: The data we want saved """ key = _build_key(host, port) _get_cache().set(key, data) def retrieve_data(host, port, expiry=None): """Retrieve the version stored for an ironic 'host', if it's not stale. Check to see if there is valid cached data for the host/port combination and return that if it isn't stale. param host: The host that we need to retrieve data for param port: The port on the host that we need to retrieve data for param expiry: The age in seconds before cached data is deemed invalid """ # Ensure that a cache file exists first if not os.path.isfile(CACHE_FILENAME): return None key = _build_key(host, port) data = _get_cache().get(key, expiration_time=expiry) if data == dogpile.cache.api.NO_VALUE: return None return data python-ironicclient-2.2.0/ironicclient/common/i18n.py0000666000175100017510000000145113232474343022653 0ustar zuulzuul00000000000000# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. # 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='ironicclient') # The primary translation function using the well-known name "_" _ = _translators.primary python-ironicclient-2.2.0/ironicclient/common/http.py0000666000175100017510000007335513232474373023072 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack LLC. # 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 copy from distutils.version import StrictVersion import functools import hashlib import logging import os import socket import ssl import textwrap import time from keystoneauth1 import adapter from keystoneauth1 import exceptions as kexc from oslo_serialization import jsonutils from oslo_utils import strutils import requests import six from six.moves import http_client import six.moves.urllib.parse as urlparse from ironicclient.common import filecache from ironicclient.common.i18n import _ from ironicclient import exc # NOTE(deva): Record the latest version that this client was tested with. # We still have a lot of work to do in the client to implement # microversion support in the client properly! See # http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa # for full details. DEFAULT_VER = '1.9' LAST_KNOWN_API_VERSION = 37 LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION) LOG = logging.getLogger(__name__) USER_AGENT = 'python-ironicclient' CHUNKSIZE = 1024 * 64 # 64kB API_VERSION = '/v1' API_VERSION_SELECTED_STATES = ('user', 'negotiated', 'cached', 'default') DEFAULT_MAX_RETRIES = 5 DEFAULT_RETRY_INTERVAL = 2 SENSITIVE_HEADERS = ('X-Auth-Token',) SUPPORTED_ENDPOINT_SCHEME = ('http', 'https') def _trim_endpoint_api_version(url): """Trim API version and trailing slash from endpoint.""" return url.rstrip('/').rstrip(API_VERSION).rstrip('/') def _extract_error_json(body): """Return error_message from the HTTP response body.""" error_json = {} try: body_json = jsonutils.loads(body) if 'error_message' in body_json: raw_msg = body_json['error_message'] error_json = jsonutils.loads(raw_msg) except ValueError: pass return error_json def get_server(endpoint): """Extract and return the server & port that we're connecting to.""" if endpoint is None: return None, None parts = urlparse.urlparse(endpoint) return parts.hostname, str(parts.port) class VersionNegotiationMixin(object): def negotiate_version(self, conn, resp): """Negotiate the server version Assumption: Called after receiving a 406 error when doing a request. param conn: A connection object param resp: The response object from http request """ def _query_server(conn): if (self.os_ironic_api_version and not isinstance(self.os_ironic_api_version, list) and self.os_ironic_api_version != 'latest'): base_version = ("/v%s" % str(self.os_ironic_api_version).split('.')[0]) else: base_version = API_VERSION return self._make_simple_request(conn, 'GET', base_version) if not resp: resp = _query_server(conn) if self.api_version_select_state not in API_VERSION_SELECTED_STATES: raise RuntimeError( _('Error: self.api_version_select_state should be one of the ' 'values in: "%(valid)s" but had the value: "%(value)s"') % {'valid': ', '.join(API_VERSION_SELECTED_STATES), 'value': self.api_version_select_state}) min_ver, max_ver = self._parse_version_headers(resp) # NOTE: servers before commit 32fb6e99 did not return version headers # on error, so we need to perform a GET to determine # the supported version range if not max_ver: LOG.debug('No version header in response, requesting from server') resp = _query_server(conn) min_ver, max_ver = self._parse_version_headers(resp) # Reset the maximum version that we permit if StrictVersion(max_ver) > StrictVersion(LATEST_VERSION): LOG.debug("Remote API version %(max_ver)s is greater than the " "version supported by ironicclient. Maximum available " "version is %(client_ver)s", {'max_ver': max_ver, 'client_ver': LATEST_VERSION}) max_ver = LATEST_VERSION # If the user requested an explicit version or we have negotiated a # version and still failing then error now. The server could # support the version requested but the requested operation may not # be supported by the requested version. # TODO(TheJulia): We should break this method into several parts, # such as a sanity check/error method. if (self.api_version_select_state == 'user' and self.os_ironic_api_version != 'latest' and not isinstance(self.os_ironic_api_version, list)): raise exc.UnsupportedVersion(textwrap.fill( _("Requested API version %(req)s is not supported by the " "server, client, or the requested operation is not " "supported by the requested version." "Supported version range is %(min)s to " "%(max)s") % {'req': self.os_ironic_api_version, 'min': min_ver, 'max': max_ver})) if self.api_version_select_state == 'negotiated': raise exc.UnsupportedVersion(textwrap.fill( _("No API version was specified and the requested operation " "was not supported by the client's negotiated API version " "%(req)s. Supported version range is: %(min)s to %(max)s") % {'req': self.os_ironic_api_version, 'min': min_ver, 'max': max_ver})) if isinstance(self.os_ironic_api_version, six.string_types): if self.os_ironic_api_version == 'latest': negotiated_ver = max_ver else: negotiated_ver = str( min(StrictVersion(self.os_ironic_api_version), StrictVersion(max_ver))) elif isinstance(self.os_ironic_api_version, list): if 'latest' in self.os_ironic_api_version: raise ValueError(textwrap.fill( _("The 'latest' API version can not be requested " "in a list of versions. Please explicitly request " "'latest' or request only versios between " "%(min)s to %(max)s") % {'min': min_ver, 'max': max_ver})) versions = [] for version in self.os_ironic_api_version: if min_ver <= StrictVersion(version) <= max_ver: versions.append(StrictVersion(version)) if versions: negotiated_ver = str(max(versions)) else: raise exc.UnsupportedVersion(textwrap.fill( _("Requested API version specified and the requested " "operation was not supported by the client's " "requested API version %(req)s. Supported " "version range is: %(min)s to %(max)s") % {'req': self.os_ironic_api_version, 'min': min_ver, 'max': max_ver})) else: raise ValueError(textwrap.fill( _("Requested API version %(req)s type is unsupported. " "Valid types are Strings such as '1.1', 'latest' " "or a list of string values representing API versions.") % {'req': self.os_ironic_api_version})) if StrictVersion(negotiated_ver) < StrictVersion(min_ver): negotiated_ver = min_ver # server handles microversions, but doesn't support # the requested version, so try a negotiated version self.api_version_select_state = 'negotiated' self.os_ironic_api_version = negotiated_ver LOG.debug('Negotiated API version is %s', negotiated_ver) # Cache the negotiated version for this server host, port = get_server(self.endpoint) filecache.save_data(host=host, port=port, data=negotiated_ver) return negotiated_ver def _generic_parse_version_headers(self, accessor_func): min_ver = accessor_func('X-OpenStack-Ironic-API-Minimum-Version', None) max_ver = accessor_func('X-OpenStack-Ironic-API-Maximum-Version', None) return min_ver, max_ver def _parse_version_headers(self, accessor_func): # NOTE(jlvillal): Declared for unit testing purposes raise NotImplementedError() def _make_simple_request(self, conn, method, url): # NOTE(jlvillal): Declared for unit testing purposes raise NotImplementedError() _RETRY_EXCEPTIONS = (exc.Conflict, exc.ServiceUnavailable, exc.ConnectionRefused, kexc.RetriableConnectionFailure) def with_retries(func): """Wrapper for _http_request adding support for retries.""" @functools.wraps(func) def wrapper(self, url, method, **kwargs): if self.conflict_max_retries is None: self.conflict_max_retries = DEFAULT_MAX_RETRIES if self.conflict_retry_interval is None: self.conflict_retry_interval = DEFAULT_RETRY_INTERVAL num_attempts = self.conflict_max_retries + 1 for attempt in range(1, num_attempts + 1): try: return func(self, url, method, **kwargs) except _RETRY_EXCEPTIONS as error: msg = ("Error contacting Ironic server: %(error)s. " "Attempt %(attempt)d of %(total)d" % {'attempt': attempt, 'total': num_attempts, 'error': error}) if attempt == num_attempts: LOG.error(msg) raise else: LOG.debug(msg) time.sleep(self.conflict_retry_interval) return wrapper class HTTPClient(VersionNegotiationMixin): def __init__(self, endpoint, **kwargs): self.endpoint = endpoint self.endpoint_trimmed = _trim_endpoint_api_version(endpoint) self.auth_token = kwargs.get('token') self.auth_ref = kwargs.get('auth_ref') self.os_ironic_api_version = kwargs.get('os_ironic_api_version', DEFAULT_VER) self.api_version_select_state = kwargs.get( 'api_version_select_state', 'default') self.conflict_max_retries = kwargs.pop('max_retries', DEFAULT_MAX_RETRIES) self.conflict_retry_interval = kwargs.pop('retry_interval', DEFAULT_RETRY_INTERVAL) self.session = requests.Session() parts = urlparse.urlparse(endpoint) if parts.scheme not in SUPPORTED_ENDPOINT_SCHEME: msg = _('Unsupported scheme: %s') % parts.scheme raise exc.EndpointException(msg) if parts.scheme == 'https': if kwargs.get('insecure') is True: self.session.verify = False elif kwargs.get('ca_file'): self.session.verify = kwargs['ca_file'] self.session.cert = (kwargs.get('cert_file'), kwargs.get('key_file')) def _process_header(self, name, value): """Redacts any sensitive header Redact a header that contains sensitive information, by returning an updated header with the sha1 hash of that value. The redacted value is prefixed by '{SHA1}' because that's the convention used within OpenStack. :returns: A tuple of (name, value) name: the safe encoding format of name value: the redacted value if name is x-auth-token, or the safe encoding format of name """ if name in SENSITIVE_HEADERS: v = value.encode('utf-8') h = hashlib.sha1(v) d = h.hexdigest() return (name, "{SHA1}%s" % d) else: return (name, value) def log_curl_request(self, method, url, kwargs): curl = ['curl -i -X %s' % method] for (key, value) in kwargs['headers'].items(): header = '-H \'%s: %s\'' % self._process_header(key, value) curl.append(header) if not self.session.verify: curl.append('-k') elif isinstance(self.session.verify, six.string_types): curl.append('--cacert %s' % self.session.verify) if self.session.cert: curl.append('--cert %s' % self.session.cert[0]) curl.append('--key %s' % self.session.cert[1]) if 'body' in kwargs: body = strutils.mask_password(kwargs['body']) curl.append('-d \'%s\'' % body) curl.append(self._make_connection_url(url)) LOG.debug(' '.join(curl)) @staticmethod def log_http_response(resp, body=None): # NOTE(aarefiev): resp.raw is urllib3 response object, it's used # only to get 'version', response from request with 'stream = True' # should be used for raw reading. status = (resp.raw.version / 10.0, resp.status_code, resp.reason) dump = ['\nHTTP/%.1f %s %s' % status] dump.extend(['%s: %s' % (k, v) for k, v in resp.headers.items()]) dump.append('') if body: body = strutils.mask_password(body) dump.extend([body, '']) LOG.debug('\n'.join(dump)) def _make_connection_url(self, url): # NOTE(pas-ha) we already stripped trailing / from endpoint_trimmed if not url.startswith('/'): url = '/' + url return self.endpoint_trimmed + url def _parse_version_headers(self, resp): return self._generic_parse_version_headers(resp.headers.get) def _make_simple_request(self, conn, method, url): return conn.request(method, self._make_connection_url(url)) @with_retries def _http_request(self, url, method, **kwargs): """Send an http request with the specified characteristics. Wrapper around request.Session.request to handle tasks such as setting headers and error handling. """ # NOTE(TheJulia): self.os_ironic_api_version is reset in # the self.negotiate_version() call if negotiation occurs. if (self.os_ironic_api_version and self.api_version_select_state == 'user' and (self.os_ironic_api_version == 'latest' or isinstance(self.os_ironic_api_version, list))): self.negotiate_version(self.session, None) # Copy the kwargs so we can reuse the original in case of redirects kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {})) kwargs['headers'].setdefault('User-Agent', USER_AGENT) if self.os_ironic_api_version: kwargs['headers'].setdefault('X-OpenStack-Ironic-API-Version', self.os_ironic_api_version) if self.auth_token: kwargs['headers'].setdefault('X-Auth-Token', self.auth_token) self.log_curl_request(method, url, kwargs) # NOTE(aarefiev): This is for backwards compatibility, request # expected body in 'data' field, previously we used httplib, # which expected 'body' field. body = kwargs.pop('body', None) if body: kwargs['data'] = body conn_url = self._make_connection_url(url) try: resp = self.session.request(method, conn_url, **kwargs) # TODO(deva): implement graceful client downgrade when connecting # to servers that did not support microversions. Details here: # https://specs.openstack.org/openstack/ironic-specs/specs/kilo-implemented/api-microversions.html#use-case-3b-new-client-communicating-with-a-old-ironic-user-specified # noqa if resp.status_code == http_client.NOT_ACCEPTABLE: negotiated_ver = self.negotiate_version(self.session, resp) kwargs['headers']['X-OpenStack-Ironic-API-Version'] = ( negotiated_ver) return self._http_request(url, method, **kwargs) except requests.exceptions.RequestException as e: message = (_("Error has occurred while handling " "request for %(url)s: %(e)s") % dict(url=conn_url, e=e)) # NOTE(aarefiev): not valid request(invalid url, missing schema, # and so on), retrying is not needed. if isinstance(e, ValueError): raise exc.ValidationError(message) raise exc.ConnectionRefused(message) body_str = None if resp.headers.get('Content-Type') == 'application/octet-stream': body_iter = resp.iter_content(chunk_size=CHUNKSIZE) self.log_http_response(resp) else: # Read body into string if it isn't obviously image data body_str = resp.text self.log_http_response(resp, body_str) body_iter = six.StringIO(body_str) if resp.status_code >= http_client.BAD_REQUEST: error_json = _extract_error_json(body_str) # NOTE(vdrok): exceptions from ironic controllers' _lookup methods # are constructed directly by pecan instead of wsme, and contain # only description field raise exc.from_response( resp, (error_json.get('faultstring') or error_json.get('description')), error_json.get('debuginfo'), method, url) elif resp.status_code in (http_client.MOVED_PERMANENTLY, http_client.FOUND, http_client.USE_PROXY): # Redirected. Reissue the request to the new location. return self._http_request(resp['location'], method, **kwargs) elif resp.status_code == http_client.MULTIPLE_CHOICES: raise exc.from_response(resp, method=method, url=url) return resp, body_iter def json_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/json') kwargs['headers'].setdefault('Accept', 'application/json') if 'body' in kwargs: kwargs['body'] = jsonutils.dump_as_bytes(kwargs['body']) resp, body_iter = self._http_request(url, method, **kwargs) content_type = resp.headers.get('Content-Type') if (resp.status_code in (http_client.NO_CONTENT, http_client.RESET_CONTENT) or content_type is None): return resp, list() if 'application/json' in content_type: body = ''.join([chunk for chunk in body_iter]) try: body = jsonutils.loads(body) except ValueError: LOG.error('Could not decode response body as JSON') else: body = None return resp, body def raw_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/octet-stream') return self._http_request(url, method, **kwargs) class VerifiedHTTPSConnection(six.moves.http_client.HTTPSConnection): """httplib-compatible connection using client-side SSL authentication :see http://code.activestate.com/recipes/ 577548-https-httplib-client-connection-with-certificate-v/ """ def __init__(self, host, port, key_file=None, cert_file=None, ca_file=None, timeout=None, insecure=False): six.moves.http_client.HTTPSConnection.__init__(self, host, port, key_file=key_file, cert_file=cert_file) self.key_file = key_file self.cert_file = cert_file if ca_file is not None: self.ca_file = ca_file else: self.ca_file = self.get_system_ca_file() self.timeout = timeout self.insecure = insecure def connect(self): """Connect to a host on a given (SSL) port. If ca_file is pointing somewhere, use it to check Server Certificate. Redefined/copied and extended from httplib.py:1105 (Python 2.6.x). This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to ssl.wrap_socket(), which forces SSL to check server certificate against our client certificate. """ sock = socket.create_connection((self.host, self.port), self.timeout) if self._tunnel_host: self.sock = sock self._tunnel() if self.insecure is True: kwargs = {'cert_reqs': ssl.CERT_NONE} else: kwargs = {'cert_reqs': ssl.CERT_REQUIRED, 'ca_certs': self.ca_file} if self.cert_file: kwargs['certfile'] = self.cert_file if self.key_file: kwargs['keyfile'] = self.key_file self.sock = ssl.wrap_socket(sock, **kwargs) @staticmethod def get_system_ca_file(): """Return path to system default CA file.""" # Standard CA file locations for Debian/Ubuntu, RedHat/Fedora, # Suse, FreeBSD/OpenBSD ca_path = ['/etc/ssl/certs/ca-certificates.crt', '/etc/pki/tls/certs/ca-bundle.crt', '/etc/ssl/ca-bundle.pem', '/etc/ssl/cert.pem'] for ca in ca_path: if os.path.exists(ca): return ca return None class SessionClient(VersionNegotiationMixin, adapter.LegacyJsonAdapter): """HTTP client based on Keystone client session.""" def __init__(self, os_ironic_api_version, api_version_select_state, max_retries, retry_interval, endpoint, **kwargs): self.os_ironic_api_version = os_ironic_api_version self.api_version_select_state = api_version_select_state self.conflict_max_retries = max_retries self.conflict_retry_interval = retry_interval self.endpoint = endpoint super(SessionClient, self).__init__(**kwargs) def _parse_version_headers(self, resp): return self._generic_parse_version_headers(resp.headers.get) def _make_simple_request(self, conn, method, url): endpoint_filter = { 'interface': self.interface, 'service_type': self.service_type, 'region_name': self.region_name } # NOTE: conn is self.session for this class return conn.request(url, method, raise_exc=False, user_agent=USER_AGENT, endpoint_filter=endpoint_filter) @with_retries def _http_request(self, url, method, **kwargs): # NOTE(TheJulia): self.os_ironic_api_version is reset in # the self.negotiate_version() call if negotiation occurs. if (self.os_ironic_api_version and self.api_version_select_state == 'user' and (self.os_ironic_api_version == 'latest' or isinstance(self.os_ironic_api_version, list))): self.negotiate_version(self.session, None) kwargs.setdefault('user_agent', USER_AGENT) kwargs.setdefault('auth', self.auth) if isinstance(self.endpoint_override, six.string_types): kwargs.setdefault( 'endpoint_override', _trim_endpoint_api_version(self.endpoint_override) ) if getattr(self, 'os_ironic_api_version', None): kwargs['headers'].setdefault('X-OpenStack-Ironic-API-Version', self.os_ironic_api_version) endpoint_filter = kwargs.setdefault('endpoint_filter', {}) endpoint_filter.setdefault('interface', self.interface) endpoint_filter.setdefault('service_type', self.service_type) endpoint_filter.setdefault('region_name', self.region_name) resp = self.session.request(url, method, raise_exc=False, **kwargs) if resp.status_code == http_client.NOT_ACCEPTABLE: negotiated_ver = self.negotiate_version(self.session, resp) kwargs['headers']['X-OpenStack-Ironic-API-Version'] = ( negotiated_ver) return self._http_request(url, method, **kwargs) if resp.status_code >= http_client.BAD_REQUEST: error_json = _extract_error_json(resp.content) # NOTE(vdrok): exceptions from ironic controllers' _lookup methods # are constructed directly by pecan instead of wsme, and contain # only description field raise exc.from_response(resp, (error_json.get('faultstring') or error_json.get('description')), error_json.get('debuginfo'), method, url) elif resp.status_code in (http_client.MOVED_PERMANENTLY, http_client.FOUND, http_client.USE_PROXY): # Redirected. Reissue the request to the new location. location = resp.headers.get('location') resp = self._http_request(location, method, **kwargs) elif resp.status_code == http_client.MULTIPLE_CHOICES: raise exc.from_response(resp, method=method, url=url) return resp def json_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/json') kwargs['headers'].setdefault('Accept', 'application/json') if 'body' in kwargs: kwargs['data'] = jsonutils.dump_as_bytes(kwargs.pop('body')) resp = self._http_request(url, method, **kwargs) body = resp.content content_type = resp.headers.get('content-type', None) status = resp.status_code if (status in (http_client.NO_CONTENT, http_client.RESET_CONTENT) or content_type is None): return resp, list() if 'application/json' in content_type: try: body = resp.json() except ValueError: LOG.error('Could not decode response body as JSON') else: body = None return resp, body def raw_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/octet-stream') return self._http_request(url, method, **kwargs) def _construct_http_client(endpoint=None, session=None, token=None, auth_ref=None, os_ironic_api_version=DEFAULT_VER, api_version_select_state='default', max_retries=DEFAULT_MAX_RETRIES, retry_interval=DEFAULT_RETRY_INTERVAL, timeout=600, ca_file=None, cert_file=None, key_file=None, insecure=None, **kwargs): if session: kwargs.setdefault('service_type', 'baremetal') kwargs.setdefault('user_agent', 'python-ironicclient') kwargs.setdefault('interface', kwargs.pop('endpoint_type', None)) kwargs.setdefault('endpoint_override', endpoint) ignored = {'token': token, 'auth_ref': auth_ref, 'timeout': timeout != 600, 'ca_file': ca_file, 'cert_file': cert_file, 'key_file': key_file, 'insecure': insecure} dvars = [k for k, v in ignored.items() if v] if dvars: LOG.warning('The following arguments are ignored when using ' 'the session to construct a client: %s', ', '.join(dvars)) return SessionClient(session=session, os_ironic_api_version=os_ironic_api_version, api_version_select_state=api_version_select_state, max_retries=max_retries, retry_interval=retry_interval, endpoint=endpoint, **kwargs) else: if kwargs: LOG.warning('The following arguments are being ignored when ' 'constructing the client: %s'), ', '.join(kwargs) return HTTPClient(endpoint=endpoint, token=token, auth_ref=auth_ref, os_ironic_api_version=os_ironic_api_version, api_version_select_state=api_version_select_state, max_retries=max_retries, retry_interval=retry_interval, timeout=timeout, ca_file=ca_file, cert_file=cert_file, key_file=key_file, insecure=insecure) python-ironicclient-2.2.0/ironicclient/common/__init__.py0000666000175100017510000000000013232474343023620 0ustar zuulzuul00000000000000python-ironicclient-2.2.0/ironicclient/common/base.py0000666000175100017510000002012613232474373023011 0ustar zuulzuul00000000000000# Copyright 2012 OpenStack LLC. # 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. """ Base utilities to build API operation managers and objects on top of. """ import abc import copy import six import six.moves.urllib.parse as urlparse from ironicclient.common.apiclient import base from ironicclient import exc def getid(obj): """Wrapper to get object's ID. Abstracts the common pattern of allowing both an object or an object's ID (UUID) as a parameter when dealing with relationships. """ try: return obj.id except AttributeError: return obj @six.add_metaclass(abc.ABCMeta) class Manager(object): """Provides CRUD operations with a particular API.""" def __init__(self, api): self.api = api def _path(self, resource_id=None): """Returns a request path for a given resource identifier. :param resource_id: Identifier of the resource to generate the request path. """ return ('/v1/%s/%s' % (self._resource_name, resource_id) if resource_id else '/v1/%s' % self._resource_name) @abc.abstractproperty def resource_class(self): """The resource class """ @abc.abstractproperty def _resource_name(self): """The resource name. """ def _get(self, resource_id, fields=None): """Retrieve a resource. :param resource_id: Identifier of the resource. :param fields: List of specific fields to be returned. :raises exc.ValidationError: For invalid resource_id arg value. """ if not resource_id: raise exc.ValidationError( "The identifier argument is invalid. " "Value provided: {!r}".format(resource_id)) if fields is not None: resource_id = '%s?fields=' % resource_id resource_id += ','.join(fields) try: return self._list(self._path(resource_id))[0] except IndexError: return None def _get_as_dict(self, resource_id, fields=None): """Retrieve a resource as a dictionary :param resource_id: Identifier of the resource. :param fields: List of specific fields to be returned. :returns: a dictionary representing the resource; may be empty """ resource = self._get(resource_id, fields=fields) if resource: return resource.to_dict() else: return {} def _format_body_data(self, body, response_key): if response_key: try: data = body[response_key] except KeyError: return [] else: data = body if not isinstance(data, list): data = [data] return data def _list_pagination(self, url, response_key=None, obj_class=None, limit=None): """Retrieve a list of items. The Ironic API is configured to return a maximum number of items per request, (see Ironic's api.max_limit option). This iterates over the 'next' link (pagination) in the responses, to get the number of items specified by 'limit'. If 'limit' is None this function will continue pagination until there are no more values to be returned. :param url: a partial URL, e.g. '/nodes' :param response_key: the key to be looked up in response dictionary, e.g. 'nodes' :param obj_class: class for constructing the returned objects. :param limit: maximum number of items to return. If None returns everything. """ if obj_class is None: obj_class = self.resource_class if limit is not None: limit = int(limit) object_list = [] object_count = 0 limit_reached = False while url: resp, body = self.api.json_request('GET', url) data = self._format_body_data(body, response_key) for obj in data: object_list.append(obj_class(self, obj, loaded=True)) object_count += 1 if limit and object_count >= limit: # break the for loop limit_reached = True break # break the while loop and return if limit_reached: break url = body.get('next') if url: # NOTE(lucasagomes): We need to edit the URL to remove # the scheme and netloc url_parts = list(urlparse.urlparse(url)) url_parts[0] = url_parts[1] = '' url = urlparse.urlunparse(url_parts) return object_list def __list(self, url, response_key=None, body=None): resp, body = self.api.json_request('GET', url) data = self._format_body_data(body, response_key) return data def _list(self, url, response_key=None, obj_class=None, body=None): if obj_class is None: obj_class = self.resource_class data = self.__list(url, response_key=response_key, body=body) return [obj_class(self, res, loaded=True) for res in data if res] def _list_primitives(self, url, response_key=None): return self.__list(url, response_key=response_key) def _update(self, resource_id, patch, method='PATCH'): """Update a resource. :param resource_id: Resource identifier. :param patch: New version of a given resource, a dictionary or None. :param method: Name of the method for the request. """ url = self._path(resource_id) kwargs = {} if patch is not None: kwargs['body'] = patch resp, body = self.api.json_request(method, url, **kwargs) # PATCH/PUT requests may not return a body if body: return self.resource_class(self, body) def _delete(self, resource_id): """Delete a resource. :param resource_id: Resource identifier. """ self.api.raw_request('DELETE', self._path(resource_id)) @six.add_metaclass(abc.ABCMeta) class CreateManager(Manager): """Provides creation operations with a particular API.""" @abc.abstractproperty def _creation_attributes(self): """A list of required creation attributes for a resource type. """ def create(self, **kwargs): """Create a resource based on a kwargs dictionary of attributes. :param kwargs: A dictionary containing the attributes of the resource that will be created. :raises exc.InvalidAttribute: For invalid attributes that are not needed to create the resource. """ new = {} invalid = [] for (key, value) in kwargs.items(): if key in self._creation_attributes: new[key] = value else: invalid.append(key) if invalid: raise exc.InvalidAttribute( 'The attribute(s) "%(attrs)s" are invalid; they are not ' 'needed to create %(resource)s.' % {'resource': self._resource_name, 'attrs': '","'.join(invalid)}) url = self._path() resp, body = self.api.json_request('POST', url, body=new) if body: return self.resource_class(self, body) class Resource(base.Resource): """Represents a particular instance of an object (tenant, user, etc). This is pretty much just a bag for attributes. """ def to_dict(self): return copy.deepcopy(self._info) python-ironicclient-2.2.0/ironicclient/__init__.py0000666000175100017510000000153213232474343022343 0ustar zuulzuul00000000000000# Copyright 2013 Hewlett-Packard Development Company, L.P. # 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 pbr.version from ironicclient import client from ironicclient import exc as exceptions __version__ = pbr.version.VersionInfo('python-ironicclient').version_string() __all__ = ( 'client', 'exc', 'exceptions', ) python-ironicclient-2.2.0/test-requirements.txt0000666000175100017510000000124013232474343022005 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. hacking>=1.0.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 doc8>=0.6.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD requests-mock>=1.1.0 # Apache-2.0 mock>=2.0.0 # BSD Babel!=2.4.0,>=2.3.4 # BSD openstackdocstheme>=1.18.1 # Apache-2.0 reno>=2.5.0 # Apache-2.0 oslotest>=3.2.0 # Apache-2.0 sphinx!=1.6.6,>=1.6.2 # BSD testtools>=2.2.0 # MIT tempest>=17.1.0 # Apache-2.0 os-testr>=1.0.0 # Apache-2.0 ddt>=1.0.1 # MIT python-openstackclient>=3.12.0 # Apache-2.0 python-ironicclient-2.2.0/zuul.d/0000775000175100017510000000000013232474761016772 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/zuul.d/legacy-ironicclient-jobs.yaml0000666000175100017510000000245513232474343024541 0ustar zuulzuul00000000000000- job: name: ironicclient-dsvm-functional parent: legacy-dsvm-base irrelevant-files: - ^.*\.rst$ - ^doc/.*$ - ^releasenotes/.*$ required-projects: - openstack-infra/devstack-gate - openstack/ironic - openstack/python-ironicclient run: playbooks/legacy/ironicclient-dsvm-functional/run.yaml post-run: playbooks/legacy/ironicclient-dsvm-functional/post.yaml timeout: 4800 - job: name: ironicclient-tempest-dsvm-src # NOTE: We do not use 'legacy-ironic-dsvm-base' as it is simpler and # less confusing to define it all here and use 'legacy-dsvm-base'. parent: legacy-dsvm-base irrelevant-files: - ^test-requirements.txt$ - ^.*\.rst$ - ^doc/.*$ - ^ironicclient/tests/.*$ - ^releasenotes/.*$ - ^setup.cfg$ - ^tools/.*$ - ^tox.ini$ required-projects: - openstack-infra/devstack-gate - openstack/ironic - openstack/ironic-lib - openstack/ironic-python-agent - openstack/ironic-tempest-plugin - openstack/pyghmi - openstack/python-ironicclient - openstack/tempest - openstack/virtualbmc run: playbooks/legacy/ironicclient-tempest-dsvm-src/run.yaml post-run: playbooks/legacy/ironicclient-tempest-dsvm-src/post.yaml timeout: 10800 python-ironicclient-2.2.0/zuul.d/project.yaml0000666000175100017510000000037613232474343021330 0ustar zuulzuul00000000000000- project: name: openstack/python-ironicclient check: jobs: - ironicclient-dsvm-functional - ironicclient-tempest-dsvm-src gate: jobs: - ironicclient-dsvm-functional - ironicclient-tempest-dsvm-src python-ironicclient-2.2.0/doc/0000775000175100017510000000000013232474761016316 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/doc/source/0000775000175100017510000000000013232474761017616 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/doc/source/user/0000775000175100017510000000000013232474761020574 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/doc/source/user/create_command.rst0000666000175100017510000001443613232474343024275 0ustar zuulzuul00000000000000=================================================== Creating the Bare Metal service resources from file =================================================== It is possible to create a set of resources using their descriptions in JSON or YAML format. It can be done in one of three ways: 1. Using OpenStackClient bare metal plugin CLI's command ``openstack baremetal create``:: $ openstack -h baremetal create usage: openstack baremetal create [-h] [ ...] Create resources from files positional arguments: File (.yaml or .json) containing descriptions of the resources to create. Can be specified multiple times. 2. Using ironic CLI's ``ironic create`` command (deprecated, please use ``openstack baremetal create`` instead):: $ ironic help create The "ironic" CLI is deprecated and will be removed in the S* release. Please use the "openstack baremetal" CLI instead. usage: ironic create [ ...] Create baremetal resources (chassis, nodes, port groups and ports). The resources may be described in one or more JSON or YAML files. If any file cannot be validated, no resources are created. An attempt is made to create all the resources; those that could not be created are skipped (with a corresponding error message). Positional arguments: File (.yaml or .json) containing descriptions of the resources to create. Can be specified multiple times. 3. Programmatically using the Python API: .. autofunction:: ironicclient.v1.create_resources.create_resources :noindex: File containing Resource Descriptions ===================================== The resources to be created can be described either in JSON or YAML. A file ending with ``.json`` is assumed to contain valid JSON, and a file ending with ``.yaml`` is assumed to contain valid YAML. Specifying a file with any other extension leads to an error. The resources that can be created are chassis, nodes, port groups and ports. A chassis can contain nodes (and resources of nodes) definitions nested under ``"nodes"`` key. A node can contain port groups definitions nested under ``"portgroups"``, and ports definitions under ``"ports"`` keys. Ports can be also nested under port groups in ``"ports"`` key. The schema used to validate the supplied data is the following:: { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Schema for ironic resources file", "type": "object", "properties": { "chassis": { "type": "array", "items": { "type": "object" } }, "nodes": { "type": "array", "items": { "type": "object" } } }, "additionalProperties": False } More detailed description of the creation process can be seen in the following sections. Examples ======== Here is an example of the JSON file that can be passed to the ``create`` command:: { "chassis": [ { "description": "chassis 3 in row 23", "nodes": [ { "name": "node-3", "driver": "agent_ipmitool", "portgroups": [ { "name": "switch.cz7882.ports.1-2", "ports": [ { "address": "ff:00:00:00:00:00" }, { "address": "ff:00:00:00:00:01" } ] } ], "ports": [ { "address": "00:00:00:00:00:02" }, { "address": "00:00:00:00:00:03" } ] }, { "name": "node-4", "driver": "agent_ipmitool", "ports": [ { "address": "00:00:00:00:00:04" }, { "address": "00:00:00:00:00:01" } ] } ] } ], "nodes": [ { "name": "node-5", "driver": "pxe_ipmitool", "chassis_uuid": "74d93e6e-7384-4994-a614-fd7b399b0785", "ports": [ { "address": "00:00:00:00:00:00" } ] }, { "name": "node-6", "driver": "pxe_ipmitool" } ] } Creation Process ================ #. The client deserializes the files' contents and validates that the top-level dictionary in each of them contains only "chassis" and/or "nodes" keys, and their values are lists. The creation process is aborted if any failure is encountered in this stage. The rest of the validation is done by the ironic-api service. #. Each resource is created via issuing a POST request (with the resource's dictionary representation in the body) to the ironic-api service. In the case of nested resources (``"nodes"`` key inside chassis, ``"portgroups"`` key inside nodes, ``"ports"`` key inside nodes or portgroups), the top-level resource is created first, followed by the sub-resources. For example, if a chassis contains a list of nodes, the chassis will be created first followed by the creation of each node. The same is true for ports and port groups described within nodes. #. If a resource could not be created, it does not stop the entire process. Any sub-resources of the failed resource will not be created, but otherwise, the rest of the resources will be created if possible. Any failed resources will be mentioned in the response. python-ironicclient-2.2.0/doc/source/conf.py0000666000175100017510000000472213232474343021120 0ustar zuulzuul00000000000000# -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'openstackdocstheme', 'cliff.sphinxext', ] # openstackdocstheme options repository_name = 'openstack/python-ironicclient' bug_project = 'python-ironicclient' bug_tag = '' # autodoc generation is a bit aggressive and a nuisance when doing heavy # text edit cycles. # execute "export SPHINX_DEBUG=1" in your terminal to disable # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'python-ironicclient' copyright = u'OpenStack Foundation' # A list of ignored prefixes for module index sorting. modindex_common_prefix = ['ironicclient.'] # 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' # A list of glob-style patterns that should be excluded when looking for # source files. They are matched against the source file names relative to the # source directory, using slashes as directory separators on all platforms. exclude_patterns = ['api/ironicclient.tests.functional.*'] # -- 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'. #html_theme_path = ["."] #html_theme = '_theme' #html_static_path = ['_static'] html_theme = 'openstackdocs' html_last_updated_fmt = '%Y-%m-%d %H:%M' # 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 LLC', 'manual' ), ] autoprogram_cliff_application = 'openstack' python-ironicclient-2.2.0/doc/source/contributor/0000775000175100017510000000000013232474761022170 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/doc/source/contributor/testing.rst0000666000175100017510000000365713232474343024410 0ustar zuulzuul00000000000000.. _testing: ======= Testing ======= Python Guideline Enforcement ............................ All code has to pass the pep8 style guideline to merge into OpenStack, to validate the code against these guidelines you can run:: $ tox -e pep8 Unit Testing ............ It is strongly encouraged to run the unit tests locally under one or more test environments prior to submitting a patch. To run all the recommended environments sequentially and pep8 style guideline run:: $ tox You can also selectively pick specific test environments by listing your chosen environments after a -e flag:: $ tox -e py35,py27,pep8,pypy .. note:: Tox sets up virtual environment and installs all necessary dependencies. Sharing the environment with devstack testing is not recommended due to conflicting configuration with system dependencies. Functional Testing .................. Functional testing assumes the existence of the script run_functional.sh in the python-ironicclient/tools directory. The script run_functional.sh generates test.conf file. To run functional tests just run ./run_functional.sh. Also, the test.conf file could be created manually or generated from environment variables. It assumes the existence of an openstack cloud installation along with admin credentials. The test.conf file lives in ironicclient/tests/functional/ directory. To run functional tests in that way create test.conf manually and run:: $ tox -e functional An example test.conf file:: [functional] api_version = 1 os_auth_url=http://192.168.0.2:5000/v2.0/ os_username=admin os_password=admin os_project_name=admin If you are testing ironic in standalone mode, only the parameters 'auth_strategy', 'os_auth_token' and 'ironic_url' are required; all others will be ignored. An example test.conf file for standalone host:: [functional] auth_strategy = noauth os_auth_token = fake ironic_url = http://10.0.0.2:6385 python-ironicclient-2.2.0/doc/source/contributor/contributing.rst0000666000175100017510000000361413232474343025433 0ustar zuulzuul00000000000000.. _contributing: =================================== Contributing to python-ironicclient =================================== If you're interested in contributing to the python-ironicclient project, the following will help get you started. #openstack-ironic on Freenode IRC Network ----------------------------------------- There is a very active chat channel at irc://freenode.net/#openstack-ironic. This is usually the best place to ask questions and find your way around. IRC stands for Internet Relay Chat and it is a way to chat online in real time. You can ask a question and come back later to read the answer in the log files. Logs for the #openstack-ironic IRC channel are stored at http://eavesdrop.openstack.org/irclogs/%23openstack-ironic/. Contributor License Agreement ----------------------------- .. index:: single: license; agreement In order to contribute to the python-ironicclient project, you need to have signed OpenStack's contributor's agreement. .. seealso:: * https://docs.openstack.org/infra/manual/developers.html * https://wiki.openstack.org/wiki/CLA LaunchPad Project ----------------- Most of the tools used for OpenStack depend on a launchpad.net ID for authentication. After signing up for a launchpad account, join the "openstack" team to have access to the mailing list and receive notifications of important events. .. seealso:: * https://launchpad.net * https://launchpad.net/python-ironicclient * https://launchpad.net/~openstack Project Hosting Details ----------------------- Bug tracker https://launchpad.net/python-ironicclient Mailing list (prefix subjects with ``[ironic]`` for faster responses) http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev Code Hosting https://git.openstack.org/cgit/openstack/python-ironicclient Code Review https://review.openstack.org/#/q/status:open+project:openstack/python-ironicclient,n,z python-ironicclient-2.2.0/doc/source/contributor/index.rst0000666000175100017510000000026413232474343024031 0ustar zuulzuul00000000000000============================================= python-ironicclient Contributor Documentation ============================================= .. toctree:: contributing testing python-ironicclient-2.2.0/doc/source/cli/0000775000175100017510000000000013232474761020365 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/doc/source/cli/osc/0000775000175100017510000000000013232474761021151 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/doc/source/cli/osc/v1/0000775000175100017510000000000013232474761021477 5ustar zuulzuul00000000000000python-ironicclient-2.2.0/doc/source/cli/osc/v1/index.rst0000666000175100017510000000146213232474343023341 0ustar zuulzuul00000000000000Command Reference ================= List of released CLI commands available in openstack client. These commands can be referenced by doing ``openstack help baremetal``. ================= baremetal chassis ================= .. autoprogram-cliff:: openstack.baremetal.v1 :command: baremetal chassis * ================ baremetal driver ================ .. autoprogram-cliff:: openstack.baremetal.v1 :command: baremetal driver * ============== baremetal node ============== .. autoprogram-cliff:: openstack.baremetal.v1 :command: baremetal node * ============== baremetal port ============== .. autoprogram-cliff:: openstack.baremetal.v1 :command: baremetal port * ================ baremetal volume ================ .. autoprogram-cliff:: openstack.baremetal.v1 :command: baremetal volume * python-ironicclient-2.2.0/doc/source/cli/osc_plugin_cli.rst0000666000175100017510000000500713232474343024110 0ustar zuulzuul00000000000000==================================================== ``openstack baremetal`` Command-Line Interface (CLI) ==================================================== .. program:: openstack baremetal .. highlight:: bash Synopsis ======== :program:`openstack [options] baremetal` [command-options] :program:`openstack help baremetal` Description =========== The OpenStack Client plugin interacts with the Bare Metal service through the ``openstack baremetal`` command line interface (CLI). To use the ``openstack`` CLI, the OpenStackClient (python-openstackclient) package must be installed. There are two ways to do this: * along with this python-ironicclient package:: $ pip install python-ironicclient[cli] * directly:: $ pip install python-openstackclient To use the CLI, you must provide your OpenStack username, password, project, and auth endpoint. You can use configuration options ``--os-username``, ``--os-password``, ``--os-project-id`` (or ``--os-project-name``), and ``--os-auth-url``, or set the corresponding environment variables:: $ export OS_USERNAME=user $ export OS_PASSWORD=password $ export OS_PROJECT_NAME=project # or OS_PROJECT_ID $ export OS_PROJECT_DOMAIN_ID=default $ export OS_USER_DOMAIN_ID=default $ export OS_IDENTITY_API_VERSION=3 $ export OS_AUTH_URL=http://auth.example.com:5000/identity This CLI is provided by python-openstackclient and osc-lib projects: * https://git.openstack.org/openstack/python-openstackclient * https://git.openstack.org/openstack/osc-lib Getting help ============ To get a list of available (sub)commands and options, run:: $ openstack help baremetal To get usage and options of a command, run:: $ openstack help baremetal Examples ======== Get information about the openstack baremetal node create command:: $ openstack help baremetal node create Get a list of available drivers:: $ openstack baremetal driver list Enroll a node with the ``ipmi`` driver:: $ openstack baremetal node create --driver ipmi --driver-info ipmi_address=1.2.3.4 Get a list of nodes:: $ openstack baremetal node list The baremetal API version can be specified via: * environment variable OS_BAREMETAL_API_VERSION:: $ export OS_BAREMETAL_API_VERSION=1.25 * or optional command line argument --os-baremetal-api-version:: $ openstack baremetal port group list --os-baremetal-api-version 1.25 Command Reference ================= .. toctree:: :glob: :maxdepth: 3 osc/v1/* python-ironicclient-2.2.0/doc/source/cli/ironic_client.rst0000666000175100017510000000564513232474343023750 0ustar zuulzuul00000000000000======================================= ``ironic`` Command-Line Interface (CLI) ======================================= .. program:: ironic .. highlight:: bash SYNOPSIS ======== :program:`ironic` [options] [command-options] :program:`ironic help` :program:`ironic help` DESCRIPTION =========== .. WARNING:: The :program:`ironic` command-line interface is deprecated; no new features will be added. This CLI will be removed in the S* release. The `openstack baremetal `_ command-line interface should be used instead. The :program:`ironic` command-line interface (CLI) interacts with the OpenStack Bare Metal Service (Ironic). In order to use the CLI, you must provide your OpenStack username, password, project (historically called tenant), and auth endpoint. You can use configuration options ``--os-username``, ``--os-password``, ``--os-tenant-id`` (or ``--os-tenant-name``), and ``--os-auth-url``, or set the corresponding environment variables:: $ export OS_USERNAME=user $ export OS_PASSWORD=password $ export OS_PROJECT_ID=b363706f891f48019483f8bd6503c54b # or OS_PROJECT_NAME $ export OS_PROJECT_NAME=project # or OS_PROJECT_ID $ export OS_AUTH_URL=http://auth.example.com:5000/v2.0 The command-line tool will attempt to reauthenticate using the provided credentials for every request. You can override this behavior by manually supplying an auth token using ``--ironic-url`` and ``--os-auth-token``, or by setting the corresponding environment variables:: $ export IRONIC_URL=http://ironic.example.org:6385/ $ export OS_AUTH_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155 Since Keystone can return multiple regions in the Service Catalog, you can specify the one you want with ``--os-region-name`` or set the following environment variable. (It defaults to the first in the list returned.) :: export OS_REGION_NAME=region Ironic CLI supports bash completion. The command-line tool can automatically fill partially typed commands. To use this feature, source the below file (available at https://git.openstack.org/cgit/openstack/python-ironicclient/tree/tools/ironic.bash_completion) to your terminal and then bash completion should work:: $ . ironic.bash_completion To avoid doing this every time, add this to your ``.bashrc`` or copy the ironic.bash_completion file to the default bash completion scripts directory on your linux distribution. OPTIONS ======= To get a list of available (sub)commands and options, run:: $ ironic help To get usage and options of a command, run:: $ ironic help EXAMPLES ======== Get information about the node-create command:: $ ironic help node-create Get a list of available drivers:: $ ironic driver-list Enroll a node with the ``ipmi`` driver, specifying the IPMI address:: $ ironic node-create -d ipmi -i ipmi_address=1.2.3.4 Get a list of nodes:: $ ironic node-list python-ironicclient-2.2.0/doc/source/cli/index.rst0000666000175100017510000000024713232474343022227 0ustar zuulzuul00000000000000====================================== python-ironicclient User Documentation ====================================== .. toctree:: osc_plugin_cli ironic_client python-ironicclient-2.2.0/doc/source/index.rst0000666000175100017510000000131613232474343021456 0ustar zuulzuul00000000000000=========================================== Python Bindings to the OpenStack Ironic API =========================================== This is a client for the OpenStack `Ironic`_ API. It provides: * a Python API: the ``ironicclient`` module, and * two command-line interfaces: ``openstack baremetal`` and ``ironic`` (deprecated, please use ``openstack baremetal`` instead). Contents ======== .. toctree:: :maxdepth: 2 api_v1 cli/index user/create_command contributor/index Release Notes Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` .. _Ironic: https://wiki.openstack.org/wiki/Ironic python-ironicclient-2.2.0/doc/source/api_v1.rst0000666000175100017510000000705113232474343021530 0ustar zuulzuul00000000000000.. _api_v1: ======================= ironicclient Python API ======================= The ironicclient python API lets you access ironic, the OpenStack Bare Metal Provisioning Service. For example, to manipulate nodes, you interact with an `ironicclient.v1.node`_ object. You obtain access to nodes via attributes of the `ironicclient.v1.client.Client`_ object. Usage ===== Get a Client object ------------------- First, create an `ironicclient.v1.client.Client`_ instance by passing your credentials to `ironicclient.client.get_client()`_. By default, the Bare Metal Provisioning system is configured so that only administrators (users with 'admin' role) have access. .. note:: Explicit instantiation of `ironicclient.v1.client.Client`_ may cause errors since it doesn't verify provided arguments, using `ironicclient.client.get_client()` is preferred way to get client object. There are two different sets of credentials that can be used:: * ironic endpoint and auth token * Identity Service (keystone) credentials Using ironic endpoint and auth token .................................... An auth token and the ironic endpoint can be used to authenticate:: * os_auth_token: authentication token (from Identity Service) * ironic_url: ironic API endpoint, eg http://ironic.example.org:6385/v1 To create the client, you can use the API like so:: >>> from ironicclient import client >>> >>> kwargs = {'os_auth_token': '3bcc3d3a03f44e3d8377f9247b0ad155', >>> 'ironic_url': 'http://ironic.example.org:6385/'} >>> ironic = client.get_client(1, **kwargs) Using Identity Service (keystone) credentials ............................................. These Identity Service credentials can be used to authenticate:: * os_username: name of user * os_password: user's password * os_auth_url: Identity Service endpoint for authorization * insecure: Boolean. If True, does not perform X.509 certificate validation when establishing SSL connection with identity service. default: False (optional) * os_tenant_{name|id}: name or ID of tenant To create a client, you can use the API like so:: >>> from ironicclient import client >>> >>> kwargs = {'os_username': 'name', >>> 'os_password': 'password', >>> 'os_auth_url': 'http://keystone.example.org:5000/', >>> 'os_project_name': 'project'} >>> ironic = client.get_client(1, **kwargs) Perform ironic operations ------------------------- Once you have an ironic `Client`_, you can perform various tasks:: >>> ironic.driver.list() # list of drivers >>> ironic.node.list() # list of nodes >>> ironic.node.get(node_uuid) # information about a particular node When the `Client`_ needs to propagate an exception, it will usually raise an instance subclassed from `ironicclient.exc.BaseException`_ or `ironicclient.exc.ClientException`_. Refer to the modules themselves, for more details. ironicclient Modules ==================== .. toctree:: :maxdepth: 1 modules .. _ironicclient.v1.node: api/ironicclient.v1.node.html#ironicclient.v1.node.Node .. _ironicclient.v1.client.Client: api/ironicclient.v1.client.html#ironicclient.v1.client.Client .. _Client: api/ironicclient.v1.client.html#ironicclient.v1.client.Client .. _ironicclient.client.get_client(): api/ironicclient.client.html#ironicclient.client.get_client .. _ironicclient.exc.BaseException: api/ironicclient.exc.html#ironicclient.exc.BaseException .. _ironicclient.exc.ClientException: api/ironicclient.exc.html#ironicclient.exc.ClientException