././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6955147 sushy-5.5.0/0000775000175000017500000000000000000000000012717 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/.coveragerc0000664000175000017500000000020200000000000015032 0ustar00zuulzuul00000000000000[run] branch = True source = sushy omit = *tests* [report] ignore_errors = True omit = *tests* [html] directory = cover ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/.mailmap0000664000175000017500000000013100000000000014333 0ustar00zuulzuul00000000000000# Format is: # # ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/.pre-commit-config.yaml0000664000175000017500000000315700000000000017206 0ustar00zuulzuul00000000000000--- repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: - id: trailing-whitespace - id: mixed-line-ending args: ['--fix', 'lf'] exclude: | (?x)( .*.svg$| ) - id: fix-byte-order-marker - id: check-merge-conflict - id: debug-statements - id: check-json files: .*\.json$ - id: check-yaml files: .*\.(yaml|yml)$ exclude: releasenotes/.*$ - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.5.4 hooks: - id: remove-tabs exclude: '.*\.(svg)$' - repo: https://opendev.org/openstack/hacking rev: 6.1.0 hooks: - id: hacking additional_dependencies: [] exclude: '^(doc|releasenotes|tools)/.*$' - repo: https://github.com/codespell-project/codespell rev: v2.2.6 hooks: - id: codespell args: [--write-changes] - repo: https://github.com/sphinx-contrib/sphinx-lint rev: v1.0.0 hooks: - id: sphinx-lint args: [--enable=default-role] files: ^doc/|releasenotes|api-ref - repo: https://opendev.org/openstack/bashate rev: 2.1.0 hooks: - id: bashate args: ["-iE006,E044", "-eE005,E042"] name: bashate description: This hook runs bashate for linting shell scripts entry: bashate language: python types: [shell] - repo: https://github.com/PyCQA/doc8 rev: v1.1.2 hooks: - id: doc8 - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.7.3 hooks: - id: ruff args: ['--fix', '--unsafe-fixes'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/.stestr.conf0000664000175000017500000000005400000000000015167 0ustar00zuulzuul00000000000000[DEFAULT] test_path=./sushy/tests top_dir=. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655096.0 sushy-5.5.0/AUTHORS0000664000175000017500000000625300000000000013775 0ustar00zuulzuul00000000000000Aija Jaunteva Aija Jaunteva Aija Jauntēva Aleksandra Bezborodova Alexon Oliveira Andreas Jaeger Anshul Jain Arne Wiebalck Arundhati Surpur Bill Dodd Bob Fournier Christopher Dearborn Debayan Ray Derek Higgins Dmitry Tantsur Dmitry Tantsur Doug Goldstein Doug Hellmann Fabian Wiesel Fedor Tarasenko Gabriela Soria Ghanshyam Mann Ha Manh Dong Hervé Beraud Ilya Etingof Iury Gregory Melo Ferreira Iury Gregory Melo Ferreira Jacob Anders James E. Blair Javier Pena Jay Faulkner Jens Sandmann John L. Villalovos Julia Kreger Kafilat Adeleke Kaifeng Wang Kamil Gustab Kamlesh Chauvhan LiZekun <2954674728@qq.com> Lin Yang Lucas Alvares Gomes Manuel Schönlaub Manuel Schönlaub Mark Goddard Nate Potter Nguyen Van Trung Nisha Agarwal OpenStack Release Bot Riccardo Pittau Richard G. Pioso Richard Pioso Ruby Loo Samuel Kunkel Sayali Kutwal Scott Tran Sean McGinnis Sharpz7 Shivanand Tendulker Steve Baker Takashi Kajinami Takashi Natsume Thomas Goirand Vanou Ishii Varsha Vu Cong Tuan Winicius Silva Youngjun Yusef Shaban ajya ankit chengebj5238 dnuka ghanshyam inspurericzhang jinxingfang kesper khansa liuwei loooosy maaoyu melissaml paresh-sao ricolin wangqi ya.wang yangyawei Ümit Seren ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/CONTRIBUTING.rst0000664000175000017500000000213200000000000015356 0ustar00zuulzuul00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in this page: http://docs.openstack.org/infra/manual/developers.html If you already have a good understanding of how the system works and your OpenStack accounts are set up, you can skip to the development workflow section of this documentation to learn how changes to OpenStack should be submitted for review via the Gerrit tool: http://docs.openstack.org/infra/manual/developers.html#development-workflow Sushy is a project under the Bare Metal (ironic) program and shares many common elements of the developer experience with ironic. You can find useful information about running unit tests or integrated testing environments in the ironic developer quickstart: https://docs.openstack.org/ironic/latest/contributor/dev-quickstart.html Bugs should be filed in launchpad, not GitHub: https://bugs.launchpad.net/sushy This repo is mirrored to Github. Issues, Pull Requests, and other Github features of this repo are not used. https://opendev.org/openstack/sushy is the canonical version of this repo. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655096.0 sushy-5.5.0/ChangeLog0000664000175000017500000004642700000000000014506 0ustar00zuulzuul00000000000000CHANGES ======= 5.5.0 ----- * trivial: fix variable in log message * enable pyupgrade via ruff to Python 3.7 5.4.0 ----- * enable flake8 logging checks in ruff * reno: Update master for unmaintained/2023.1 * enable pycodestyle and pyflakes checks in ruff * change ambiguous variable name * enable ruff with flake8-bandit rules * ensure all requests calls have a timeout * ignore open coded password from flake8-bandit * switch to pre-commit * trim trailing whitespace * Add Port resource * Provide vmedia username and password if required * Drop unnecessary 'x' bit from doc config file 5.3.0 ----- * bump pbr to match what pyproject.toml requests * Remove Python 3.8 support * contributor: links to ironic dev docs, launchpad * Fix setting "HttpBootUri" attributes * add pyproject.toml to support pip 23.1 * Make default request timeout configurable on Connector * Update master for stable/2024.2 * fix spelling and make codespell pass * EthernetInterface without an Id * add a link to the release notes in the README * drop usage of pkg\_resource for newer python compat * Fix versions in release notes 5.2.0 ----- * When ManagedBy attribute is missing from System retry with Managers * Name is not mandatory for Storage/StorageControllers * Adds Ethernet Interfaces for manager * avoid MAC addresses that are the empty string * Log the response when unable to parse JSON 5.1.1 ----- * Fix link to bugs * Trivial: fix duplicated docstring 5.1.0 ----- * reno: Update master for unmaintained/zed * Handle NotAcceptable when Accept-Encoding: identity is not allowed * reno: Update master for unmaintained/xena * reno: Update master for unmaintained/wallaby * reno: Update master for unmaintained/victoria * refectoring: Fix parameter and annotation mismatch * Update master for stable/2024.1 5.0.0 ----- * reno: Update master for unmaintained/yoga * Force constraints when installing a package during tox test * [codespell] Adding CI target for Tox Codespell * [codespell] Adding Tox Target for Codespell * [codespell] Fixing Spelling Mistakes * Update supported python versions 4.8.0 ----- * Allows System to access VirtualMedia in Sushy * Handle a different related properties for missing TransferProtocolType * Add release version to release notes * Handle exceptions after re-authentication * Remove version field from iLO error * Add a boot progress indicator 4.7.0 ----- * Adds an option for setting the http boot uri 4.6.0 ----- * Fix missing ETag when patching Redfish resource * Update master for stable/2023.2 * Fix wrong \_get\_registry logic in ResourceBase 4.5.1 ----- * Requests must always have a read/connect timeout * Handle session-uri in body 4.5.0 ----- * Retry on ilo state error * Update pep8 dep hacking to latest: v6 * Handle TransferMethod in vmedia insertion * Add TransferProtocolType for any general error that mentions it * Exclude all files starting with . from flake8 tests * Update master for stable/2023.1 * Handle non-default language for registries 4.4.2 ----- * workaround: requests verify handling if env is set * Remove setuptools workaround * Fix tox4 and setuptools errors * Fix exceeding retries * Fix volume deletion on newer iDRACs 4.4.1 ----- * Handle proper code\_status in unit test * Handle a different error code for missing TransferProtocolType * Retry on iDRAC SYS518 errors for all requests * Fix setting boot related attributes 4.4.0 ----- * Make server connection retries configurable * Increase server side retries * Improve resiliency of eTag handling * Update release versions for yoga and zed * Fix misuse of assertTrue * Add Python3 antelope unit tests * Update master for stable/zed * Fix misuse of assertIsNone 4.3.0 ----- * Capture requests errors 4.2.2 ----- * Do not send compression as acceptable encoding * Better logging when registries are not available 4.2.1 ----- * Handle mismatch between registry Identity and the way it's referred to * Add new Storage controllers * Handle AccessError with Basic Auth instead of "reauth" * Replace the netboot job with a local-boot one * Remove unicode literal from code * Update jobs names 4.2.0 ----- * Drop lower-constraints.txt and its testing * Add Python3 zed unit tests * Update master for stable/yoga * Fix session authentication issues 4.1.1 ----- * Re-trying InsertMedia call with TransferProtocolType if required * Add missing \_\_init\_\_.py * Follow up to Attempt using Redfish settings resource to get URI * Attempt using Redfish settings resource to get URI 4.1.0 ----- * Stop logging MessageRegistry objects * Re-add python 3.6/3.7 in classifier * Revert "Raise an AccessError with SessionService init" * [trivial] fix Xena release versions * Use Etag in precondition header when setting boot options * Use only Yoga tests * Raise an AccessError with SessionService init * Test python 3.6 for distributions compatibility 4.0.0 ----- * Finalize the enum migration * Updating yoga tested python versions in classifier * Migrate System Network to enums * Migrate Fabric to enums * Migrate Chassis to enums * Support for creating and deleting virtual media TLS certificates * [Trivial] Fix docstring typo: s/SECORE/SECURE/g * Migrate Manager constants to enums * Migrate CompositionService, EventService and TaskService to enums * Run Ironic unit tests to avoid regressions * Add support for additional network resources * Add support for NetworkAdapter resources * Migrate System Storage constants to enums * Migrate System constants to enums * Changing boot device string for vmedia on SuperMicro * Handle weak Etags * Add basic support for CertificateService * Add some VirtualMedia fields from 1.3.0 and 1.4.0 * Migrate Protocol constants to enums * Migrate common constants to enums * Prepare the ground to use enums instead of strings * Remove deprecated Task monitors and Volume methods * Add revision for drive resource * Add Python3 yoga unit tests * Update master for stable/xena 3.12.0 ------ * Support credentials for VirtualMedia URI * Change default value: insert\_media write\_protected 3.11.1 ------ * Add lower-constraints job to current development branch 3.11.0 ------ * Removing optional fields from insert\_media payload * Increase version of hacking and pycodestyle 3.10.0 ------ * Fix Context for EventDestination * Fix OEM required attribute parsing * Add resource root property * Add Drive.volumes * Fix Processor.sub\_processors * Protect Connector against empty auth object * Update min version of tox to use allowlist 3.9.1 ----- * Fix incorrect formatting and a Python 3.10 failure 3.9.0 ----- * Use TOX\_CONSTRAINTS\_FILE * EventService support * [trivial] add wallaby versions to release notes 3.8.0 ----- * Add support for BIOS Attribute Registry * setup.cfg: Replace dashes with underscores * Implement fallback method for virtual media * [doc] Fix formating issue * Update venv entry in tox.ini * Add Python3 xena unit tests * Update master for stable/wallaby * Ensure Content-Type header is set when required 3.7.0 ----- * Follow up TaskMonitor refactor * Refactor TaskMonitor and update Volume methods * Don't log ERROR if GET of /redfish/v1/SessionService fails * Fix deprecation on collections.MutableMapping * Automatically retry HTTP 5xx on GET requests * Initial support for secure boot databases 3.6.1 ----- * Fix TaskMonitor constructor calls in volume.py * Fix ExtendedInfo error handling for non-list item * Update minversion of tox * Add release version to release notes 3.6.0 ----- * Secure boot support: enabling/disabling and resetting keys * Fixes issue of redfish firmware update * Add doc/requirements * Add OperationApplyTime support to Volume methods * Remove lower-constraints job * Fix lower-constraints with the new pip resolver * Raise exception when async operation fails 3.5.0 ----- * Adds sushy library overview * Avoid running functional jobs for doc changes * Fixes a typo when accessing the connector in CompositionService * Add support to expose oem\_vendors from resource links * Adds basic support for TaskService to retrieve task information * Set safe version of hacking * Lower log severity for unknown type registries * Log only fields set in redfish response, not entire json * Fix #Bios.ResetBios for HTTP 400 Bad request error * Trivial: add missing \_\_init\_\_.py to sushy/resources/taskservice * Log extended error information in addition to returning it * Make message parsing more resilient * Make Actions field in Volume resource optional * Fix l-c job * Add Python3 wallaby unit tests * Update master for stable/victoria 3.4.0 ----- * Allow monitoring progress of a firmware update * Add BIOS update apply time and maintenance window * Revert "Add BIOS update apply time and maintenance window" * Add BIOS update apply time and maintenance window * Use Sessions URL from root service if it's provided 3.3.1 ----- * Less scary warning when GET /redfish/v1/SessionService fails * Do not log passwords and auth tokens when using SessionService * Remove auth token header completely when error occurs * Add a CI job with UEFI+vmedia and clean up the job definitions * Include extended information in debugging output * Fix retrieving software & firmware inventory * Update version of hacking * Update system schema to 1.10 * Set min version of tox to 3.2.1 3.3.0 ----- * drop mock from lower-constraints * Add RAIDType properties to storage resources * Make Volume/VolumeCollection operations blocking * Remove python-subunit, testtools and testscenarios * Switch to newer openstackdocstheme and reno versions * Fix pdf build * Remove translation sections from setup.cfg * Fix OEM extension loading for different servers * Update lower-constraints.txt * Fix pep8 test * Restore default netboot boot option * Add py38 package metadata * Add import order check * Use unittest.mock instead of third party mock * Stop configuring install\_command in tox * Add Python3 victoria unit tests * Update master for stable/ussuri * Convert sushy tempest to dib * Cleanup py27 support * Switch to the new canonical constraints URL on master 3.2.0 ----- * [trivial] add reason why we skip W503 in pep8 check * Bump hacking to 3.0.0 * Explicitly set ramdisk type * Ignore failing message registry download * Lazily load message registries * Add \`Connector(..., response\_callback=None)\`\` parameter * Make MessageRegistryFile.Registry attribute non-required * Add \`set\_system\_boot\_options\` method * Fix incorrect usage of assertRaisesRegex in unit tests 3.1.0 ----- * Relax required Redfish fields handling * Fix 'None' field value processing * Automatically discover available system/manager/chassis * Handle incomplete messages in MessageRegistry * Add Task Monitor support for async operations * Enforce running tox with correct python version based on env * SSC.disks\_sizes\_bytes handle CapacityBytes is None * Stop using six library 3.0.0 ----- * Add OEM extension example script * Drop python 2.7 support and testing * Switch jobs to python3 * Fix typo in the section Enabling SSL * Add versions to release notes series * Update master for stable/train 2.0.0 ----- * Build pdf doc * Add conditional field matching * Change OEM extensions architecture * Unify OEM Actions with non-OEM Actions * Implements adapter checking * Retry Virtual Media eject action on HTTP 400 response * Cache message registries * Disable HTTP connection pooling * Add MappedListField * Make message registries available to all resources * Action #Bios.ResetBios fails as POST request has no body * Added changes to \`simple\_update\` on update service * Make UpdateService.simple\_update() operational * Fix exposed UpdateService constants * Low case \`ParamTypes\` in received \`MessageRegistry\` * Disregard registry files of unsupported types * Handle incomplete message registries * Refactor DurableName identifier and Protocol fields * Add \`\`Endpoint\`\` sub-resource to \`\`Fabric\`\` * Update Python 3 test runtimes for Train * Include OData-Version header in Redfish requests 1.9.0 ----- * Use collections.abc instead of collections when available * Enhance Storage models to support RAID config * Add @Redfish.Settings update status and expose it for BIOS * Update sphinx requirements * Cleanup for Standard message registry loading * Make Manager->Actions field optional * Add Power and Thermal resources to Chassis * update git.openstack.org to opendev * OpenDev Migration Patch * Deprecate System-specific \`IndicatorLED\` state constants * Dropping the py35 testing * Update model to support ApplyTime annotations * Adding Power resource schema * Expand Drive schema * Adding Thermal resource schema * Add settable \`IndicatorLED\` property to the \`Drive\` resource * Add settable \`IndicatorLED\` of \`System\` and \`Chassis\` * Add versions to release notes series * Add mappings for \`system\_type\` * Add \`FabricCollection\` and \`Fabric\` classes * Change sushy devstack job to python3 * Fix wrong default JsonDataReader() argument * Add public resource loading and message parsing * Add support for loading packaged standard registries * Update master for stable/stein 1.8.0 ----- * Add foundation for supporting Redfish OEMs * Introduce default value for \`transfer\_protocol\` parameter * Add support for ilo Virtual Media * Add support for the \`CompositionService\` resource * Add support for the \`UpdateService\` resource * Follow Up Zuulv3 * Move to zuulv3 * Introduce \`dateutil\` * Update the docstring of \`sub\_processors()\` * Update to public * Add missing tests * Add Chassis<->ComputerSystem/Manager linkage * Add System<->Manager linkage follow up * Add System<->Manager linkage * Change openstack-dev to openstack-discuss and update URL * Add \`ChassisCollection\` and \`Chassis\` classes * Cleanup JsonDataReader name * remove useless whitespces * Unify sushy models by Redfish schema bundle 1.7.0 ----- * Delete HTTP tokens on \`Sushy\` shutdown * [Trivial Fix] modify spelling errors of fulfill, for, containing * Change adapter to 'int\_or\_none' for processor properties * [Trivial Fix] modify spelling error of "committed" * Add support for loading resources from archive file * [Trivial Fix] modify spelling error of "resource" * Follow-up to 27c725c to move up \`\`cache\_clear\`\` * Requests session keyword arguments for sushy connector * Introduce \`\`cache\_it\`\` and \`\`cache\_clear\`\` * Fix crashing \`BasicAuth\` context manager * Remove stray unicode character from tox.ini * Add \`ProtocolFeaturesSupported\` property of the \`Root Service\` * Add product property to the root Sushy object * Update to use mapped field for Settings Message Severity field * Omit tests from code coverage run * Use templates for cover and lower-constraints * Return sizes of storage devices * Fix for MediaTypes in virtual media * Add Message Registry File resource * Cleanup names for message registry * Add storage and simple\_storage attr to system * Caching ResourceCollectionBase::get\_members() * Add a virtual media resource * add python 3.6 unit test job * switch documentation job to new PTI * import zuul job settings from project-config * Add system storage resource support * Cleanup docstring for removed etag param * Add Redfish Message Registry resource * Add DictionaryField class to resource base * Update reno for stable/rocky 1.6.0 ----- * Add system simple storage resource support * Add storage disk drive * Switch to use stestr for unit test * Cleanup unittest file loading * Remove etag from Bios * Hide Attribute Registry property in Bios * Introduce BIOS API 1.5.0 ----- * Change BootSourceOverrideMode from BIOS to Legacy * fix tox python3 overrides * Add reusable ActionField * tox: Use default Python 3 version, remove pypy * Create mappings for System Indicator LED 1.4.0 ----- * Gate fix: Cap hacking to avoid gate failure * Do not run functional (API) tests in the CI * Add storage volume * Add processor ProcessorId field and Status field * Update Launchpad references to Storyboard * Fix wrong message of invalid boot "enabled" parameter * add lower-constraints job * Add system status field * Updated from global requirements * Mark Systems/Managers/SessionService optional * Replace curly quotes with straight quotes * fix error url * Zuul: Remove project name * Restores sushy session functionality * Updated from global requirements * Update reno for stable/queens 1.3.0 ----- * Restore the default semantics of refresh() * Restore interface of Connector * Add support to accept custom connector object * Updated from global requirements * Implement Redfish Sessions * Update documentation related to 'refresh()' * Updated from global requirements * Updated from global requirements * Update method docstring * Indicating the location tests directory in oslo\_debug\_helper * Use the tempest plugin from openstack/ironic-tempest-plugin * Avoid tox\_install.sh for constraints support * Follow-up patch for 8fe2904a62b0f56dc3fc3fefc5a5a746911ce891 * Updated from global requirements * zuul: clean up job definition * Add Zuul v3 jobs in tree * Refining the resource refresh * Add ListField class to resource base 1.2.0 ----- * Adds EthernetInterface to the library * Add int\_or\_none adapter function * Add PUT method to connector * flake8: Enable some off-by-default checks * Updated from global requirements * Enable some off-by-default checks * Update reno for stable/pike * Updated from global requirements * Add DELETE method to connector * Replace HTTP numeric constants with http\_client constants * Update some tests to copy the dictionaries passed in 1.0.0 ----- * Correct sushy homepage * Minor spelling fix * Rework sushy documentation for doc migration * Change theme to openstackdocs * Removes unnecessary utf-8 encoding * Drop MANIFEST.in - it's not needed by pbr * Add manager resource * Updated from global requirements * Updated from global requirements * Change assertTrue(isinstance()) by optimal assert * Updated from global requirements 0.2.0 ----- * Declarative approach to parsing JSON fields * Updated from global requirements * Updated from global requirements * Fix working with requests Session object * Improve logging and error handling in base code * Add System Processor/Memory info * Properly support upper-constraints * Updated from global requirements 0.1.0 ----- * Remove the tools/ directory from the sushy tree * Sushy to adhere to the resource identifier portion of the spec * Redfish-simulator: Do not cache the connection * Refactor resource collection * Fix members identity extraction * Add docstrings to various methods * Change get\_allowed\_\*() methods return type from list to set * Separate documentation readme.rst * Fix hardcoded reset system path * Updated from global requirements * Libvirt-Mockup: Get vCPUs even if the domain is not running * [Fix gate]Update test requirement * Updated from global requirements * Refining test coverage * Libvirt-Mockup: Add support for getting the boot source target * Rework exceptions * Libvirt-Mockup: Turn off strict\_slashes on all routes * Create a tox target for running the libvirt simulator * Add link to the python documentation about the NullHandler * [libvirt-simulator] Use a real identity in links from System * Add SSL support to the mockup servers * Add a mockup for controlling VMs with libvirt * Updated from global requirements * Drop the six dependency for mockup\_server.py * Remove the REQUEST\_NUMBER variable from tools/mockup\_server.py * Relax the \_parse\_attributes() method from the System resource * Add a script to serve the Redfish mockup files * Fix documentation link * Add some logs and fix the get\_allowed\_\*() methods * Improve unit-tests * Initial commit ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/HACKING.rst0000664000175000017500000000020400000000000014511 0ustar00zuulzuul00000000000000Sushy Style Commandments ======================== Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/LICENSE0000664000175000017500000002363700000000000013737 0ustar00zuulzuul00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6955147 sushy-5.5.0/PKG-INFO0000644000175000017500000000420700000000000014015 0ustar00zuulzuul00000000000000Metadata-Version: 2.1 Name: sushy Version: 5.5.0 Summary: Sushy is a small Python library to communicate with Redfish based systems Home-page: https://docs.openstack.org/sushy/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org 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 :: Implementation :: CPython Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Requires-Python: >=3.9 License-File: LICENSE Requires-Dist: pbr>=6.0.0 Requires-Dist: requests>=2.14.2 Requires-Dist: python-dateutil>=2.7.0 Requires-Dist: stevedore>=1.29.0 Overview ======== Sushy is a Python library to communicate with `Redfish`_ based systems. The goal of the library is to be extremely simple, small, have as few dependencies as possible and be very conservative when dealing with BMCs by issuing just enough requests to it (BMCs are very flaky). Therefore, the scope of the library has been limited to what is supported by the `OpenStack Ironic `_ project. As the project grows and more features from `Redfish`_ are needed we can expand Sushy to fulfill those requirements. * Free software: Apache license * Includes Redfish registry files licensed under Creative Commons Attribution 4.0 License: https://creativecommons.org/licenses/by/4.0/ * Documentation: https://docs.openstack.org/sushy/latest/ * Usage: https://docs.openstack.org/sushy/latest/reference/usage.html * Source: https://opendev.org/openstack/sushy * Bugs: https://bugs.launchpad.net/sushy * Release Notes: https://docs.openstack.org/releasenotes/sushy/ .. _Redfish: http://www.dmtf.org/standards/redfish ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/README.rst0000664000175000017500000000206100000000000014405 0ustar00zuulzuul00000000000000Overview ======== Sushy is a Python library to communicate with `Redfish`_ based systems. The goal of the library is to be extremely simple, small, have as few dependencies as possible and be very conservative when dealing with BMCs by issuing just enough requests to it (BMCs are very flaky). Therefore, the scope of the library has been limited to what is supported by the `OpenStack Ironic `_ project. As the project grows and more features from `Redfish`_ are needed we can expand Sushy to fulfill those requirements. * Free software: Apache license * Includes Redfish registry files licensed under Creative Commons Attribution 4.0 License: https://creativecommons.org/licenses/by/4.0/ * Documentation: https://docs.openstack.org/sushy/latest/ * Usage: https://docs.openstack.org/sushy/latest/reference/usage.html * Source: https://opendev.org/openstack/sushy * Bugs: https://bugs.launchpad.net/sushy * Release Notes: https://docs.openstack.org/releasenotes/sushy/ .. _Redfish: http://www.dmtf.org/standards/redfish ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/bindep.txt0000664000175000017500000000014600000000000014722 0ustar00zuulzuul00000000000000# fonts-freefont-otf is needed for pdf docs builds with the 'xelatex' engine fonts-freefont-otf [doc] ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6075149 sushy-5.5.0/doc/0000775000175000017500000000000000000000000013464 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/doc/requirements.txt0000664000175000017500000000017700000000000016755 0ustar00zuulzuul00000000000000reno>=3.1.0 # Apache-2.0 sphinx>=2.0.0,!=2.1.0 # BSD openstackdocstheme>=2.2.1 # Apache-2.0 sphinxcontrib-apidoc>=0.2.0 # BSD ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6075149 sushy-5.5.0/doc/source/0000775000175000017500000000000000000000000014764 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/doc/source/conf.py0000664000175000017500000000575000000000000016272 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import os import sys sys.path.insert(0, os.path.abspath('../..')) # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinxcontrib.apidoc', #'sphinx.ext.intersphinx', 'openstackdocstheme' ] # 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 # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. copyright = '2016, OpenStack Foundation' # 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 = 'native' # openstackdocstheme options openstackdocs_repo_name = 'openstack/sushy' openstackdocs_use_storyboard = False openstackdocs_pdf_link = True # -- 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 = 'openstackdocs' # html_static_path = ['static'] # Output file base name for HTML help builder. htmlhelp_basename = 'sushydoc' # The openstackdocstheme 2.1.0 extension stopped always overriding latex_engine # to 'xelatex'. We need the 'xelatex' engine in order to handle some Unicode # characters we use in our feature classification matrix, like the "X" mark, so # we specify it here. latex_engine = 'xelatex' latex_use_xindy = False # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', 'doc-sushy.tex', 'Sushy Documentation', 'OpenStack Foundation', 'manual'), ] # Example configuration for intersphinx: refer to the Python standard library. #intersphinx_mapping = {'http://docs.python.org/': None} # -- sphinxcontrib.apidoc configuration -------------------------------------- apidoc_module_dir = '../../sushy' apidoc_output_dir = 'reference/api' apidoc_excluded_paths = [ 'tests', ] ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6075149 sushy-5.5.0/doc/source/contributor/0000775000175000017500000000000000000000000017336 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/doc/source/contributor/index.rst0000664000175000017500000000610600000000000021202 0ustar00zuulzuul00000000000000.. _contributing: ===================== Contributing to Sushy ===================== How to contribute ================= .. include:: ../../../CONTRIBUTING.rst Running a Redfish emulator ========================== Testing and/or developing Sushy without owning a real baremetal machine that supports the Redfish protocol is possible by running an emulator, the `sushy-tools`_ project ships with two emulators that can be used for this purpose. To install it run:: sudo pip install --user sushy-tools .. note:: Installing the dependencies requires libvirt development files. For example, run the following command to install them on Fedora:: sudo dnf install -y libvirt-devel Static emulator ~~~~~~~~~~~~~~~ After installing `sushy-tools`_ you will have a new CLI tool named ``sushy-static``. This tool creates a HTTP server to serve any of the `Redfish mockups `_. The files are static so operations like changing the boot device or the power state **will not** have any effect. But that should be enough for enabling people to test parts of the library. To use ``sushy-static`` we need the Redfish mockup files that can be downloaded from https://www.dmtf.org/standards/redfish, for example:: wget https://www.dmtf.org/sites/default/files/standards/documents/DSP2043_1.0.0.zip After the download, extract the files somewhere in the file-system:: unzip DSP2043_1.0.0.zip -d Now run ``sushy-static`` pointing to those files. For example to serve the ``DSP2043-server`` mockup files, run:: sushy-static --mockup-files /DSP2043-server Libvirt emulator ~~~~~~~~~~~~~~~~ The second emulator shipped by `sushy-tools`_ is the CLI tool named ``sushy-emulator``. This tool starts a ReST API that users can use to interact with virtual machines using the Redfish protocol. So operations such as changing the boot device or the power state will actually affect the virtual machines. This allows users to test the library in a more dynamic way. To run it do .. code-block:: sh sushy-emulator # Or, running with custom parameters sushy-emulator --port 8000 --libvirt-uri "qemu:///system" That's it, now you can test Sushy against the ``http://localhost:8000`` endpoint. Enabling SSL ~~~~~~~~~~~~ Both mockup servers supports `SSL`_ if you want Sushy with it. To set it up, first you need to generate key and certificate files with OpenSSL use following command:: openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 Start the mockup server passing the ``--ssl-certificate`` and ``--ssl-key`` parameters to it, for example:: sushy-emulator --ssl-key key.pem --ssl-certificate cert.pem Now to connect with `SSL`_ to the server use the ``verify`` parameter pointing to the certificate file when instantiating Sushy, for example: .. code-block:: python import sushy # Note the HTTP"S" s = sushy.Sushy('https://localhost:8000', verify='cert.pem', username='foo', password='bar') .. _SSL: https://en.wikipedia.org/wiki/Secure_Sockets_Layer .. _sushy-tools: https://opendev.org/openstack/sushy-tools ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/doc/source/index.rst0000664000175000017500000000173400000000000016632 0ustar00zuulzuul00000000000000.. sushy documentation master file, created by sphinx-quickstart on Tue Jul 9 22:26:36 2013. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. ================================= Welcome to Sushy's documentation! ================================= .. include:: ../../README.rst Features ======== * Abstraction around the SystemCollection and System resources (Basic server identification and asset information) * RAID in Redfish based Systems * Redfish Ethernet Interface * System mappings * System processor * Storage management * Systems power management (Both soft and hard; Including NMI injection) * Changing systems boot device, frequency (Once or permanently) and mode (UEFI or BIOS) * Chassis management * OEM extension * Virtual media management * Session Management Documentation ============= .. toctree:: :maxdepth: 1 install/index contributor/index reference/index * :ref:`genindex` ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6075149 sushy-5.5.0/doc/source/install/0000775000175000017500000000000000000000000016432 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/doc/source/install/index.rst0000664000175000017500000000030400000000000020270 0ustar00zuulzuul00000000000000================ Installing Sushy ================ At the command line:: $ pip install sushy Or, if you have virtualenvwrapper installed:: $ mkvirtualenv sushy $ pip install sushy ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6075149 sushy-5.5.0/doc/source/reference/0000775000175000017500000000000000000000000016722 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/doc/source/reference/index.rst0000664000175000017500000000046100000000000020564 0ustar00zuulzuul00000000000000======================= Sushy Library Reference ======================= Usage ===== .. toctree:: :maxdepth: 2 usage Sushy Python API Reference ========================== * :ref:`modindex` .. # api/modules is hidden since it's in the modindex link above. .. toctree:: :hidden: api/modules ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/doc/source/reference/usage.rst0000664000175000017500000002376400000000000020574 0ustar00zuulzuul00000000000000.. _usage: Using Sushy =========== To use sushy in a project: ----------------------------------------- Specifying an authentication type ----------------------------------------- There are three authentication objects. By default we use SessionOrBasicAuth. Authentication Modes: * auth.SessionOrBasicAuth: Use session based authentication. If we are unable to create a session we will fallback to basic authentication. * auth.BasicAuth: Use basic authentication only. * auth.SessionAuth: Use session based authentication only. .. code-block:: python import logging import sushy from sushy import auth # Enable logging at DEBUG level LOG = logging.getLogger('sushy') LOG.setLevel(logging.DEBUG) LOG.addHandler(logging.StreamHandler()) basic_auth = auth.BasicAuth(username='foo', password='bar') session_auth = auth.SessionAuth(username='foo', password='bar') session_or_basic_auth = auth.SessionOrBasicAuth(username='foo', password='bar') s = sushy.Sushy('http://localhost:8000/redfish/v1', auth=basic_auth) s = sushy.Sushy('http://localhost:8000/redfish/v1', auth=session_auth) s = sushy.Sushy('http://localhost:8000/redfish/v1', auth=session_or_basic_auth) # It is important to note that you can # call sushy without supplying an # authentication object. In that case we # will use the SessionOrBasicAuth authentication # object in an attempt to connect to all different # types of redfish servers. s = sushy.Sushy('http://localhost:8000/redfish/v1', username='foo', password='bar') ---------------------------------------- Creating and using a sushy system object ---------------------------------------- .. code-block:: python import logging import sushy # Enable logging at DEBUG level LOG = logging.getLogger('sushy') LOG.setLevel(logging.DEBUG) LOG.addHandler(logging.StreamHandler()) s = sushy.Sushy('http://localhost:8000/redfish/v1', username='foo', password='bar') # Get the Redfish version print(s.redfish_version) # Instantiate a system object sys_inst = s.get_system('/redfish/v1/Systems/437XR1138R2') # Using system collections # Instantiate a SystemCollection object sys_col = s.get_system_collection() # Print the ID of the systems available in the collection print(sys_col.members_identities) # Get a list of systems objects available in the collection sys_col_insts = sys_col.get_members() # Instantiate a system object, same as getting it directly # from the s.get_system() sys_inst = sys_col.get_member(sys_col.members_identities[0]) # Refresh the system collection object # # See below for more options on how to refresh resources. sys_col.refresh() # Using system actions # Power the system ON sys_inst.reset_system(sushy.ResetType.ON) # Get a list of allowed reset values print(sys_inst.get_allowed_reset_system_values()) # Refresh the system object (with all its sub-resources) sys_inst.refresh() # Alternatively, you can only refresh the resource if it is stale by passing # force=False: sys_inst.refresh(force=False) # A resource can be marked stale by calling invalidate. Note that its # subresources won't be marked as stale, and thus they won't be refreshed by # a call to refresh(force=False) sys_inst.invalidate() # Get the current power state print(sys_inst.power_state) # Set the next boot device to boot once from PXE in UEFI mode sys_inst.set_system_boot_source(sushy.BootSource.PXE, enabled=sushy.BootSourceOverrideEnabled.ONCE, mode=sushy.BootSourceOverrideMode.UEFI) # Get the current boot source information print(sys_inst.boot) # Get a list of allowed boot source target values print(sys_inst.get_allowed_system_boot_source_values()) # Get the memory summary print(sys_inst.memory_summary) # Get the processor summary print(sys_inst.processors.summary) ----------------------------------------- Creating and using a sushy manager object ----------------------------------------- .. code-block:: python import logging import sushy # Enable logging at DEBUG level LOG = logging.getLogger('sushy') LOG.setLevel(logging.DEBUG) LOG.addHandler(logging.StreamHandler()) s = sushy.Sushy('http://localhost:8000/redfish/v1', username='foo', password='bar') # Instantiate a manager object mgr_inst = s.get_manager('BMC') # Get the manager name & description print(mgr_inst.name) print(mgr_inst.description) # Using manager collections # Instantiate a ManagerCollection object mgr_col = s.get_manager_collection() # Print the ID of the managers available in the collection print(mgr_col.members_identities) # Get a list of manager objects available in the collection mgr_insts = mgr_col.get_members() # Instantiate a manager object, same as getting it directly # from the s.get_manager() mgr_inst = mgr_col.get_member(mgr_col.members_identities[0]) # Refresh the manager collection object mgr_col.invalidate() mgr_col.refresh() # Using manager actions # Get supported graphical console types print(mgr_inst.get_supported_graphical_console_types()) # Get supported serial console types print(mgr_inst.get_supported_serial_console_types()) # Get supported command shell types print(mgr_inst.get_supported_command_shell_types()) # Get a list of allowed manager reset values print(mgr_inst.get_allowed_reset_manager_values()) # Reset the manager mgr_inst.reset_manager(sushy.ResetType.FORCE_RESTART) # Refresh the manager object (with all its sub-resources) mgr_inst.refresh(force=True) # Using Virtual Media # Instantiate a VirtualMediaCollection object virtmedia_col = mgr_inst.virtual_media # Print the ID of the VirtualMedia available in the collection print(virtmedia_col.members_identities) # Get a list of VirtualMedia objects available in the collection virtmedia_insts = virtmedia_col.get_members() # Instantiate a VirtualMedia object virtmedia_inst = virtmedia_col.get_member( virtmedia_col.members_identities[0]) # Print out some of the VirtualMedia properties print(virtmedia_inst.name, virtmedia_inst.media_types) # Insert virtual media (invalidates virtmedia_inst contents) virtmedia_inst.insert_media('https://www.dmtf.org/freeImages/Sardine.img') # Refresh the resource to load actual contents virtmedia_inst.refresh() # Print out some of the VirtualMedia properties print(virtmedia_inst.image, virtmedia_inst.image_path, virtmedia_inst.inserted, virtmedia_inst.write_protected) # ... Boot the system off the virtual media... # Eject virtual media (invalidates virtmedia_inst contents) virtmedia_inst.eject_media() ----------------------------------------------- Creating and using a sushy client with Sessions ----------------------------------------------- .. code-block:: python import logging import sushy # Enable logging at DEBUG level LOG = logging.getLogger('sushy') LOG.setLevel(logging.DEBUG) LOG.addHandler(logging.StreamHandler()) s = sushy.Sushy('http://localhost:8000/redfish/v1', username='foo', password='bar') # Get the ComputerSystem object (if there is only one), otherwise # the identity must be provided as a path to the system. system = s.get_system() # A session is created automatically for you. # Print the boot field in the ComputerSystem. print(system.boot) # Upon session timeout, Sushy recreates the session based upon # provided credentials. If this fails, an exception is raised. # Explicitly request a session_key and session_uri. # This is not stored, but may be useful. session_key, session_uri = s.create_session(username='foo', password='bar') # Retrieve the session session = s.get_session(session_uri) # Delete the session session.delete() -------------------- Using OEM extensions -------------------- Before running this example, please make sure you have a Redfish BMC that includes the OEM piece for a specific vendor, as well as the Sushy OEM extension package installed in the system for the same vendor. You can check the presence of the OEM extension within each Redfish resource by specifying the vendor ID and search for them. In the following example, we are looking up "Acme" vendor extension to Redfish Manager resource. .. code-block:: python import sushy root = sushy.Sushy('http://localhost:8000/redfish/v1') # Instantiate a system object system = root.get_system('/redfish/v1/Systems/437XR1138R2') print('Working on system resource %s' % system.identity) for manager in system.managers: print('Using System manager %s' % manager.identity) # Get a list of OEM extension names for the system manager oem_vendors = manager.oem_vendors print('Listing OEM extension name(s) for the System ' 'manager %s' % manager.identity ) print(*oem_vendors, sep="\n") try: manager_oem = manager.get_oem_extension('Acme') except sushy.exceptions.OEMExtensionNotFoundError: print('ERROR: Acme OEM extension not found in ' 'Manager %s' % manager.identity) continue print('%s is an OEM extension of Manager %s' % (manager_oem.get_extension(), manager.identity)) # set boot device to a virtual media device image manager_oem.set_virtual_boot_device(sushy.VirtualMediaType.CD, manager=manager) If you do not have any real baremetal machine that supports the Redfish protocol you can look at the :ref:`contributing` page to learn how to run a Redfish emulator. For the OEM extension example, presently, both of the emulators (static/dynamic) do not expose any OEM; as a result, users may need to add manually some OEM resources to emulators' templates. It may be easier to start with a static emulator. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/pyproject.toml0000664000175000017500000000123100000000000015630 0ustar00zuulzuul00000000000000[build-system] requires = ["pbr>=6.0.0", "setuptools>=64.0.0"] build-backend = "pbr.build" [tool.doc8] ignore = ["D001"] [tool.ruff] line-length = 79 target-version = "py37" [tool.ruff.lint] select = [ "E", # pycodestyle (error) "F", # pyflakes "G", # flake8-logging-format "LOG", # flake8-logging "S", # flake8-bandit "UP", # pyupgrade ] [tool.ruff.lint.per-file-ignores] "sushy/tests/**/*.py" = [ "S104", # disable 'hardcoded_bind_all_interfaces' for tests "S105", # disable 'hardcoded_password_string' for tests "S106", # disable 'hardcoded_password_funcarg' for tests ] ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.5915148 sushy-5.5.0/releasenotes/0000775000175000017500000000000000000000000015410 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6355147 sushy-5.5.0/releasenotes/notes/0000775000175000017500000000000000000000000016540 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/.placeholder0000664000175000017500000000000000000000000021011 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/HttpBootUri-to-settings-6fcbdeed2b4e84df.yaml0000664000175000017500000000015700000000000026733 0ustar00zuulzuul00000000000000--- fixes: - | PATCH 'HttpBootUri' against SettingsURI if present. If not, fall back to System URI. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/accept-encoding-4646ea43998f80bd.yaml0000664000175000017500000000015300000000000024622 0ustar00zuulzuul00000000000000--- fixes: - | Adds a work-around for cases where ``Accept-Encoding: identity`` is not accepted. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/action-parameter-missing-7d234b96b5b1d81a.yaml0000664000175000017500000000036700000000000026546 0ustar00zuulzuul00000000000000--- fixes: - | Correctly handles error code ``Base.1.12.ActionParameterMissing`` when dealing with hardware that requires ``TransferProtocolType`` for virtual media operations and has different message in RelatedProperties response. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-apply-time-support-to-bios-315ebad429dcab3d.yaml0000664000175000017500000000067600000000000027752 0ustar00zuulzuul00000000000000--- features: - | Adds support for ``bios`` resource to allow specifying BIOS attribute update time and maintenance window when updating BIOS attributes using ``set_attribute`` or ``set_attributes``. The update is backward compatible and when new parameters not passed, they default to ``None``. Also adds ``maintenance_window`` for ``bios`` resource to expose default maintenance window set by the system if any. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-bios-bf69ac56c4ae8f50.yaml0000664000175000017500000000011300000000000023464 0ustar00zuulzuul00000000000000--- features: - | Adds support for the BIOS resource to the library. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-bios-update-status-cc59816c374b78e4.yaml0000664000175000017500000000023200000000000026057 0ustar00zuulzuul00000000000000--- features: - | ``Bios`` resource introduces ``update_status`` property that exposes the status and any errors of last BIOS attribute update. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-chassis-linkage-d8e567f9c791169d.yaml0000664000175000017500000000037200000000000025410 0ustar00zuulzuul00000000000000--- features: - | Establishes linkage between Chassis and ComputerSystem/Managers resources as references at sushy data abstraction level. That makes it possible to look up Chassis by Manager/ComputerSystem or any other way around. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-chassis-support-5b97daffe1c61a2b.yaml0000664000175000017500000000011700000000000025752 0ustar00zuulzuul00000000000000--- features: - | Adds support for the Chassis resource to the library. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-custom-connector-support-0a49c6649d5f7eaf.yaml0000664000175000017500000000017400000000000027501 0ustar00zuulzuul00000000000000--- features: - | Adds the ability to specify user-defined connector object on creation of a root Sushy instance. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-default-identity-10c5dd23bed0e915.yaml0000664000175000017500000000072000000000000025706 0ustar00zuulzuul00000000000000--- features: - | The ``get_system``, ``get_manager`` and ``get_chassis`` methods modified not to require the ``identity`` parameter referring to a particular resource instance. If ``identity`` is omitted, sushy will default to the only available resource for as long as it's single and therefore deterministic. The intent is to simplify user API by not requiring the consumer to discover available resources prior to requesting one. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-drive-led-97b687013fec88c9.yaml0000664000175000017500000000033000000000000024203 0ustar00zuulzuul00000000000000--- features: - | Adds the ``IndicatorLED`` property to the ``Drive`` resource. The state of the LED can be read and can be changed via the ``.set_indicator_led()`` method of the ``Drive`` sushy class. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-drive-revision-a0c069fff236479d.yaml0000664000175000017500000000012200000000000025337 0ustar00zuulzuul00000000000000--- features: - | Adds the ``Revision`` property to the ``Drive`` resource. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-drive-volumes-971d80644c3bd1e0.yaml0000664000175000017500000000014300000000000025103 0ustar00zuulzuul00000000000000--- features: - | Adds ``Drive.volumes`` property for list of volumes that drive is part of. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-endpoint-subresource-to-fabric-b03e5fd99ece1bf4.yaml0000664000175000017500000000033300000000000030636 0ustar00zuulzuul00000000000000--- features: - | Exposes the ``endpoint`` sub-resource from the ``fabric`` resource. ``endpoint`` represents the properties of an entity that sends or receives protocol defined messages over a transport. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-fabric-support-1520f7fcb0e12539.yaml0000664000175000017500000000011600000000000025236 0ustar00zuulzuul00000000000000--- features: - | Adds support for the Fabric resource to the library. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-http-boot-uri-support-5c25816e13ccdb27.yaml0000664000175000017500000000046200000000000026614 0ustar00zuulzuul00000000000000--- features: - | Adds functionality to the ``set_system_boot_options`` method permitting an ``http_boot_uri`` option to be set. - | Adds an ``http_boot_uri`` option to the System boot object, permitting an API client user to view what the BMC's redfish ``HttpBootUri`` setting is. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-initial-redfish-oem-extension-support-50c9849bb7b6b25c.yaml0000664000175000017500000000077500000000000031762 0ustar00zuulzuul00000000000000--- features: - | Adds foundation for supporting resource extensibility proposed as OEM extensibility in Redfish specification [1] to the library. * Provides an attribute 'oem_vendors' in Resource classes to discover the available OEM extensions. * Provides a method 'get_oem_extension()' in Resource classes to get the vendor defined resource OEM extension object, if discovered. [1] http://redfish.dmtf.org/schemas/DSP0266_1.1.html#resource-extensibility ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-mapped-list-field-04c671f7a73d83f6.yaml0000664000175000017500000000016300000000000025617 0ustar00zuulzuul00000000000000--- features: - | Adds a new field called ``MappedListField`` which supports a list of mapped instances. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-network-adapter-26d01d8d9fb1d7ad.yaml0000664000175000017500000000012500000000000025633 0ustar00zuulzuul00000000000000--- features: - | Adds support for the NetworkAdapter resource to the library. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-network-device-function-and-port-e880d8f461e3723d.yaml0000664000175000017500000000015400000000000030614 0ustar00zuulzuul00000000000000features: - | Adds support for the NetworkDeviceFunction and NetworkPort resource to the library. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-odata-version-header-96dc8179c0e2e9bd.yaml0000664000175000017500000000020200000000000026457 0ustar00zuulzuul00000000000000--- fixes: - | Improve interoperability by including the recommended OData-Version header in outgoing Redfish requests. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-partial-key-match-27bed73d577b1187.yaml0000664000175000017500000000071400000000000025635 0ustar00zuulzuul00000000000000--- features: - | Adds the ability to conditionally match sushy fields against received JSON object. The conditional matching is performed by a user-supplied callable which gets the key to consider (along with the value and potentially other details) and should indicate the the caller if the match occurred. The motivation behind this change is to accommodate malformed Redfish resource properties as observed in the OEM wilderness. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-port-e57ec6759ee70bf7.yaml0000664000175000017500000000011100000000000023450 0ustar00zuulzuul00000000000000--- features: - Adds support for the ``Port`` resource to the library. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-power-resource-e141ddf298673305.yaml0000664000175000017500000000007700000000000025220 0ustar00zuulzuul00000000000000--- features: - | Adds the Power resource to the Library.././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-processor-id-and-status-b81d4c6e6c14c25f.yaml0000664000175000017500000000013400000000000027136 0ustar00zuulzuul00000000000000--- features: - | Adds the processor status and id fields to the ``Processor`` class. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-raid-type-properties-2090da5bea37c660.yaml0000664000175000017500000000020200000000000026437 0ustar00zuulzuul00000000000000--- features: - | Add RAIDType property to the Volume resource and SupportedRAIDTypes property to the Storage resource. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-read-and-connect-timeout-9f7dc3ed63c192c8.yaml0000664000175000017500000000037200000000000027253 0ustar00zuulzuul00000000000000fixes: - Fixes bug where sushy would not pass a read/connect timeout through to requests when making requests to a redfish service. This means that an ill-timed failure could cause python processes calling sushy to freeze indefinitely. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-response-cb-65d448ee2690d0b2.yaml0000664000175000017500000000042000000000000024525 0ustar00zuulzuul00000000000000--- features: - | Adds optional ``response_callback`` parameter to ``Connector`` class that can be used by the application to receive vanilla HTTP messages in the course of running Redfish call. The intention is to facilitate Redfish exchange debugging. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-simple-storage-915464811737bb05.yaml0000664000175000017500000000024100000000000025020 0ustar00zuulzuul00000000000000--- features: - | Adds the "SimpleStorage" to the library. It also provides the max size available (in bytes) among all its directly attached devices. ././@PaxHeader0000000000000000000000000000021000000000000011446 xustar0000000000000000114 path=sushy-5.5.0/releasenotes/notes/add-storage-and-simple-storage-attributes-to-system-16e81f9b15b1897d.yaml 22 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-storage-and-simple-storage-attributes-to-system-16e81f9b15b1897d.0000664000175000017500000000070700000000000032726 0ustar00zuulzuul00000000000000--- features: - | Exposes the ``simple_storage`` and ``storage`` properties from system resource in sushy. * ``simple_storage`` property indicates a collection of storage controllers and their directly-attached devices associated with the system. * ``storage`` property refers to a collection of storage subsystem associated with system. Resources such as drives and volumes can be accessed from that subsystem. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-storage-da766d3dbf9fb385.yaml0000664000175000017500000000026600000000000024213 0ustar00zuulzuul00000000000000--- features: - | Adds the Storage resource to the library. It also provides the max size available (in bytes) of drives and volumes that can be accessed from storage. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-sushy-root-to-resources-1f221794557aa5fc.yaml0000664000175000017500000000020100000000000027065 0ustar00zuulzuul00000000000000--- features: - | Adds ``root`` property to Sushy resources to link to main Sushy object. Empty for Sushy root itself. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-system-bootprogress-42ee452cfa279c63.yaml0000664000175000017500000000075500000000000026453 0ustar00zuulzuul00000000000000--- features: - | Adds a ``boot_progress`` mapped object to the System object. Inside of this object, a ``last_state`` value maps to values provided by the ``sushy.BootProgressState`` enum. Also exposes, for information purposes, the ``last_boot_seconds_count``, ``last_state_updated_at``, and ``oem_last_state`` values, which map to the DMTF Redfish values ``LastBootTimeSeconds``, ``LastStateTime``, and ``OemLastState`` fields for the ``BootProgress`` object. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-system-manager-linkage-86be69c9df4cb359.yaml0000664000175000017500000000035200000000000027037 0ustar00zuulzuul00000000000000--- features: - | Establishes ComputerSystem->Managers and Manager->ComputerSystems references at sushy data abstraction level what make it possible to look up Manager(s) responsible for a ComputerSystem and vice versa. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-system-status-field-41b3f2a8c4b85f38.yaml0000664000175000017500000000012000000000000026310 0ustar00zuulzuul00000000000000--- features: - | Adds the system status field to show the system status. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-system-type-mapping-bf456c5c15a90877.yaml0000664000175000017500000000024000000000000026247 0ustar00zuulzuul00000000000000--- features: - | Adds mappings and constants for possible values of System Type in System resource. This represents the type of the computer system. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-task-monitor-support-21f711927ad6ec91.yaml0000664000175000017500000000020000000000000026441 0ustar00zuulzuul00000000000000--- features: - | Add support for a Task Monitor resource to be able to monitor the state of asynchronous operations. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-task-service-c751ce51e0b8dc11.yaml0000664000175000017500000000021600000000000025034 0ustar00zuulzuul00000000000000--- features: - | Adds initial support for the TaskService resource to the library. `TaskService` is responsible for managing tasks.././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-thermal-resource-5c965a3c940f9028.yaml0000664000175000017500000000010100000000000025511 0ustar00zuulzuul00000000000000--- features: - | Adds the Thermal resource to the Library.././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add-virtual-media-support-f522fbec4420341c.yaml0000664000175000017500000000012400000000000026625 0ustar00zuulzuul00000000000000--- features: - | Adds support for the virtual media resource to the library. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add_composition_service-84750d8d1d96474a.yaml0000664000175000017500000000047100000000000026412 0ustar00zuulzuul00000000000000--- features: - | Adds support for the CompositionService resource to the library. The `CompositionService` is the top level resource for all things related to Composability. If a Redfish service supports Composability, the Service Root resource will contain the `CompositionService` property. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add_ethernet_interface-df308f814f0e4bce.yaml0000664000175000017500000000016600000000000026453 0ustar00zuulzuul00000000000000--- features: - | Adds the "EthernetInterfaces" to the library. It also returns the list of connected MACs. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add_keyword_argument_for_connector-cea5dc4e6c01b548.yaml0000664000175000017500000000017000000000000031110 0ustar00zuulzuul00000000000000--- features: - | Adds functionality to pass different requests library session arguments to sushy connector. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add_product_and_protocol_features_supported-59de3f89b7382434.yaml0000664000175000017500000000017300000000000032562 0ustar00zuulzuul00000000000000--- features: - | Adds `Product` and `ProductFeaturesSupported` properties support to the Redfish `Root Service` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/add_update_service-b54c9bb0177e3468.yaml0000664000175000017500000000022600000000000025377 0ustar00zuulzuul00000000000000--- features: - | Adds support for the UpdateService resource to the library. `UpdateService` is responsible for managing firmware updates. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/allow_empty_context_eventdestination-9a96c34dd7edbeca.yaml0000664000175000017500000000036000000000000031711 0ustar00zuulzuul00000000000000--- fixes: - | Removes the requirement from `Context` to be present when requesting a subscription, some BMCs do not report `Context` when the subscription is created with empty string and would cause `MissingAttributeError`. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/apply-time-support-for-volume-ops-f2ebc412e3b4290a.yaml0000664000175000017500000000166000000000000030372 0ustar00zuulzuul00000000000000--- deprecations: - | The ``supported_values`` property in the ``OperationApplyTimeSupportField`` class is deprecated. Use the ``mapped_supported_values`` property instead. The ``mapped_supported_values`` property uses the ``MappedListField`` type to map the Redfish schema-defined enumeration values to constants exposed by the Sushy package. features: - | Update the ``create_volume`` method in the ``VolumeCollection`` class and the ``delete_volume`` and ``initialize_volume`` methods in the ``Volume`` class to take optional ``apply_time`` and ``timeout`` keyword parameters. This allows the caller of those volume methods to specify a preferred ``OperationApplyTime`` annotation and a maximum timeout for synchronous operations. For asynchronous operations, those three methods will now return a ``TaskMonitor`` instance that the caller can use to monitor the state of the task. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/bios-attribute-registry-a55c2d81c730a795.yaml0000664000175000017500000000034500000000000026366 0ustar00zuulzuul00000000000000--- features: - | Add support for caching Redfish Attribute Registries. In particular, cache the BIOS Attribute Registry and provide a function to return it if it matches the AttributeRegistry field in System BIOS. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/bug-1754514-ca6ebe16c4e4b3b0.yaml0000664000175000017500000000017400000000000023454 0ustar00zuulzuul00000000000000--- critical: - | Fixes authentication failure when SessionService attribute is not present in the root resource. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/catch-general-requests-exceptions-b5fd706597708fb6.yaml0000664000175000017500000000057600000000000030336 0ustar00zuulzuul00000000000000--- fixes: - | Fixes potential cases where exceptions from the underlying ``requests`` library may be raised up through ``sushy``, resulting in client applications possibly not understanding that an error has occurred. The sushy ``ConnectionError`` exception is now returned upon any exception falling under ``RequestException`` from the ``requests`` library. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/certificate-collection-acc67488c240274c.yaml0000664000175000017500000000010700000000000026173 0ustar00zuulzuul00000000000000--- features: - | Adds basic support for ``CertificateService``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/change-bootdev-smc-ab30317eaf5c37d9.yaml0000664000175000017500000000023600000000000025363 0ustar00zuulzuul00000000000000--- fixes: - | Changing boot device string for virtual media from "Cd" to "UsbCd" on SuperMicro machines to match their specific naming convention. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/change-vmedia-write-protected-attr-586370a552288801.yaml0000664000175000017500000000041500000000000030045 0ustar00zuulzuul00000000000000--- fixes: - | Changes the default value of "WriteProtected" parameter in in VirtualMedia::insert_media() from False to True. This is to match Redfish schema 2021.1 and VirtualMedia.v1_3_0: https://redfish.dmtf.org/schemas/v1/VirtualMedia.v1_3_0.json ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/check-for-boot-attrs-in-settingsuri-1cad07b6eb1c81b3.yaml0000664000175000017500000000134500000000000030704 0ustar00zuulzuul00000000000000--- fixes: - | Adds an extra check for cases where the BMC provides a SettingsObject URI through @Redfish.Settings but this URI does not allow setting boot related attributes. Prior to sending a PATCH request to SettingsURI, a GET request is issued to verify if it contains the attributes to be updated. In case these attributes are missing, the request is made against System URI instead. Issues such as the one addressed with this change usually manifest themselves with a Redfish response containing an error message similar to the following: ``MessageId: Base.1.8.PropertyNotWritable, Message: The property BootSourceOverrideEnabled is a read only property and cannot be assigned a value.`` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/config-server-side-retries-d16824019bd709a2.yaml0000664000175000017500000000053000000000000026641 0ustar00zuulzuul00000000000000--- features: - | Adds two new configuration options: ``server_side_retries`` allows to set a custom value for the number of times we should retry a GET request in case of a server error, defaults to 10; ``server_side_retries_delay`` allows to customize the time in seconds between the request retries, defaults to 3. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/decouple-boot-params-c75e80f5951abb12.yaml0000664000175000017500000000077200000000000025667 0ustar00zuulzuul00000000000000--- fixes: - | Adds a new ``set_system_boot_options`` method to the ``System`` object superseding the ``set_system_boot_source`` method. The new method has all boot parameters optional to allow for more atomicity when PATCH'ing Redfish ``Boot`` object. The new method will only include those items in the PATCH document, that are explicitly passed by the user. This change might improve interoperability with BMCs that do not handle certain attributes of the ``Boot`` object. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/deprecate-system-leds-f1a72422c53d281e.yaml0000664000175000017500000000070400000000000025755 0ustar00zuulzuul00000000000000--- deprecations: - | Deprecates system-specific indicator LEDs as redundant. The ``SYSTEM_INDICATOR_LED_LIT``, ``SYSTEM_INDICATOR_LED_BLINKING``, ``SYSTEM_INDICATOR_LED_OFF`` and ``SYSTEM_INDICATOR_LED_UNKNOWN`` constants should not be used. Generic indicator LED constants should be used instead. Those are ``INDICATOR_LED_LIT``, ``INDICATOR_LED_BLINKING``, ``INDICATOR_LED_OFF`` and ``INDICATOR_LED_UNKNOWN`` respectively. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/disable-conn-pooling-3456782afe56ac94.yaml0000664000175000017500000000056500000000000025605 0ustar00zuulzuul00000000000000--- fixes: - | Disable HTTP connection pooling by asking HTTP server to close our connection right upon use. The rationale is that some BMC observed in the wild seem to close persistent connections abruptly upon eventual reuse failing completely unrelated operation. So in ``sushy`` we just try not to maintain persistent connections with BMC at all. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/do-not-offer-compression-encoding-884ca8a7458cb096.yaml0000664000175000017500000000144700000000000030226 0ustar00zuulzuul00000000000000--- fixes: - | Fixes unreliable behavior with ETag interactions with some BMCs as opportunistic use of compressed responses may cause the BMC to change an ETag response to "Weak", which is to be expected as an ETag represents an absolute byte-by-byte response consistency, and compression cannot reliably honor that contract. Introduction of a client perceiving a "Weak" ETag may not be expected by the server, and the server may reject responses because the ETag is not a "Strong" ETag when we respond or interact with a resource. As a result, requests no longer offer oppurtunistic compression of responses as an acceptable possibility, which overall has minimal impact, espescially when compared to the value of consistent BMC behavior and interaction. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/drop-py-2-7-cc931c210ce08e33.yaml0000664000175000017500000000030400000000000023517 0ustar00zuulzuul00000000000000upgrade: - | Python 2.7 support has been dropped. Last release of sushy to support Python 2.7 is OpenStack Train. The minimum version of Python now supported by sushy is Python 3.6. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/enhance-oem-extension-design-3143717e710b3eaf.yaml0000664000175000017500000000112200000000000027205 0ustar00zuulzuul00000000000000--- upgrade: - | OEM resource class hierarchy has been redesigned to allow for non-terminal sub-resources (e.g. Links) to be handled within OEM resource model. As a consequence, backward compatibility with previously existing OEM extension framework (anything based on ``OEMExtensionResourceBase`` class) is not preserved. User OEM code migration would involve switching from ``OEMExtensionResourceBase`` to ``OEMResourceBase`` (note ``__init__`` call signature change) and replacing ``OEMField``-based classes with their generic sushy ``Field`` counterparts. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/enhance-storage-volume-drive-support-16314d30f3631fb3.yaml0000664000175000017500000000016300000000000030656 0ustar00zuulzuul00000000000000--- features: - | Update the Storage, Volume, and Drive models to support RAID configuration management. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/enums-3aff03d940012f33.yaml0000664000175000017500000000171200000000000022674 0ustar00zuulzuul00000000000000--- prelude: | Sushy now exposes Python enums instead of simple string constants. Please check the upgrade notes before updating to this version. upgrade: - | Python `enumerations `_ are now used instead of strings for all Sushy constants. The old names are kept but now point to enumerated values, so e.g. ``sushy.POWER_STATE_ON`` is an alias for ``sushy.PowerState.ON``. Consumers of the library may see breakages in two cases: * Relying on literal values of the old constants. Use constants instead. * Relying on the fact that Sushy constants are strings (e.g. storing them in a database). You can either use the ``value`` attribute to get the underlying string or create your own mapping. deprecations: - | The old constant names (not based on enumerations) are now deprecated. For example, use ``sushy.PowerState.ON`` instead of ``sushy.POWER_STATE_ON``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/ethernet-interfaces-manager-5529326417a38da9.yaml0000664000175000017500000000015000000000000026772 0ustar00zuulzuul00000000000000--- features: - | Adds `EthernetInterfaces` property support to the Redfish Manager instance. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/event-service-d6607420effc3df8.yaml0000664000175000017500000000030100000000000024500 0ustar00zuulzuul00000000000000--- features: - | Adds support for the Redfish EventService resource. `EventService` is responsible for managing event subscriptions and generates the events sent to subscribers. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/expand-drive-schema-042901f919be646c.yaml0000664000175000017500000000024600000000000025332 0ustar00zuulzuul00000000000000--- features: - | Adds ``CapacityBites``, ``Manufacturer``, ``Model``, ``PartNumber``, ``SerialNumber`` and ``Status`` properties to the ``Drive`` resource.././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-2008198-bios-factory-reset-400-bad-request-3f4a7a2aada0835b.yaml0000664000175000017500000000046200000000000031635 0ustar00zuulzuul00000000000000--- fixes: - | Fixes an issue in performing action ``#Bios.ResetBios`` when no body in POST request provided and BMC responds with HTTP 400 Bad request, for example, Dell R630 having iDRAC 2.75.75.75. See `story 2008198 `__ for details. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-eject-media-empty-dict-573b4c9e06f52ce7.yaml0000664000175000017500000000030100000000000026653 0ustar00zuulzuul00000000000000--- fixes: - | Some vendors like HPE iLO has this kind of implementation that for eject virtual media need to pass empty dictionary otherwise throws Unsupported media type error. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-exceeding-retries-663ab543cc14f261.yaml0000664000175000017500000000022700000000000025744 0ustar00zuulzuul00000000000000--- fixes: - | Fixes exceeding retries. Before this fix running sushy for longer it no longer retried for temporary failures when it should. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-extended-info-error-handling-73fecb6bf5c852ff.yaml0000664000175000017500000000040600000000000030322 0ustar00zuulzuul00000000000000--- fixes: - | Fixes ``AttributeError: 'str' object has no attribute 'get'`` during error handling. This occurs when BMC does not return a list of messages inside ``@Message.ExtendedInfo``, but a single item. This has been observed with iDRAC. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-insert-media-payload-b5d4c707f81d9603.yaml0000664000175000017500000000210200000000000026355 0ustar00zuulzuul00000000000000--- upgrade: - | "Inserted" and "WriteProtected" optional attributes are no longer present in the InsertMedia API call payload when default values {"Inserted": True, "WriteProtected": True} are specified by the consumer (e.g. Ironic) and PATCH method of configuring virtual media is not used. Behaviour is unchanged if PATCH method is used. fixes: - | Removing "Inserted" and "WriteProtected" parameters from the Redfish VirtualMedia::insert_media() API call payload when default values {"Inserted": True, "WriteProtected": True} are set and PATCH method is not used. Those parameters are optional as per Redfish schema 2021.1. Some BMCs (e.g. SuperMicro X11/X12 platforms) treat these fields as read-only and setting them causes vMedia insert failures. These attributes should default to True on the BMC side. Some BMCs using PATCH method of configuring virtual media (e.g. Lenovo SD530) still require "Inserted" attribute, so only changing this for non-PATCH methods of configuring virtual media such as InsertMedia action. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-malformed-boot-mode-1ba1117cad8dcc47.yaml0000664000175000017500000000026300000000000026402 0ustar00zuulzuul00000000000000--- fixes: - | Fixes malformed value of the ``BootSourceOverrideMode`` element which goes against the Redfish schema and causes some of the boot mode calls to fail. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-manager-action-d71fd415cea29aa6.yaml0000664000175000017500000000036300000000000025450 0ustar00zuulzuul00000000000000--- fixes: - | Makes ``Manager->Actions`` field optional as Redfish Manager schema defines it. Otherwise sushy fails hard at parsing response from a Redfish agent that does not include ``Actions`` field in its document tree. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-missing-etags-ded8c0bb31fafef7.yaml0000664000175000017500000000027400000000000025561 0ustar00zuulzuul00000000000000--- fixes: - | Fixes missing ETag in PATCH operation against Redfish resources with backward compatibility for Redfish implementation which doesn't work with ETag in header. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-oem-loading-52da045252b6c33e.yaml0000664000175000017500000000026600000000000024530 0ustar00zuulzuul00000000000000--- fixes: - | Fixes Sushy OEM extension loading when using multiple servers that caused loaded extensions to point to server for which the extension was loaded first. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-refine-resource-refresh-86c21ce230967251.yaml0000664000175000017500000000036600000000000026742 0ustar00zuulzuul00000000000000--- features: - | New ``force`` argument to the ``refresh`` method on resources can be set to ``False`` to prevent refreshing of resources that are not stale. Resources can be marked as stale by calling a new ``invalidate`` method. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-required-oem-attribute-parsing-205e4186275aa293.yaml0000664000175000017500000000034200000000000030241 0ustar00zuulzuul00000000000000--- fixes: - | Fixes OEM resource attribute parsing for OEM resources with required fields. Before the fix an error such as "The attribute is missing from the resource " occurred. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-retry_volume_operation-on_sys518-009f2b16e5c38a27.yaml0000664000175000017500000000060400000000000030723 0ustar00zuulzuul00000000000000--- fixes: - | Add retries on iDRAC error with code SYS518 and message "iDRAC is currently unable to display any information because data sources are unavailable." for all request types in addition to existing GET methods. This helps to fix a known intermittent issue when deleting set of volumes one after another and iDRAC is reloading after deleting each volume. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-return-full-weak-etag-04265472cbea9c0e.yaml0000664000175000017500000000027200000000000026542 0ustar00zuulzuul00000000000000--- fixes: - | Makes the unstripped version of an Etag available in addition to the stripped one in order to support vendor implementations which require one or the other. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-simple-storage-device-capacity-bytes-null-0672eed36d9da70a.yaml0000664000175000017500000000075200000000000032570 0ustar00zuulzuul00000000000000--- fixes: - | Fixes bug in ``SimpleStorageCollection.disks_sizes_bytes`` which assumed the type of a disk's ``CapacityBytes`` property is ``integer``. According to the Distributed Management Task Force (DMTF) Redfish standard schema [1], it can be ``null``, which is converted to ``None`` in Python. For more information, see `story 2006918 `_. [1] https://redfish.dmtf.org/schemas/SimpleStorage.v1_2_3.json././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-simple-update-e88838fab4170920.yaml0000664000175000017500000000014000000000000025041 0ustar00zuulzuul00000000000000--- fixes: - | Fixes bug in ``UpdateService.simple_update`` method making it operational. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-software-firmware-inventory-3e0e79e052aa76d9.yaml0000664000175000017500000000023500000000000030131 0ustar00zuulzuul00000000000000--- fixes: - | Fixes bugs in the ``UpdateService.software_inventory`` and ``UpdateService.firmware_inventory`` properties making them operational. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-subprocessors-3b619434dba4636d.yaml0000664000175000017500000000016400000000000025260 0ustar00zuulzuul00000000000000--- fixes: - | Fixes ``Processor.sub_processors`` for "'Processor' object has no attribute 'conn'" error. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-taskmonitor-init-calls-in-volume-module-0f8a747acd0cfe3f.yaml0000664000175000017500000000052500000000000032452 0ustar00zuulzuul00000000000000--- fixes: - | Fixes issues in the ``volume`` module where the first parameter passed to the ``TaskMonitor`` constructor was incorrect. The parameter passed was the resource object (self), but it should have been the connector object (self._conn). This affected the ``create_volume()`` and ``delete_volume()`` methods. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-to-close-session-on-dealloc-c3687d4dcb1441b8.yaml0000664000175000017500000000035200000000000027646 0ustar00zuulzuul00000000000000--- fixes: - | Tries to terminate authenticated Redfish session at BMC Session Service on the event of ``Sushy`` object deallocation. This should reduce the chance of authenticated sessions pool exhaustion at some BMCs. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-update-service-constants-b8c3f48ccee6ce1f.yaml0000664000175000017500000000045200000000000027667 0ustar00zuulzuul00000000000000--- fixes: - | The ``transfer_protocol`` parameter of the ``UpdateService.simple_update`` method should be given one of the newly exposed constants rather than a string literal. This is a breaking change. features: - | Exposes ``UpdateService`` constants to ``sushy`` namespace.././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-use-headers-for-options-736940b87c06c189.yaml0000664000175000017500000000012600000000000026702 0ustar00zuulzuul00000000000000--- fixes: - | If available, uses headers with an Etag to set the boot options. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-virtual-media-fallback-15a559414a65c014.yaml0000664000175000017500000000041300000000000026472 0ustar00zuulzuul00000000000000--- fixes: - | Adds a fallback for inserting and ejecting virtual media using the PATCH HTTP request instead of the explicit action URIs. The fallback is required for Lenovo ThinkSystem machines (i.e. SD530, ..) that only implement the PATCH method.././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-volume-actions-not-required-730fd637dd2587ce.yaml0000664000175000017500000000023200000000000030015 0ustar00zuulzuul00000000000000--- fixes: - | The ``Actions`` field in the ``Volume`` resource was incorrectly specified as being required. This fix makes the field optional. ././@PaxHeader0000000000000000000000000000023100000000000011451 xustar0000000000000000131 path=sushy-5.5.0/releasenotes/notes/fix-volume-delete-configuration-unsuported-operational_time_property-f53f650d8612a847.yaml 22 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fix-volume-delete-configuration-unsuported-operational_time_property-0000664000175000017500000000023700000000000034445 0ustar00zuulzuul00000000000000fixes: - | Fixes 'Unsupported parameter name @Redfish.OperationApplyTime' error on iDRAC firmware version 6.00.02.00 or newer when deleting volumes. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/fixes-ilo5-redfish-firmware-update-issue-273862b2a11e3536.yaml0000664000175000017500000000027000000000000031237 0ustar00zuulzuul00000000000000--- features: - | Adding a new attribute task_uri to monitor redfish firmware update since some vendors(ilo) does not provide appropriate response with task_monitor uri. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/get-retry-9ca311caf8a0b7bb.yaml0000664000175000017500000000012500000000000023763 0ustar00zuulzuul00000000000000--- fixes: - | Automatically retries internal server errors from GET requests. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/handle-basic-auth-access-errors-393b368b31f5cad2.yaml0000664000175000017500000000054600000000000027665 0ustar00zuulzuul00000000000000--- fixes: - | Fixes an issue in the sushy connector object handling where a Sushy client utilizing ``basic`` authentication would not raise an AccessError exception once the credentials stopped working. We now explicitly check to see if ``basic`` authentication is in use, and raise the exception if an AccessError is encountered. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/handle_transfer_method-a51d5a17e381ebee.yaml0000664000175000017500000000012000000000000026464 0ustar00zuulzuul00000000000000--- fixes: - | Add TransferMethod for any general error that mentions it. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/health_literals_change-0e3fc0c439b765e3.yaml0000664000175000017500000000037500000000000026317 0ustar00zuulzuul00000000000000--- other: - | Changes the values for the constants ``HEALTH_STATE_ENABLED``, ``HEALTH_STATE_DISABLED``, ``HEALTH_OK``, ``HEALTH_WARNING`` and ``HEALTH_CRITICAL``. These could be correctly used with their mapped values in mappings.py. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/increase-server-retries-5f11edde8ee0b461.yaml0000664000175000017500000000042400000000000026547 0ustar00zuulzuul00000000000000fixes: - | To avoid timeouts with recent versions of firmwares we need to increase the number of server side retries. For example, in idrac firmware series 5.x the time gap between operations is almost 20 seconds instead of the 10 seconds in the 4.x series. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/indicator-led-mappings-e7b34da03f6abb06.yaml0000664000175000017500000000017400000000000026321 0ustar00zuulzuul00000000000000--- features: - | Adds mappings and constants for possible values of the Indicator LED value in the System class. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/lazily-load-registries-0e9441e435c2471d.yaml0000664000175000017500000000034100000000000026073 0ustar00zuulzuul00000000000000--- fixes: - | Postpones (potentially very large) Redfish message registries download and processing up to the first access by the client. The goal is to reduce the amount of unnecessary traffic and CPU cycles. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/make-leds-settable-c82cb513de0171f5.yaml0000664000175000017500000000032200000000000025271 0ustar00zuulzuul00000000000000--- features: - | The ``IndicatorLED`` property of ``System`` and ``Chassis`` resources made settable with the introduction of the ``.set_indicator_led()`` method to the respective sushy classes. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/make-volume-ops-blocking-de5c2ae032041d5d.yaml0000664000175000017500000000014100000000000026504 0ustar00zuulzuul00000000000000--- features: - | Make POST and DELETE operations in Volume and VolumeCollection blocking. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/message-parsing-resilience-534da532515a15da.yaml0000664000175000017500000000074600000000000026757 0ustar00zuulzuul00000000000000--- fixes: - | Makes message parsing more resilient by handling the case where the message ID only contains a message key and no registry name. In this case, fall back to the ``Messages`` message registry file and then to the ``BaseMessages`` message registry file. If the message ID cannot be found, then set the message to ``unknown``. When parsing messages, if not enough arguments were supplied, then fill in the remaining arguments with ``unknown``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/message-registry-logging-39624ae114c02e15.yaml0000664000175000017500000000012100000000000026375 0ustar00zuulzuul00000000000000--- fixes: - | The large ``MessageRegistry`` objects are no longer logged. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/monitor_firmware_update-664b0c6c1a0307cf.yaml0000664000175000017500000000025000000000000026546 0ustar00zuulzuul00000000000000--- features: - | Added the ability to monitor the progress of a firmware update by changing the ``simple_update`` operation to return a task monitor object. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/more-transferprotocoltype-739ce8bdedbcb51c.yaml0000664000175000017500000000012500000000000027411 0ustar00zuulzuul00000000000000--- fixes: - | Add TransferProtocolType for any general error that mentions it ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/no-passwords-295207ac891d27ab.yaml0000664000175000017500000000020100000000000024210 0ustar00zuulzuul00000000000000--- security: - | No longer logs passwords and auth tokens in DEBUG mode when using SessionService for authentication. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/non-default-language-registries-f73bdecc98ba2cc8.yaml0000664000175000017500000000034000000000000030316 0ustar00zuulzuul00000000000000--- fixes: - | In some cases registries might not be available because the our check for the registry language did not account for a possible country suffix. The logic was improved to take it into account. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/property-missing-7602c421ec177e9a.yaml0000664000175000017500000000026700000000000025120 0ustar00zuulzuul00000000000000--- fixes: - | Correctly handles error code ``Base.1.5.PropertyMissing`` when dealing with hardware that requires ``TransferProtocolType`` for virtual media operations. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/raise-error-on-async-task-failure-b67c7bc189a4d6ca.yaml0000664000175000017500000000021300000000000030344 0ustar00zuulzuul00000000000000--- fixes: - | Fixes an issue in the ``Connector`` class where an exception is not raised when an asynchronous operations fails. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/re-raise-1fe9f912691e893e.yaml0000664000175000017500000000017200000000000023315 0ustar00zuulzuul00000000000000--- fixes: - | Correctly handles errors on a request that is re-tried after refreshing an authentication token. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/reauthentication-session-fallback-failure-fixes-4f0dcfdad1afd2d7.yaml0000664000175000017500000000263000000000000033540 0ustar00zuulzuul00000000000000--- fixes: - | Fixes issues with the refresh of ``Session`` based authentication where a previous refresh attempt failing could result in a fallback to ``Basic`` authentication and would silently fail. The client library now attempts to re-authenticate. - | Fixes silent failures when a refresh of an authentication ``Session`` fails and was unable to be re-established due to an ``AccessError``. Should this occur, now the ``AccessError`` exception is explicitly raised as opposed to attempting to fall back to ``Basic`` authentication. - | Fixes issues where the ``Session`` and ``Basic`` auth interface would fallback to ``Basic`` authentication should a ``ConnectionError`` exception occur while attempting to perform an authentication action. ``ConnectionError`` exceptions are signs of networking transport issues, and should be investigated. A ``ConnectionError`` exception is now raised. - | Prevents the combined ``Session`` and ``Basic`` authentication support from falling back to ``Basic`` authentication once ``Session`` based authentication has been established. This should be considered a potential security issue or an environmental change requiring potential client re-initialization. This is exposed as an ``AccessError`` exception. Continued operations against the Sushy library will attempt to reauthenticate, if possible. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/redfish-response-log-294f3f10b770e356.yaml0000664000175000017500000000021700000000000025542 0ustar00zuulzuul00000000000000--- fixes: - | Reduce the logging from sushy by logging only attributes and values set in the redfish response, not the entire json. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/refactor-taskmonitor-update-volume-ba99380188395852.yaml0000664000175000017500000000276200000000000030333 0ustar00zuulzuul00000000000000--- features: - | Adds new method ``get_task_monitor`` to retrieve TaskMonitor instance by task monitor URI. deprecations: - | Existing two ``TaskMonitor``-s are deprecated and replaced with one ``taskmonitor.TaskMonitor``. For ``resources.task_monitor.TaskMonitor`` users changes include: * ``in_progress`` is replaced with method ``check_is_processing`` * ``location_header`` is replaced with method ``task_monitor_uri`` * there is no replacement for ``set_retry_after``, ``taskmonitor.TaskMonitor`` sets this internally from Retry-After header For ``resources.taskservice.taskmonitor.TaskMonitor`` users changes include: * ``check_is_processing``, ``sleep_for`` and static ``get_task_monitor`` added. * in ``__init__`` parameter ``field_data`` is deprecated, use ``response`` * in ``__init__`` parameter ``task_monitor`` is renamed to ``task_monitor_uri`` * ``task_monitor`` is deprecated, use ``task_monitor_uri`` * ``retry_after`` is deprecated, use ``sleep_for`` Methods ``create_volume``, ``delete_volume``, ``initialize_volume`` in volume module are deprecated and replaced with ones named ``create``, ``delete`` and ``initialize``. New methods for asynchronous operations return ``taskmonitor.TaskMonitor`` instead of deprecated ``resources.task_monitor.TaskMonitor``. Method ``resources.updateservice.UpdateService.get_task_monitor`` is deprecated, use ``Sushy.get_task_monitor`` instead. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/releasenote-d7138d1e1d414632.yaml0000664000175000017500000000100000000000000023770 0ustar00zuulzuul00000000000000--- fixes: - | Alters eTag handling in PATCH requests: First, the original eTag is used. In case of a failure, if the eTag provided was weak, it is converted to a strong format by removing the weak prefix. If this approach is not applicable or fails, the final attempt is made omitting the eTag entirely. By taking this approach, no workarounds are applied if BMC is handling eTags as expected and in case of failures, known workarounds are attempted, improving overall resiliency. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/remove-deprecated-task-monitors-58c505d43e1fa6a7.yaml0000664000175000017500000000065200000000000030047 0ustar00zuulzuul00000000000000--- deprecations: - | ``resources.task_monitor.TaskMonitor`` and ``resources.taskservice.taskmonitor.TaskMonitor`` that were deprecated in Wallaby are now removed. Use ``taskmonitor.TaskMonitor`` instead. - | Methods ``create_volume``, ``delete_volume``, ``initialize_volume`` in volume module that were deprecated in Wallaby are now removed. Use ``create``, ``delete`` and ``initialize`` instead. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/remove-py38-6eebcd268c6f8e37.yaml0000664000175000017500000000016600000000000024121 0ustar00zuulzuul00000000000000--- upgrade: - | Support for Python 3.8 has been removed. Now the minimum python version supported is 3.9 . ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/retry-if-transferprototype-missing-9cae57f3ecf470a9.yaml0000664000175000017500000000047200000000000031036 0ustar00zuulzuul00000000000000--- fixes: - | Resolved virtualmedia attach failures caused by the lack of TransferProtocolType parameter in the request payload which is required by certain BMCs (e.g. on Nokia servers). This is done by adding capability to retry virtualmedia InsertMedia with the updated payload in such cases. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/retry-ilo-not-ready-error-0b4dce882282eaac.yaml0000664000175000017500000000074600000000000026757 0ustar00zuulzuul00000000000000--- fixes: - | An issue was encountered on some HPE iLO supported machines where the Baseboard Management Controller would respond with a HTTP 400 error and an error message indicating the requested operation was invalid for the then system state. For example, attempting to change the power state via the BMC shortly after previously changing the power state. We now attempt to retry within the permitted number of retries when this error is encountered. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/retry-on-missing-managedby-b2a5240eab8b4262.yaml0000664000175000017500000000036500000000000027000 0ustar00zuulzuul00000000000000--- fixes: - | Adds the ability to retry an attempt to determine a Manager for a System when ManagedBy attribute is missing but Managers attribute is present. This resolves issues with certain hardware models such as Huawei 1288H. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/secure-boot-76c5b80371ea85d1.yaml0000664000175000017500000000021600000000000024011 0ustar00zuulzuul00000000000000--- features: - | Adds support for UEFI secure boot: reading the current status, enabling or disabling secure boot, resetting keys. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/secure-boot-database-7fae673722d7cf4f.yaml0000664000175000017500000000015300000000000025725 0ustar00zuulzuul00000000000000--- features: - | Adds support for fetching and resetting individual UEFI secure boot databases. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/sessions.yml0000664000175000017500000000025100000000000021127 0ustar00zuulzuul00000000000000--- features: - | Adds "SessionService" and "Sessions" to the library. - | Adds the ability to specify authentication type on creation of root sushy object. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/skip-empty-mac-address-0d758a9c0d70e2a9.yaml0000664000175000017500000000022500000000000026124 0ustar00zuulzuul00000000000000--- fixes: - | Some hardware vendors set MAC addresses to the empty string which is not valid so skip including those ethernet_interfaces. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/standard-registry-license-0ded489afd6cfad1.yaml0000664000175000017500000000027500000000000027235 0ustar00zuulzuul00000000000000--- other: - | Includes Redfish standard message registry files that are licensed under Creative Commons Attribution 4.0 License: https://creativecommons.org/licenses/by/4.0/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/storage-controller-name-6924b71a97f78481.yaml0000664000175000017500000000017400000000000026210 0ustar00zuulzuul00000000000000--- fixes: - | The ``Name`` field of a StorageControllers sub-field of a Storage resource is no longer mandatory. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/storage-controllers-resource-7ab112f5d2c34ca0.yaml0000664000175000017500000000026700000000000027540 0ustar00zuulzuul00000000000000--- features: - | Adds ``controllers`` property of ``Storage`` class that was introduced in Redfish v1.9 to replace ``storage_controllers`` deprecated in Redfish v1.13. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/story-2006246-reset-bios-return-http-error-415-08170df7fe6300f8.yaml0000664000175000017500000000036200000000000031570 0ustar00zuulzuul00000000000000--- fixes: - | Fixes an issue in performing action ``#Bios.ResetBios`` when BMC expects the POST request with empty body instead of no body. See `story 2006246 `__ for details. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/story-2007216-fix-to-message-registry-cff37659f03ba815.yaml0000664000175000017500000000035600000000000030347 0ustar00zuulzuul00000000000000--- fixes: - | Handles incomplete messages in MessageRegistry that are not having fields like 'Description' and 'Severity'. See story `2007216 `_ for more information. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/sushy-system-virtualmedia-7a61bd77780f7b0e.yaml0000664000175000017500000000026200000000000027030 0ustar00zuulzuul00000000000000--- features: - | Adds support for virtual media via ``System`` resource. For more information, see `bug 2039458 `_. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/update-apply-time-support-53c5445b58cd3b42.yaml0000664000175000017500000000102300000000000026626 0ustar00zuulzuul00000000000000--- features: - | Update sushy models to support the Redfish SettingsApplyTime and OperationApplyTimeSupport annotations. deprecations: - | The ``operation_apply_time_support`` and ``maintenance_window`` properties in the ``SettingsField`` class are deprecated. The ``SettingsField`` class represents the ``@Redfish.Settings`` annotation and those properties cannot appear within this annotation. Instead use the ``apply_time_settings`` property in the target resource (e.g. ``Bios`` resource). ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/update_sushy_models-9b8ea0350eb4d4d0.yaml0000664000175000017500000000042200000000000025767 0ustar00zuulzuul00000000000000features: - | Unifies sushy models by Redfish schema bundle. These changes introduce and update currently implemented sushy models to comply with the most recent schema bundle[1]. [1]https://www.dmtf.org/documents/redfish-spmf/redfish-schema-bundle-20181 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/use-sessions-url-from-root-8b8eca57dc450705.yaml0000664000175000017500000000045200000000000027027 0ustar00zuulzuul00000000000000--- fixes: - | Instead of trying to GET /redfish/v1/SessionService, which is usually reachable via authentication, fail, and then guess /redfish/v1/SessionService/Sessions as Sessions URL, we try first to use directly the Sessions URL provided by the root service, if available. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/use-settingsobject-if-supported-12a332f9905d64ce.yaml0000664000175000017500000000040500000000000030017 0ustar00zuulzuul00000000000000--- fixes: - | Fixes incompatibility with BMCs that require use of a specific SettingsObject URI specified in @Redfish.Settings resource in order to set BootSourceOverride or similar attributes. For example, this is required on Nokia servers. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/vmedia-1.4.0-9957460fed59d85c.yaml0000664000175000017500000000027600000000000023522 0ustar00zuulzuul00000000000000--- features: - | Allows reading and changing the virtual media fields ``TransferMethod`` and ``VerifyCertificate``. - | Allows reading the virtual media field ``UserName``. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/vmedia-blank-auth-c01916469219a39e.yaml0000664000175000017500000000012400000000000024714 0ustar00zuulzuul00000000000000--- fixes: - | Provides dummy vmedia username and password if required by BMC ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/vmedia-certificate-06c367c6ef33d139.yaml0000664000175000017500000000013700000000000025313 0ustar00zuulzuul00000000000000--- features: - | Adds support for creating and deleting virtual media TLS certificates. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/vmedia-credentials-14b7705c3c94cc07.yaml0000664000175000017500000000016300000000000025316 0ustar00zuulzuul00000000000000--- features: - | The ``VirtualMedia.insert_media`` call now supports credentials for the ``image`` URI. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/notes/workaround-sushy-requests-verify-handling-6879c273b651246f.yaml0000664000175000017500000000042500000000000031740 0ustar00zuulzuul00000000000000--- fixes: - | Workaround for https://github.com/psf/requests/issues/3829 If the env ``REQUESTS_CA_BUNDLE`` is set the ``requests.Session()`` ignores the verify parameter. Therefore the verify parameter is moved directly into the function call of requests. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6395147 sushy-5.5.0/releasenotes/source/0000775000175000017500000000000000000000000016710 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/source/2023.1.rst0000664000175000017500000000024000000000000020163 0ustar00zuulzuul00000000000000=================================== 2023.1 Series (4.4.x) Release Notes =================================== .. release-notes:: :branch: unmaintained/2023.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/source/2023.2.rst0000664000175000017500000000023200000000000020165 0ustar00zuulzuul00000000000000=================================== 2023.2 Series (4.5.x) Release Notes =================================== .. release-notes:: :branch: stable/2023.2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/source/2024.1.rst0000664000175000017500000000026200000000000020170 0ustar00zuulzuul00000000000000=========================================== 2024.1 Series (4.6.0 - 5.0.x) Release Notes =========================================== .. release-notes:: :branch: stable/2024.1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/source/2024.2.rst0000664000175000017500000000020200000000000020163 0ustar00zuulzuul00000000000000=========================== 2024.2 Series Release Notes =========================== .. release-notes:: :branch: stable/2024.2 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6395147 sushy-5.5.0/releasenotes/source/_static/0000775000175000017500000000000000000000000020336 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/source/_static/.placeholder0000664000175000017500000000000000000000000022607 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6395147 sushy-5.5.0/releasenotes/source/_templates/0000775000175000017500000000000000000000000021045 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/source/_templates/.placeholder0000664000175000017500000000000000000000000023316 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/source/conf.py0000664000175000017500000002145100000000000020212 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # 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', ] # 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 = 'sushy Release Notes' copyright = '2016, OpenStack Foundation' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'native' # 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 # openstackdocstheme options openstackdocs_repo_name = 'openstack/sushy' openstackdocs_use_storyboard = True # -- 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 = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'SushyReleaseNotesdoc' # -- 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', 'SushyReleaseNotes.tex', 'Sushy Release Notes Documentation', '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', 'sushyreleasenotes', 'Sushy Release Notes Documentation', ['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', 'SushyReleaseNotes', 'Sushy Release Notes Documentation', 'Ironic Developers', 'SushyReleaseNotes', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/source/index.rst0000664000175000017500000000045100000000000020551 0ustar00zuulzuul00000000000000============================================ sushy Release Notes ============================================ .. toctree:: :maxdepth: 1 unreleased 2024.2 2024.1 2023.2 2023.1 zed yoga xena wallaby victoria ussuri train stein rocky queens pike ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/source/pike.rst0000664000175000017500000000025200000000000020371 0ustar00zuulzuul00000000000000========================================= Pike Series (1.0.0 - 1.1.x) Release Notes ========================================= .. release-notes:: :branch: stable/pike ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/source/queens.rst0000664000175000017500000000026200000000000020742 0ustar00zuulzuul00000000000000=========================================== Queens Series (1.2.0 - 1.3.x) Release Notes =========================================== .. release-notes:: :branch: stable/queens ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/source/rocky.rst0000664000175000017500000000025600000000000020574 0ustar00zuulzuul00000000000000========================================== Rocky Series (1.4.0 - 1.6.x) Release Notes ========================================== .. release-notes:: :branch: stable/rocky ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/source/stein.rst0000664000175000017500000000025600000000000020567 0ustar00zuulzuul00000000000000========================================== Stein Series (1.7.0 - 1.8.x) Release Notes ========================================== .. release-notes:: :branch: stable/stein ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/source/train.rst0000664000175000017500000000026100000000000020556 0ustar00zuulzuul00000000000000=========================================== Train Series (1.9.0 - 2.0.x) Release Notes =========================================== .. release-notes:: :branch: stable/train ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/source/unreleased.rst0000664000175000017500000000016000000000000021566 0ustar00zuulzuul00000000000000============================== Current Series Release Notes ============================== .. release-notes:: ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/source/ussuri.rst0000664000175000017500000000026200000000000020774 0ustar00zuulzuul00000000000000=========================================== Ussuri Series (3.0.0 - 3.2.x) Release Notes =========================================== .. release-notes:: :branch: stable/ussuri ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/source/victoria.rst0000664000175000017500000000030000000000000021253 0ustar00zuulzuul00000000000000============================================= Victoria Series (3.3.0 - 3.4.x) Release Notes ============================================= .. release-notes:: :branch: unmaintained/victoria ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/source/wallaby.rst0000664000175000017500000000027400000000000021100 0ustar00zuulzuul00000000000000============================================ Wallaby Series (3.5.0 - 3.7.x) Release Notes ============================================ .. release-notes:: :branch: unmaintained/wallaby ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/source/xena.rst0000664000175000017500000000026300000000000020376 0ustar00zuulzuul00000000000000========================================== Xena Series (3.8.0 - 3.12.x) Release Notes ========================================== .. release-notes:: :branch: unmaintained/xena ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/source/yoga.rst0000664000175000017500000000026000000000000020377 0ustar00zuulzuul00000000000000========================================= Yoga Series (4.0.0 - 4.1.x) Release Notes ========================================= .. release-notes:: :branch: unmaintained/yoga ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/releasenotes/source/zed.rst0000664000175000017500000000025400000000000020225 0ustar00zuulzuul00000000000000======================================== Zed Series (4.2.0 - 4.3.x) Release Notes ======================================== .. release-notes:: :branch: unmaintained/zed ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/requirements.txt0000664000175000017500000000105000000000000016177 0ustar00zuulzuul00000000000000# Requirements lower bounds listed here are our best effort to keep them up to # date but we do not test them so no guarantee of having them all correct. If # you find any incorrect lower bounds, let us know or propose a fix. # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr>=6.0.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 python-dateutil>=2.7.0 # BSD stevedore>=1.29.0 # Apache-2.0 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6955147 sushy-5.5.0/setup.cfg0000664000175000017500000000226300000000000014543 0ustar00zuulzuul00000000000000[metadata] name = sushy summary = Sushy is a small Python library to communicate with Redfish based systems description_file = README.rst author = OpenStack author_email = openstack-discuss@lists.openstack.org home_page = https://docs.openstack.org/sushy/latest/ python_requires = >=3.9 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 :: Implementation :: CPython Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 [files] packages = sushy [entry_points] sushy.resources.system.oems = contoso = sushy.resources.oem.fake:get_extension [codespell] quiet-level = 4 ignore-words-list = assertIn skip = AUTHORS,ChangeLog,*.pyc,*.inv,*.svg,*.png,*.sample,./doc/build/*,./api-ref/build/*,./releasenotes/build/*,./api-ref/build/*,./build/* [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/setup.py0000664000175000017500000000127100000000000014432 0ustar00zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import setuptools setuptools.setup( setup_requires=['pbr>=6.0.0'], pbr=True) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6395147 sushy-5.5.0/sushy/0000775000175000017500000000000000000000000014072 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/__init__.py0000664000175000017500000000316000000000000016203 0ustar00zuulzuul00000000000000 # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging import pbr.version from sushy.main import Sushy from sushy.resources.certificateservice.constants import * # noqa from sushy.resources.chassis.constants import * # noqa from sushy.resources.constants import * # noqa from sushy.resources.eventservice.constants import * # noqa from sushy.resources.fabric.constants import * # noqa from sushy.resources.ipaddresses import * # noqa from sushy.resources.manager.constants import * # noqa from sushy.resources.registry.constants import * # noqa from sushy.resources.system.constants import * # noqa from sushy.resources.system.network.constants import * # noqa from sushy.resources.system.storage.constants import * # noqa from sushy.resources.updateservice.constants import * # noqa from sushy.resources.taskservice.constants import * # noqa __all__ = ('Sushy',) __version__ = pbr.version.VersionInfo( 'sushy').version_string() # Set the default handler to avoid "No handler found" warnings. See: # https://docs.python.org/3/howto/logging.html#library-config logging.getLogger(__name__).addHandler(logging.NullHandler()) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/auth.py0000664000175000017500000002630600000000000015414 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # Sushy Redfish Authentication Modes import abc import logging from sushy import exceptions LOG = logging.getLogger(__name__) class AuthBase(metaclass=abc.ABCMeta): def __init__(self, username=None, password=None): """A class representing a base Sushy authentication mechanism :param username: User account with admin/server-profile access privilege. :param password: User account password. """ self._username = username self._password = password self._root_resource = None self._connector = None def set_context(self, root_resource, connector): """Set the context of the authentication object. :param root_resource: Root sushy object :param connector: Connector for http connections """ # Set the root resource, and connector to use # for normal operations. self._root_resource = root_resource self._connector = connector self._connector.set_auth(self) def authenticate(self): """Perform authentication. :raises: RuntimeError """ if self._root_resource is None or self._connector is None: raise RuntimeError('_root_resource / _connector is missing. ' 'Forgot to call set_context()?') self._do_authenticate() @abc.abstractmethod def _do_authenticate(self): """Method to establish a session to a Redfish controller. Needs to be implemented by extending auth class, because each authentication type will authenticate in its own way. """ @abc.abstractmethod def can_refresh_session(self): """Method to assert if session based refresh can be done.""" def close(self): """Shutdown Redfish authentication object Undoes whatever should be undone to cancel authenticated session. """ def __enter__(self): """Allow object to be called with the 'with' statement.""" return self def __exit__(self, exception_type, exception_value, traceback): """Allow object to be called with the 'with' statement. Allow object to be called with the 'with' statement but also ensure we call close method on exit. """ self.close() class BasicAuth(AuthBase): """Basic Authentication class. This is a class used to encapsulate a basic authentication session. :param username: User account with admin/server-profile access privilege. :param password: User account password. """ def _do_authenticate(self): """Attempts to establish a Basic Authentication Session. """ LOG.debug('Setting basic authentication connector information.') self._connector.set_http_basic_auth(self._username, self._password) def can_refresh_session(self): """Method to assert if session based refresh can be done.""" return False class SessionAuth(AuthBase): """Session Authentication class. This is a class used to encapsulate a redfish session. """ def __init__(self, username=None, password=None): """A class representing a Session Authentication object. :param username: User account with admin/server-profile access privilege. :param password: User account password. """ self._session_key = None """Our Sessions Key""" self._session_resource_id = None """Our Sessions Unique Resource ID or URL""" self._session_auth_previously_successful = False """Our reminder for tracking if session auth has previously worked.""" super().__init__(username, password) def get_session_key(self): """Returns the session key. :returns: The session key. """ return self._session_key def get_session_resource_id(self): """Returns the session resource id. :returns: The session resource id. """ return self._session_resource_id def _do_authenticate(self): """Establish a redfish session. :raises: MissingXAuthToken :raises: ConnectionError :raises: AccessError :raises: HTTPError """ auth_token = None auth_token, session_uri = self._root_resource.create_session( self._username, self._password) # Record the session authentication data. self._session_key = auth_token self._session_resource_id = session_uri # Set flag so we know we've previously successfully achieved # session authentication in order to lockout possible fallback during # session refresh/renegotiation. self._session_auth_previously_successful = True self._connector.set_http_session_auth(auth_token) def can_refresh_session(self): """Method to assert if session based refresh can be done.""" return (self._session_key is not None and self._session_resource_id is not None) def refresh_session(self): """Method to refresh a session to a Redfish controller. This method is called to create a new session after a session that has already been established has timed-out or expired. :raises: MissingXAuthToken :raises: ConnectionError :raises: AccessError :raises: HTTPError """ self.reset_session_attrs() self._do_authenticate() def close(self): """Close the Redfish Session. Attempts to close an established RedfishSession by deleting it from the remote Redfish controller. """ if self._session_resource_id is not None: try: self._connector.delete(self._session_resource_id) except (exceptions.AccessError, exceptions.ServerSideError) as exc: LOG.warning('Received exception "%(exception)s" while ' 'attempting to delete the active session: ' '%(session_id)s', {'exception': exc, 'session_id': self._session_resource_id}) self.reset_session_attrs() def reset_session_attrs(self): """Reset active session related attributes.""" self._session_key = None self._session_resource_id = None # Requests session object data is merged with user submitted data # per https://requests.readthedocs.io/en/master/user/advanced/ # so we need to clear data explicitly set on the session too. self._connector._session.auth = None if 'X-Auth-Token' in self._connector._session.headers: # Delete the token value that was saved to the session # as otherwise we would end up with a dictionary containing # a {'X-Auth-Token': null} being sent across to the remote # bmc. del self._connector._session.headers['X-Auth-Token'] class SessionOrBasicAuth(SessionAuth): def __init__(self, username=None, password=None): super().__init__(username, password) self.basic_auth = BasicAuth(username=username, password=password) def _fallback_to_basic_authentication(self): """Fallback to basic authentication.""" self.reset_session_attrs() self.basic_auth.set_context(self._root_resource, self._connector) self.basic_auth.authenticate() def _do_authenticate(self): """Establish a RedfishSession. We will attempt to establish a redfish session. If we are unable to establish one, fallback to basic authentication. """ try: # Attempt session based authentication super()._do_authenticate() except exceptions.AccessError as e: if (not self.can_refresh_session() and not self._session_auth_previously_successful): # We should only try and fallback if we've not been able # to establish a session. If we had a session previously # and we're trying to fallback, something fishy is afoot. LOG.warning('Falling back to "Basic" authentication as ' 'we have been unable to authenticate to the ' 'BMC. Exception: %(exception)s.', {'exception': e}) self._fallback_to_basic_authentication() else: # We previously had session authentication, something # has changed which is far out of the ordinary. LOG.debug('Received exception "%(exception)s" while ' 'attempting to re-establish a session. ' 'Raising AccessError, as something fishy ' 'has occurred and the BMC has suddenly ' 'ceased to support Session based ' 'authentication.', {'exception': e}) raise except exceptions.ConnectionError as e: # The reason to explicitly catch a connectivity failure is the # case where transitory connectivity failures can occur while # working to authenticate. LOG.debug('Encountered a connectivity failure while attempting ' 'to authenticate. We will not explicitly fallback. ' 'Error: %(exception)s', {'exception': e}) # Previously we would silently eat the failure as SushyError # and fallback as it is a general fault. Callers on direct # invocations through a connector _op method call can still # receive these exceptions, and applications like Ironic do # consider a client reuse disqualifier if there has been # a connection failure, so it is okay for us to fix the behavior # here. raise except exceptions.SushyError as e: LOG.debug('Received exception "%(exception)s" while ' 'attempting to establish a session. ' 'Falling back to basic authentication.', {'exception': e}) self._fallback_to_basic_authentication() def refresh_session(self): """Method to refresh a session to a Redfish controller. This method is called to create a new RedfishSession if we have previously established a RedfishSession and the previous session has timed-out or expired. If we did not previously have an established session, we simply return our BasicAuthentication requests.Session. """ if self.can_refresh_session(): super().refresh_session() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/connector.py0000664000175000017500000005744000000000000016450 0ustar00zuulzuul00000000000000# Copyright 2017 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 http import client as http_client import logging import re import time from urllib import parse as urlparse import requests from urllib3.exceptions import InsecureRequestWarning from sushy import exceptions from sushy.taskmonitor import TaskMonitor from sushy import utils LOG = logging.getLogger(__name__) class Connector: def __init__( self, url, username=None, password=None, verify=True, response_callback=None, server_side_retries=0, server_side_retries_delay=0, default_request_timeout=60): self._url = url self._verify = verify self._session = requests.Session() self._response_callback = response_callback self._auth = None self._server_side_retries = server_side_retries self._server_side_retries_delay = server_side_retries_delay self._default_request_timeout = default_request_timeout # NOTE(TheJulia): In order to help prevent recursive post operations # by allowing us to understand that we should stop authentication. self._sessions_uri = None # NOTE(etingof): field studies reveal that some BMCs choke at # long-running persistent HTTP connections (or TCP connections). # By default, we ask HTTP server to shut down HTTP connection we've # just used. self._session.headers['Connection'] = 'close' if username or password: LOG.warning('Passing username and password to Connector is ' 'deprecated. Authentication is passed through ' 'set_auth now, support for these arguments will ' 'be removed in the future') self.set_http_basic_auth(username, password) if not self._verify: # As the user specifically needs to opt out of certificate # validation the user is aware of the security implications # and does not need to be overwhelmed by the urllib3 warnings requests.packages.urllib3.disable_warnings( category=InsecureRequestWarning) def set_auth(self, auth): """Sets the authentication mechanism for our connector.""" self._auth = auth def set_http_basic_auth(self, username, password): """Sets the http basic authentication information.""" self._session.auth = (username, password) def set_http_session_auth(self, session_auth_token): """Sets the session authentication information.""" self._session.auth = None self._session.headers.update({'X-Auth-Token': session_auth_token}) def close(self): """Close this connector and the associated HTTP session.""" self._session.close() def check_retry_on_exception(self, exception_msg): """Checks whether retry on exception is required.""" retry = False exc_str = str(exception_msg) if 'SYS518' in exc_str: LOG.debug('iDRAC is not yet ready after previous operation. ' 'Error: %(err)s', {'err': exc_str}) retry = True elif 'iLO' in exc_str and 'InvalidOperationForSystemState' in exc_str: LOG.debug('iLO is not ready after previous operation. ' 'Error: %(err)s', {'err': exc_str}) retry = True return retry def _op(self, method, path='', data=None, headers=None, blocking=False, timeout=None, server_side_retries_left=None, allow_reauth=True, **extra_session_req_kwargs): """Generic RESTful request handler. :param method: The HTTP method to be used, e.g: GET, POST, PUT, PATCH, etc... :param path: The sub-URI or absolute URL path to the resource. :param data: Optional JSON data. :param headers: Optional dictionary of headers. Use None value to remove a default header. :param blocking: Whether to block for asynchronous operations. :param timeout: Max time in seconds to wait for blocking async call or for requests library to connect and read. Timeouts should never be disabled: set to a large enough value instead. :param server_side_retries_left: Remaining retries. If not provided will use limit provided by instance's server_side_retries :param allow_reauth: Whether to allow refreshing the authentication token. :param extra_session_req_kwargs: Optional keyword argument to pass requests library arguments which would pass on to requests session object. :returns: The response object from the requests library. :raises: ConnectionError :raises: HTTPError """ if server_side_retries_left is None: server_side_retries_left = self._server_side_retries timeout = timeout or self._default_request_timeout url = path if urlparse.urlparse(path).netloc else urlparse.urljoin( self._url, path) headers = (headers or {}).copy() lc_headers = [k.lower() for k in headers] if data is not None and 'content-type' not in lc_headers: headers['Content-Type'] = 'application/json' if 'odata-version' not in lc_headers: headers['OData-Version'] = '4.0' # NOTE(TheJulia): Depending on the BMC, offering compression as an # acceptable response changes the ETag behavior to offering an # automatic "weak" ETag response, which is appropriate because the # body content *may* not be a byte for byte match given compression. # Overall, the value of compression is less than the value of concise # interaction with the BMC. Setting to identity basically means # "without modification or compression". By default, python-requests # indicates responses can be compressed. if 'accept-encoding' not in lc_headers: headers['Accept-Encoding'] = 'identity' # Allow removing default headers headers = {k: v for k, v in headers.items() if v is not None} # TODO(lucasagomes): We should mask the data to remove sensitive # information LOG.debug('HTTP request: %(method)s %(url)s; headers: %(headers)s; ' 'body: %(data)s; blocking: %(blocking)s; timeout: ' '%(timeout)s; session arguments: %(session)s;', {'method': method, 'url': url, 'headers': utils.sanitize(headers), 'data': utils.sanitize(data), 'blocking': blocking, 'timeout': timeout, 'session': extra_session_req_kwargs}) try: response = self._session.request(method, url, json=data, headers=headers, verify=self._verify, timeout=timeout, **extra_session_req_kwargs) except requests.exceptions.RequestException as e: # Capture any general exception by looking for the parent # class of exceptions in the requests library. # Specifically this will cover cases such as transport # failures, connection timeouts, and encoding errors. # Raising this as sushy ConnectionError allows users to # understand something bad has happened, and to # allow them to respond accordingly. raise exceptions.ConnectionError(url=url, error=e) if self._response_callback: self._response_callback(response) # If we received an AccessError, and we # previously established a redfish session # there is a chance that the session has timed-out. # Attempt to re-establish a session. try: exceptions.raise_for_response(method, url, response) except exceptions.AccessError as e: if (method == 'POST' and self._sessions_uri is not None and self._sessions_uri in url): LOG.error('Authentication to the session service failed. ' 'Please check credentials and try again.') raise if not allow_reauth: LOG.error("Failure occurred while attempting to retry " "request after refreshing the session: %s", e) raise if self._auth is not None: # self._session.auth value is only set when basic auth is used if self._session.auth is not None: LOG.warning('We have encountered an AccessError when ' 'using \'basic\' authentication. %(err)s', {'err': str(e)}) # NOTE(TheJulia): There is no way to recover Basic auth, # as we need the client to be re-launched with new # credentials. raise try: if self._auth.can_refresh_session(): self._auth.refresh_session() else: LOG.warning('Session authentication appears to have ' 'been lost at some point in time. ' 'Connectivity may have been lost during ' 'a prior session refresh. Attempting to ' 're-authenticate.') self._auth.authenticate() except exceptions.AccessError as refresh_exc: LOG.error("A failure occurred while attempting to refresh " "the session. Error: %s", refresh_exc.message) raise LOG.debug("Authentication refreshed successfully, " "retrying the call.") return self._op( method, path, data=data, headers=headers, blocking=blocking, timeout=timeout, server_side_retries_left=server_side_retries_left, allow_reauth=False, **extra_session_req_kwargs) else: if method == 'GET' and url.endswith('SessionService'): LOG.debug('HTTP GET of SessionService failed %s, ' 'this is expected prior to authentication', e.message) else: LOG.error("Authentication error detected. Cannot proceed: " "%s", e.message) raise except exceptions.ServerSideError as e: if ((method.lower() == 'get' or self.check_retry_on_exception(e.message)) and server_side_retries_left > 0): LOG.warning('Got server side error %s in response to a ' 'request, retrying after %d seconds. Retries ' 'left %d.', e, self._server_side_retries_delay, server_side_retries_left) time.sleep(self._server_side_retries_delay) server_side_retries_left -= 1 return self._op( method, path, data=data, headers=headers, blocking=blocking, timeout=timeout, server_side_retries_left=server_side_retries_left, **extra_session_req_kwargs) else: raise except exceptions.BadRequestError as e: if (method.lower() != 'get' and self.check_retry_on_exception(e.message) and server_side_retries_left > 0): LOG.warning('Server has indicated a BadRequest for %s but ' 'the response payload is a known retriable ' 'condition and we will retry in %d seconds. ' 'Retries left %d.', e, self._server_side_retries_delay, server_side_retries_left) time.sleep(self._server_side_retries_delay) server_side_retries_left -= 1 return self._op( method, path, data=data, headers=headers, blocking=blocking, timeout=timeout, server_side_retries_left=server_side_retries_left, **extra_session_req_kwargs) else: raise except exceptions.NotAcceptableError as e: # NOTE(dtantsur): some HPE Gen 10 Plus machines do not allow # identity encoding when fetching registries. if (method.lower() == 'get' and headers.get('Accept-Encoding') == 'identity'): LOG.warning('Server has indicated a NotAcceptable for %s, ' 'retrying without identity encoding', e) headers = dict(headers, **{'Accept-Encoding': None}) return self._op( method, path, data=data, headers=headers, blocking=blocking, timeout=timeout, server_side_retries_left=server_side_retries_left, **extra_session_req_kwargs) else: raise if blocking and response.status_code == 202: if not response.headers.get('Location'): m = (f'HTTP response for {method} request to {url} ' 'returned status 202, but no Location header') raise exceptions.ConnectionError(url=url, error=m) mon = TaskMonitor.from_response(self, response, path) mon.wait(timeout) response = mon.response exceptions.raise_for_response(method, url, response) LOG.debug('HTTP response for %(method)s %(url)s: ' 'status code: %(code)s', {'method': method, 'url': url, 'code': response.status_code}) return response def get(self, path='', data=None, headers=None, blocking=False, timeout=None, **extra_session_req_kwargs): """HTTP GET method. :param path: Optional sub-URI path to the resource. :param data: Optional JSON data. :param headers: Optional dictionary of headers. :param blocking: Whether to block for asynchronous operations. :param timeout: Max time in seconds to wait for blocking async call. Defaults to default_request_timeout on Connector. :param extra_session_req_kwargs: Optional keyword argument to pass requests library arguments which would pass on to requests session object. :returns: The response object from the requests library. :raises: ConnectionError :raises: HTTPError """ return self._op('GET', path, data=data, headers=headers, blocking=blocking, timeout=timeout, **extra_session_req_kwargs) def post(self, path='', data=None, headers=None, blocking=False, timeout=None, **extra_session_req_kwargs): """HTTP POST method. :param path: Optional sub-URI path to the resource. :param data: Optional JSON data. :param headers: Optional dictionary of headers. :param blocking: Whether to block for asynchronous operations. :param timeout: Max time in seconds to wait for blocking async call. Defaults to default_request_timeout on Connector. :param extra_session_req_kwargs: Optional keyword argument to pass requests library arguments which would pass on to requests session object. :returns: The response object from the requests library. :raises: ConnectionError :raises: HTTPError """ return self._op('POST', path, data=data, headers=headers, blocking=blocking, timeout=timeout, **extra_session_req_kwargs) def _etag_handler(self, path='', data=None, headers=None, etag=None, blocking=False, timeout=None, **extra_session_req_kwargs): """eTag handler containing workarounds for PATCH requests with eTags. :param path: Optional sub-URI path to the resource. :param data: Optional JSON data. :param headers: Optional dictionary of headers. :param etag: eTag string. :param blocking: Whether to block for asynchronous operations. :param timeout: Max time in seconds to wait for blocking async call. Defaults to default_request_timeout on Connector. :param extra_session_req_kwargs: Optional keyword argument to pass requests library arguments which would pass on to requests session object. :returns: The response object from the requests library. :raises: ConnectionError :raises: HTTPError """ if etag is not None: if not headers: headers = {} headers['If-Match'] = etag try: response = self._op('PATCH', path, data=data, headers=headers, blocking=blocking, timeout=timeout, **extra_session_req_kwargs) except exceptions.HTTPError as resp: LOG.warning("Initial request with eTag failed: %s", resp) if resp.status_code == http_client.PRECONDITION_FAILED: # NOTE(janders) if there was no eTag provided AND the response # is HTTP 412 Precondition Failed, raise the exception if not etag: raise # checking for weak eTag pattern = re.compile(r'^(W\/)("\w*")$') match = pattern.match(etag) if match: LOG.info("Weak eTag provided with original request to " "%s. Attempting to conversion to strong eTag " "and re-trying.", path) # trying weak eTag converted to strong headers['If-Match'] = match.group(2) try: response = self._op('PATCH', path, data=data, headers=headers, blocking=blocking, timeout=timeout, **extra_session_req_kwargs) except exceptions.HTTPError as resp: if (resp.status_code == http_client. PRECONDITION_FAILED): LOG.warning("Request to %s with weak eTag " "converted to strong eTag also " "failed. Making the final attempt " "with no eTag specified.", path) response = None if response: return response else: # eTag is strong, if it failed the only other thing # to try is removing it entirely # info LOG.warning("Strong eTag provided - retrying request to " "%s with eTag removed.", path) del headers['If-Match'] try: response = self._op('PATCH', path, data=data, headers=headers, blocking=blocking, timeout=timeout, **extra_session_req_kwargs) except exceptions.HTTPError as resp: LOG.error("Final re-try with eTag removed has failed, " "raising exception %s", resp) raise else: raise return response def patch(self, path='', data=None, headers=None, etag=None, blocking=False, timeout=None, **extra_session_req_kwargs): """HTTP PATCH method. :param path: Optional sub-URI path to the resource. :param data: Optional JSON data. :param headers: Optional dictionary of headers. :param etag: Optional eTag string. :param blocking: Whether to block for asynchronous operations. :param timeout: Max time in seconds to wait for blocking async call. Defaults to default_request_timeout on Connector. :param extra_session_req_kwargs: Optional keyword argument to pass requests library arguments which would pass on to requests session object. :returns: The response object from the requests library. :raises: ConnectionError :raises: HTTPError """ return self._etag_handler(path, data, headers, etag, blocking, timeout, **extra_session_req_kwargs) def put(self, path='', data=None, headers=None, blocking=False, timeout=None, **extra_session_req_kwargs): """HTTP PUT method. :param path: Optional sub-URI path to the resource. :param data: Optional JSON data. :param headers: Optional dictionary of headers. :param blocking: Whether to block for asynchronous operations. :param timeout: Max time in seconds to wait for blocking async call. Defaults to default_request_timeout on Connector. :param extra_session_req_kwargs: Optional keyword argument to pass requests library arguments which would pass on to requests session object. :returns: The response object from the requests library. :raises: ConnectionError :raises: HTTPError """ return self._op('PUT', path, data=data, headers=headers, blocking=blocking, timeout=timeout, **extra_session_req_kwargs) def delete(self, path='', data=None, headers=None, blocking=False, timeout=None, **extra_session_req_kwargs): """HTTP DELETE method. :param path: Optional sub-URI path to the resource. :param data: Optional JSON data. :param headers: Optional dictionary of headers. :param blocking: Whether to block for asynchronous operations. :param timeout: Max time in seconds to wait for blocking async call. Defaults to default_request_timeout on Connector. :param extra_session_req_kwargs: Optional keyword argument to pass requests library arguments which would pass on to requests session object. :returns: The response object from the requests library. :raises: ConnectionError :raises: HTTPError """ return self._op('DELETE', path, data=data, headers=headers, blocking=blocking, timeout=timeout, **extra_session_req_kwargs) def __enter__(self): return self def __exit__(self, *_args): self.close() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/exceptions.py0000664000175000017500000001406400000000000016632 0ustar00zuulzuul00000000000000# Copyright 2017 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 http import client as http_client import logging LOG = logging.getLogger(__name__) class SushyError(Exception): """Basic exception for errors raised by Sushy""" message = None def __init__(self, message=None, **kwargs): if message is not None: self.message = message if self.message and kwargs: self.message = self.message % kwargs super().__init__(self.message) class ConnectionError(SushyError): message = 'Unable to connect to %(url)s. Error: %(error)s' class MissingAttributeError(SushyError): message = ('The attribute %(attribute)s is missing from the ' 'resource %(resource)s') class MalformedAttributeError(SushyError): message = ('The attribute %(attribute)s is malformed in the ' 'resource %(resource)s: %(error)s') class MissingActionError(SushyError): message = ('The action %(action)s is missing from the ' 'resource %(resource)s') class InvalidParameterValueError(SushyError): message = ('The parameter "%(parameter)s" value "%(value)s" is invalid. ' 'Valid values are: %(valid_values)s') class ArchiveParsingError(SushyError): message = 'Failed parsing archive "%(path)s": %(error)s' class UnknownDefaultError(SushyError): message = 'Failed at determining default for "%(entity)s": %(error)s' class ExtensionError(SushyError): message = ('Sushy Extension Error: %(error)s') class OEMExtensionNotFoundError(SushyError): message = 'No %(resource)s OEM extension found by name "%(name)s".' class MissingHeaderError(SushyError): message = 'Response to %(target_uri)s did not contain a %(header)s header' class HTTPError(SushyError): """Basic exception for HTTP errors""" status_code = None """HTTP status code.""" body = None """Error JSON body, if present.""" code = 'Base.1.0.GeneralError' """Error code defined in the Redfish specification, if present.""" detail = None """Error message defined in the Redfish specification, if present.""" message = ('HTTP %(method)s %(url)s returned code %(code)s. %(error)s ' 'Extended information: %(ext_info)s') extended_info = None """Extended information provided in the response.""" def __init__(self, method, url, response): self.status_code = response.status_code try: body = response.json() except ValueError: LOG.warning('Error response from %(method)s %(url)s ' 'with status code %(code)s has no JSON body', {'method': method, 'url': url, 'code': self.status_code}) error = 'unknown error' else: self.body = body.get('error', {}) self.code = self.body.get('code', 'Base.1.0.GeneralError') self.detail = self.body.get('message') self.extended_info = self.body.get('@Message.ExtendedInfo') message = self._get_most_severe_msg(self.extended_info or [{}]) self.detail = message or self.detail error = '{}: {}'.format(self.code, self.detail or 'unknown error.') kwargs = {'method': method, 'url': url, 'code': self.status_code, 'error': error, 'ext_info': self.extended_info} LOG.debug('HTTP response for %(method)s %(url)s: ' 'status code: %(code)s, error: %(error)s, ' 'extended: %(ext_info)s', kwargs) super().__init__(**kwargs) @staticmethod def _get_most_severe_msg(extended_info): if not isinstance(extended_info, list): return extended_info.get('Message', None) if len(extended_info) > 0: for sev in ['Critical', 'Warning']: for i, m in enumerate(extended_info): if m.get('Severity') == sev: return m.get('Message') @property def related_properties(self): """List of properties related to the error.""" try: return self.extended_info[0]['RelatedProperties'] except (IndexError, KeyError, TypeError): return [] class BadRequestError(HTTPError): pass class ResourceNotFoundError(HTTPError): # Overwrite the complex generic message with a simpler one. message = 'Resource %(url)s not found' class ServerSideError(HTTPError): pass class AccessError(HTTPError): pass class NotAcceptableError(HTTPError): pass class MissingXAuthToken(HTTPError): message = ('No X-Auth-Token returned from remote host when ' 'attempting to establish a session. Error: %(error)s') def raise_for_response(method, url, response): """Raise a correct error class, if needed.""" if response.status_code < http_client.BAD_REQUEST: return elif response.status_code == http_client.NOT_FOUND: raise ResourceNotFoundError(method, url, response) elif response.status_code == http_client.BAD_REQUEST: raise BadRequestError(method, url, response) elif response.status_code in (http_client.UNAUTHORIZED, http_client.FORBIDDEN): raise AccessError(method, url, response) elif response.status_code == http_client.NOT_ACCEPTABLE: raise NotAcceptableError(method, url, response) elif response.status_code >= http_client.INTERNAL_SERVER_ERROR: raise ServerSideError(method, url, response) else: raise HTTPError(method, url, response) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/main.py0000664000175000017500000006207300000000000015400 0ustar00zuulzuul00000000000000# Copyright 2017 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 collections from importlib import resources import logging import os import requests from sushy import auth as sushy_auth from sushy import connector as sushy_connector from sushy import exceptions from sushy.resources import base from sushy.resources.certificateservice import certificateservice from sushy.resources.chassis import chassis from sushy.resources.compositionservice import compositionservice from sushy.resources.eventservice import eventservice from sushy.resources.fabric import fabric from sushy.resources.manager import manager from sushy.resources.registry import message_registry from sushy.resources.registry import message_registry_file from sushy.resources.sessionservice import session from sushy.resources.sessionservice import sessionservice from sushy.resources.system import system from sushy.resources.taskservice import taskservice from sushy.resources.updateservice import updateservice from sushy import taskmonitor from sushy import utils LOG = logging.getLogger(__name__) STANDARD_REGISTRY_PATH = 'standard_registries' class ProtocolFeaturesSupportedField(base.CompositeField): excerpt_query = base.Field('ExcerptQuery') """The excerpt query parameter is supported""" expand_query = base.Field('ExpandQuery') """The expand query parameter is supported""" filter_query = base.Field('FilterQuery') """The filter query parameter is supported""" only_member_query = base.Field('OnlyMemberQuery') """The only query parameter is supported""" select_query = base.Field('SelectQuery') """The select query parameter is supported""" class LazyRegistries(collections.abc.MutableMapping): """Download registries on demand. Redfish message registries can be very large. On top of that, they are not used frequently. Thus, let's not pull them off the BMC unless the consumer is actually trying to use them. :param service_root: Redfish service root object :type service_root: sushy.main.Sushy """ def __init__(self, service_root): self._service_root = service_root self._registries = None def __getitem__(self, key): registries = self.registries return registries[key] def __setitem__(self, key, value): registries = self.registries registries[key] = value def __delitem__(self, key): registries = self.registries del registries[key] def __iter__(self): registries = self.registries return iter(registries or ()) def __len__(self): registries = self.registries return len(registries) @property def registries(self): if self._registries is None: self._registries = self._service_root.registries return self._registries class Sushy(base.ResourceBase): identity = base.Field('Id', required=True) """The Redfish root service identity""" name = base.Field('Name') """The Redfish root service name""" uuid = base.Field('UUID') """The Redfish root service UUID""" product = base.Field('Product') """The product associated with this Redfish service""" protocol_features_supported = ProtocolFeaturesSupportedField( 'ProtocolFeaturesSupported') """The information about protocol features supported by the service""" _composition_service_path = base.Field( ['CompositionService', '@odata.id']) """CompositionService path""" _systems_path = base.Field(['Systems', '@odata.id']) """SystemCollection path""" _managers_path = base.Field(['Managers', '@odata.id']) """ManagerCollection path""" _chassis_path = base.Field(['Chassis', '@odata.id']) """ChassisCollection path""" _fabrics_path = base.Field(['Fabrics', '@odata.id']) """FabricCollection path""" _session_service_path = base.Field(['SessionService', '@odata.id']) """SessionService path""" _registries_path = base.Field(['Registries', '@odata.id']) """Registries path""" _update_service_path = base.Field(['UpdateService', '@odata.id']) """UpdateService path""" _event_service_path = base.Field(['EventService', '@odata.id']) """EventService path""" _certificate_service_path = base.Field(['CertificateService', '@odata.id']) """CertificateService path""" def __init__(self, base_url, username=None, password=None, root_prefix='/redfish/v1/', verify=True, auth=None, connector=None, public_connector=None, language='en', server_side_retries=10, server_side_retries_delay=3): """A class representing a RootService :param base_url: The base URL to the Redfish controller. It should include scheme and authority portion of the URL. For example: https://mgmt.vendor.com :param username: User account with admin/server-profile access privilege :param password: User account password :param root_prefix: The default URL prefix. This part includes the root service and version. Defaults to /redfish/v1 :param verify: Either a boolean value, a path to a CA_BUNDLE file or directory with certificates of trusted CAs. If set to True the driver will verify the host certificates; if False the driver will ignore verifying the SSL certificate; if it's a path the driver will use the specified certificate or one of the certificates in the directory. Defaults to True. :param auth: An authentication mechanism to utilize. :param connector: A user-defined connector object. Defaults to None. :param public_connector: A user-defined connector to use for requests on the Internet, e.g., for Message Registries. Defaults to None. :param language: RFC 5646 language code for Message Registries. Defaults to 'en'. :param server_side_retries: Number of times to retry GET requests in case of server side errors. Defaults to 10. :param server_side_retries_delay: Time in seconds between retries of GET requests in case of server side errors. Defaults to 3. """ self._root_prefix = root_prefix if (auth is not None and (password is not None or username is not None)): msg = ('Username or Password were provided to Sushy ' 'when an authentication mechanism was specified.') raise ValueError(msg) if auth is None: auth = sushy_auth.SessionOrBasicAuth(username=username, password=password) self._auth = auth super().__init__( connector or sushy_connector.Connector( base_url, verify=verify, server_side_retries=server_side_retries, server_side_retries_delay=server_side_retries_delay), path=self._root_prefix) self._public_connector = public_connector or requests self._language = language self._base_url = base_url self._auth.set_context(self, self._conn) self._auth.authenticate() def __del__(self): if self._auth: try: self._auth.close() except Exception as ex: LOG.warning('Ignoring error while closing Redfish session ' 'with %s: %s', self._base_url, ex) self._auth = None def _parse_attributes(self, json_doc): """Parse the attributes of a resource. Parsed JSON fields are set to `self` as declared in the class. :param json_doc: parsed JSON document in form of Python types """ super()._parse_attributes(json_doc) self.redfish_version = json_doc.get('RedfishVersion') def get_system_collection(self): """Get the SystemCollection object :raises: MissingAttributeError, if the collection attribute is not found :returns: a SystemCollection object """ if not self._systems_path: raise exceptions.MissingAttributeError( attribute='Systems/@odata.id', resource=self._path) return system.SystemCollection( self._conn, self._systems_path, redfish_version=self.redfish_version, registries=self.lazy_registries, root=self) def get_system(self, identity=None): """Given the identity return a System object :param identity: The identity of the System resource. If not given, sushy will default to the single available System or fail if there appear to be more or less then one System listed. :raises: `UnknownDefaultError` if default system can't be determined. :returns: The System object """ if identity is None: systems_collection = self.get_system_collection() listed_systems = systems_collection.get_members() if len(listed_systems) != 1: raise exceptions.UnknownDefaultError( entity='ComputerSystem', error='System count is not exactly one') identity = listed_systems[0].path return system.System(self._conn, identity, redfish_version=self.redfish_version, registries=self.lazy_registries, root=self) def get_chassis_collection(self): """Get the ChassisCollection object :raises: MissingAttributeError, if the collection attribute is not found :returns: a ChassisCollection object """ if not self._chassis_path: raise exceptions.MissingAttributeError( attribute='Chassis/@odata.id', resource=self._path) return chassis.ChassisCollection(self._conn, self._chassis_path, redfish_version=self.redfish_version, registries=self.lazy_registries, root=self) def get_chassis(self, identity=None): """Given the identity return a Chassis object :param identity: The identity of the Chassis resource. If not given, sushy will default to the single available chassis or fail if there appear to be more or less then one Chassis listed. :raises: `UnknownDefaultError` if default system can't be determined. :returns: The Chassis object """ if identity is None: chassis_collection = self.get_chassis_collection() listed_chassis = chassis_collection.get_members() if len(listed_chassis) != 1: raise exceptions.UnknownDefaultError( entity='Chassis', error='Chassis count is not exactly one') identity = listed_chassis[0].path return chassis.Chassis(self._conn, identity, redfish_version=self.redfish_version, registries=self.lazy_registries, root=self) def get_fabric_collection(self): """Get the FabricCollection object :raises: MissingAttributeError, if the collection attribute is not found :returns: a FabricCollection object """ if not self._fabrics_path: raise exceptions.MissingAttributeError( attribute='Fabrics/@odata.id', resource=self._path) return fabric.FabricCollection(self._conn, self._fabrics_path, redfish_version=self.redfish_version, registries=self.lazy_registries, root=self) def get_fabric(self, identity): """Given the identity return a Fabric object :param identity: The identity of the Fabric resource :returns: The Fabric object """ return fabric.Fabric(self._conn, identity, redfish_version=self.redfish_version, registries=self.lazy_registries, root=self) def get_manager_collection(self): """Get the ManagerCollection object :raises: MissingAttributeError, if the collection attribute is not found :returns: a ManagerCollection object """ if not self._managers_path: raise exceptions.MissingAttributeError( attribute='Managers/@odata.id', resource=self._path) return manager.ManagerCollection(self._conn, self._managers_path, redfish_version=self.redfish_version, registries=self.lazy_registries, root=self) def get_manager(self, identity=None): """Given the identity return a Manager object :param identity: The identity of the Manager resource. If not given, sushy will default to the single available Manager or fail if there appear to be more or less then one Manager listed. :returns: The Manager object """ if identity is None: managers_collection = self.get_manager_collection() listed_managers = managers_collection.get_members() if len(listed_managers) != 1: raise exceptions.UnknownDefaultError( entity='Manager', error='Manager count is not exactly one') identity = listed_managers[0].path return manager.Manager(self._conn, identity, redfish_version=self.redfish_version, registries=self.lazy_registries, root=self) def get_session_service(self): """Get the SessionService object :raises: MissingAttributeError, if the collection attribute is not found :returns: as SessionCollection object """ if not self._session_service_path: raise exceptions.MissingAttributeError( attribute='SessionService/@odata.id', resource=self._path) return sessionservice.SessionService( self._conn, self._session_service_path, redfish_version=self.redfish_version, root=self) def get_sessions_path(self): """Returns the Sessions url""" # NOTE(TheJulia): This method is the standard discovery method # advised by the DMTF DSP0266 standard to find the session service. try: links_url = self.json.get('Links') sessions_uri = links_url['Sessions']['@odata.id'] # Save the session URL for post detection and prevention # of recursive authentication attempts. self._conn._sessions_uri = sessions_uri return sessions_uri except (TypeError, KeyError): raise exceptions.MissingAttributeError( attribute='Links/Sessions/@data.id', resource=self.path) def get_session(self, identity): """Given the identity return a Session object :param identity: The identity of the session resource :returns: The Session object """ return session.Session( self._conn, identity, redfish_version=self.redfish_version, registries=self.lazy_registries, root=self) def create_session(self, username=None, password=None): """Creates a session without invoking SessionService. For use when a new connection is to be established. Removes prior Session and authentication data before making the request. :param username: The username to utilize to create a session with the remote endpoint. :param password: The password to utilize to create a session with the remote endpoint. :returns: A session key and uri in the form of a tuple :raises: MissingXAuthToken :raises: ConnectionError :raises: AccessError :raises: HTTPError :raises: MissingAttributeError """ # Explicitly removes in-client session data to proceed. This prevents # AccessErrors as prior authentication shouldn't be submitted with a # new authentication attempt, and doing so with old/invalid session # data can result in an AccessError being raised. self._conn._session.auth = None if 'X-Auth-Token' in self._conn._session.headers: # Delete the token value that was saved to the session # as otherwise we would end up with a dictionary containing # a {'X-Auth-Token': null} being sent across to the remote # bmc. del self._conn._session.headers['X-Auth-Token'] try: session_service_path = self.get_sessions_path() except (exceptions.MissingAttributeError, exceptions.AccessError): # Guesses path based upon DMTF standard, in the event # the links resource on the service root is incorrect. session_service_path = os.path.join(self._path, 'SessionService/Sessions') LOG.warning('Could not discover the Session service path, ' 'falling back to %s.', session_service_path) session_url = self._root_prefix + 'SessionService/Sessions' self._conn._sessions_uri = session_url data = {'UserName': username, 'Password': password} LOG.debug("Requesting new session from %s.", session_service_path) rsp = self._conn.post(session_service_path, data=data) session_key = rsp.headers.get('X-Auth-Token') if session_key is None: raise exceptions.MissingXAuthToken( method='POST', url=session_service_path, response=rsp) session_uri = rsp.headers.get('Location') if session_uri is None: try: body = rsp.json() session_uri = body.get("@odata.id") except ValueError: # JSON decoding failed pass if session_uri is None: LOG.warning("Received X-Auth-Token but NO session uri.") return session_key, session_uri def get_update_service(self): """Get the UpdateService object :returns: The UpdateService object """ if not self._update_service_path: raise exceptions.MissingAttributeError( attribute='UpdateService/@odata.id', resource=self._path) return updateservice.UpdateService( self._conn, self._update_service_path, redfish_version=self.redfish_version, registries=self.lazy_registries, root=self) def get_task_service(self): """Get the TaskService object :returns: The TaskService object """ return taskservice.TaskService( self._conn, utils.get_sub_resource_path_by(self, 'Tasks'), redfish_version=self.redfish_version, registries=self.lazy_registries, root=self) def _get_registry_collection(self): """Get MessageRegistryFileCollection object This resource is optional and can be empty. :returns: MessageRegistryFileCollection object or None if Registries not provided """ if self._registries_path: return message_registry_file.MessageRegistryFileCollection( self._conn, self._registries_path, redfish_version=self.redfish_version, root=self) def get_composition_service(self): """Get the CompositionService object :raises: MissingAttributeError, if the composition service attribute is not found :returns: The CompositionService object """ if not self._composition_service_path: raise exceptions.MissingAttributeError( attribute='CompositionService/@odata.id', resource=self._path) return compositionservice.CompositionService( self._conn, self._composition_service_path, redfish_version=self.redfish_version, registries=self.lazy_registries, root=self) def get_certificate_service(self): """Get the CertificateService object :returns: The CertificateService object """ if not self._certificate_service_path: raise exceptions.MissingAttributeError( attribute='CertificateService/@odata.id', resource=self._path) return certificateservice.CertificateService( self._conn, self._certificate_service_path, redfish_version=self.redfish_version, registries=self.lazy_registries, root=self) def get_event_service(self): """Get the EventService object :raises: MissingAttributeError, if the EventService is not found :returns: The EventService object """ if not self._event_service_path: raise exceptions.MissingAttributeError( attribute='EventService/@odata.id', resource=self._path) return eventservice.EventService( self._conn, self._event_service_path, redfish_version=self.redfish_version, registries=self.lazy_registries, root=self) def _get_standard_message_registry_collection(self): """Load packaged standard message registries :returns: list of MessageRegistry """ return [ message_registry.MessageRegistry( None, os.path.join(STANDARD_REGISTRY_PATH, json_file.name), reader=base.JsonPackagedFileReader(__package__), ) for json_file in resources.files(__package__) .joinpath(STANDARD_REGISTRY_PATH) .iterdir() if json_file.is_file() ] @property @utils.cache_it def registries(self): """Gets and combines all registries together Fetches all registries if any provided by Redfish service and combines together with packaged standard registries. Both message and attribute registries are supported from the Redfish service. :returns: dict of combined registries keyed by both the registry name (Registry_name.Major_version.Minor_version) and the registry file identity, with the value being the actual registry itself. """ standard = self._get_standard_message_registry_collection() registries = {r.registry_prefix + '.' + r.registry_version.rsplit('.', 1)[0]: r for r in standard if r.language == self._language} registry_col = self._get_registry_collection() endpoint_registries = {} if registry_col: provided = registry_col.get_members() for r in provided: # Check for Message and Attribute registries registry = r.get_message_registry( self._language, self._public_connector) if not registry: registry = r.get_attribute_registry( self._language, self._public_connector) if registry: endpoint_registries[r.registry] = registry endpoint_registries.setdefault(r.identity, registry) if endpoint_registries: LOG.debug('Found registries for %(id)s: %(reg)s', {'id': self.identity, 'reg': ', '.join(endpoint_registries)}) registries.update(endpoint_registries) else: LOG.debug('No registries are available for %s', self.identity) return registries @property def lazy_registries(self): """Gets and combines all message registries together Fetches all registries if any provided by Redfish service and combines together with packaged standard registries. :returns: dict of combined message registries where key is Registry_name.Major_version.Minor_version and value is registry itself. """ return LazyRegistries(self) def get_task_monitor(self, task_monitor_uri): """Used to retrieve a TaskMonitor by task monitor URI. :param task_monitor_uri: Task monitor URI :returns: A task monitor. """ return taskmonitor.TaskMonitor( self._conn, task_monitor_uri, redfish_version=self.redfish_version, registries=self.registries) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6435146 sushy-5.5.0/sushy/resources/0000775000175000017500000000000000000000000016104 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/__init__.py0000664000175000017500000000000000000000000020203 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/base.py0000664000175000017500000007500200000000000017374 0ustar00zuulzuul00000000000000# Copyright 2017 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 abc import collections import copy import enum from importlib import resources import io import json import logging import zipfile from sushy import exceptions from sushy.resources import constants from sushy.resources import oem from sushy import utils LOG = logging.getLogger(__name__) class Field: """Definition for fields fetched from JSON.""" def __init__(self, path, required=False, default=None, adapter=lambda x: x): """Create a field definition. :param path: JSON field to fetch the value from. Either a string, or a list of strings in case of a nested field. :param required: whether this field is required. Missing required, but not defaulted, fields result in MissingAttributeError. :param default: the default value to use when the field is missing. :param adapter: a function to call to transform and/or validate the received value. UnicodeError, ValueError or TypeError from this call are reraised as MalformedAttributeError. """ if not callable(adapter): raise TypeError("Adapter must be callable") if not isinstance(path, list): path = [path] elif not path: raise ValueError('Path cannot be empty') self._path = path self._required = required self._default = default self._adapter = adapter def _get_item(self, dct, key_or_callable, **context): if not callable(key_or_callable): return dct[key_or_callable] for candidate_key in dct: if key_or_callable( candidate_key, value=dct[candidate_key], **context): return dct[candidate_key] raise KeyError(key_or_callable) def _load(self, body, resource, nested_in=None): """Load this field from a JSON object. :param body: parsed JSON body. :param resource: ResourceBase instance for which the field is loaded. :param nested_in: parent resource path (for error reporting only), must be a list of strings or None. :raises: MissingAttributeError if a required field is missing and not defaulted. :raises: MalformedAttributeError on invalid field value or type. :returns: loaded and verified value """ name = self._path[-1] for path_item in self._path[:-1]: body = body.get(path_item, {}) try: item = self._get_item(body, name) except KeyError: if self._required: path = (nested_in or []) + self._path if self._default is None: raise exceptions.MissingAttributeError( attribute='/'.join(path), resource=resource.path) logging.warning( 'Applying default "%s" on required, but missing ' 'attribute "%s"', self._default, path) # Do not run the adapter on the default value return self._default # NOTE(etingof): this is just to account for schema violation if item is None: return try: return self._adapter(item) except (UnicodeError, ValueError, TypeError) as exc: path = (nested_in or []) + self._path raise exceptions.MalformedAttributeError( attribute='/'.join(path), resource=resource.path, error=exc) def _collect_fields(resource): """Collect fields from the JSON. :param resource: ResourceBase or CompositeField instance. :returns: generator of tuples (key, field) """ for attr in dir(resource.__class__): field = getattr(resource.__class__, attr) if isinstance(field, Field): yield (attr, field) class CompositeField(collections.abc.Mapping, Field, metaclass=abc.ABCMeta): """Base class for fields consisting of several sub-fields.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._subfields = dict(_collect_fields(self)) def _load(self, body, resource, nested_in=None): """Load the composite field. :param body: parent JSON body. :param resource: parent resource. :param nested_in: parent resource name (for error reporting only). :returns: a new object with sub-fields attached to it. """ nested_in = (nested_in or []) + self._path value = super()._load(body, resource) if value is None: return None # We need a new instance, as this method is called a singleton instance # that is attached to a class (not instance) of a resource or another # CompositeField. We don't want to end up modifying this instance. instance = copy.copy(self) for attr, field in self._subfields.items(): # Hide the Field object behind the real value setattr(instance, attr, field._load(value, resource, nested_in)) return instance # Satisfy the mapping interface, see # https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping def __getitem__(self, key): if key in self._subfields: return getattr(self, key) else: raise KeyError(key) def __len__(self): return len(self._subfields) def __iter__(self): return iter(self._subfields) class ListField(Field): """Base class for fields consisting of a list of several sub-fields.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._subfields = dict(_collect_fields(self)) def _load(self, body, resource, nested_in=None): """Load the field list. :param body: parent JSON body. :param resource: parent resource. :param nested_in: parent resource name (for error reporting only). :returns: a new list object containing subfields. """ nested_in = (nested_in or []) + self._path values = super()._load(body, resource) if values is None: return None # Initialize the list that will contain each field instance instances = [] for value in values: instance = copy.copy(self) for attr, field in self._subfields.items(): # Hide the Field object behind the real value setattr(instance, attr, field._load(value, resource, nested_in)) instances.append(instance) return instances def __getitem__(self, key): return getattr(self, key) class LinksField(CompositeField): """Reference to linked resources.""" oem_vendors = Field('Oem', adapter=list) class DictionaryField(Field): """Base class for fields consisting of dictionary of several sub-fields.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._subfields = dict(_collect_fields(self)) def _load(self, body, resource, nested_in=None): """Load the dictionary. :param body: parent JSON body. :param resource: parent resource. :param nested_in: parent resource name (for error reporting only). :returns: a new dictionary object containing subfields. """ nested_in = (nested_in or []) + self._path values = super()._load(body, resource) if values is None: return None instances = {} for key, value in values.items(): instance_value = copy.copy(self) for attr, field in self._subfields.items(): # Hide the Field object behind the real value setattr(instance_value, attr, field._load(value, resource, nested_in)) instances[key] = instance_value return instances def __getitem__(self, key): return getattr(self, key) class MappedField(Field): """Field taking real value from a mapping.""" def __init__(self, field, mapping, required=False, default=None): """Create a mapped field definition. :param field: JSON field to fetch the value from. This can be either a string or a list of string. In the latter case, the value will be fetched from a nested object. :param mapping: a mapping to take values from, a dictionary or an enumeration. :param required: whether this field is required. Missing required, but not defaulted, fields result in MissingAttributeError. :param default: the default value to use when the field is missing. This value is not matched against the mapping. """ if isinstance(mapping, type) and issubclass(mapping, enum.Enum): def adapter(value): try: return mapping(value) except ValueError: return None elif isinstance(mapping, collections.abc.Mapping): adapter = mapping.get else: raise TypeError("The mapping argument must be a mapping or " "an enumeration") super().__init__( field, required=required, default=default, adapter=adapter) class MappedListField(Field): """Field taking a list of values with a mapping for the values Given JSON {'field':['xxx', 'yyy']}, a sushy resource definition and mapping {'xxx':'a', 'yyy':'b'}, the sushy object to come out will be like resource.field = ['a', 'b'] """ def __init__(self, field, mapping, required=False, default=None): """Create a mapped list field definition. :param field: JSON field to fetch the list of values from. :param mapping: a mapping for the list elements. :param required: whether this field is required. Missing required, but not defaulted, fields result in MissingAttributeError. :param default: the default value to use when the field is missing. """ if isinstance(mapping, type) and issubclass(mapping, enum.Enum): def adapter(value): try: return mapping(value) except ValueError: return None elif isinstance(mapping, collections.abc.Mapping): adapter = mapping.get else: raise TypeError("The mapping argument must be a mapping or " "an enumeration") self._mapping_adapter = adapter super().__init__( field, required=required, default=default, adapter=lambda x: x) def _load(self, body, resource, nested_in=None): """Load the mapped list. :param body: parent JSON body. :param resource: parent resource. :param nested_in: parent resource name (for error reporting only). :returns: a new list object containing the mapped values. """ nested_in = (nested_in or []) + self._path values = super()._load(body, resource) if values is None: return instances = [self._mapping_adapter(value) for value in values if self._mapping_adapter(value) is not None] return instances class MessageListField(ListField): """List of messages with details of settings update status""" message_id = Field('MessageId') """The key for this message which can be used to look up the message in a message registry """ message = Field('Message') """Human readable message, if provided""" severity = MappedField('Severity', constants.Severity) """Severity of the error""" resolution = Field('Resolution') """Used to provide suggestions on how to resolve the situation that caused the error """ _related_properties = Field('RelatedProperties') """List of properties described by the message""" message_args = Field('MessageArgs') """List of message substitution arguments for the message referenced by `message_id` from the message registry """ class FieldData: """Contains data to be used when constructing Fields""" def __init__(self, status_code, headers, json_doc): """Initializes the FieldData instance""" self._status_code = status_code self._headers = headers self._json_doc = json_doc @property def status_code(self): """The status code""" return self._status_code @property def headers(self): """The headers""" return self._headers @property def json_doc(self): """The parsed JSON body""" return self._json_doc class AbstractDataReader(metaclass=abc.ABCMeta): def set_connection(self, connector, path): """Sets mandatory connection parameters :param connector: A Connector instance :param path: path of the resource """ self._conn = connector self._path = path @abc.abstractmethod def get_data(self): """Based on data source get data and parse to JSON""" class JsonDataReader(AbstractDataReader): """Gets the data from HTTP response given by path""" def get_data(self): """Gets JSON file from URI directly""" data = self._conn.get(path=self._path) try: json_data = data.json() if data.content else {} except Exception as exc: LOG.error("Unable to parse JSON in response. %(exc)s. The server " "returned:\n%(data)s", {'exc': exc, 'data': data.content}) raise return FieldData(data.status_code, data.headers, json_data) class JsonPublicFileReader(AbstractDataReader): """Loads the data from the Internet""" def get_data(self): """Get JSON file from full URI""" data = self._conn.get(self._path) return FieldData(data.status_code, data.headers, data.json()) class JsonArchiveReader(AbstractDataReader): """Gets the data from JSON file in archive""" def __init__(self, archive_file): """Initializes the reader :param archive_file: file name of JSON file in archive """ self._archive_file = archive_file def get_data(self): """Gets JSON file from archive. Currently supporting ZIP only""" data = self._conn.get(path=self._path) if data.headers.get('content-type') == 'application/zip': try: archive = zipfile.ZipFile(io.BytesIO(data.content)) json_data = json.loads(archive.read(self._archive_file) .decode(encoding='utf-8')) return FieldData(data.status_code, data.headers, json_data) except (zipfile.BadZipfile, ValueError) as e: raise exceptions.ArchiveParsingError( path=self._path, error=e) else: LOG.error('Support for %(type)s not implemented', {'type': data.headers['content-type']}) return FieldData(data.status_code, data.headers, None) class JsonPackagedFileReader(AbstractDataReader): """Gets the data from packaged file given by path""" def __init__(self, resource_package_name): """Initializes the reader :param resource_package: Python package/module name """ self._resource_package_name = resource_package_name def get_data(self): """Gets JSON file from packaged file denoted by path""" ref = resources.files(self._resource_package_name).joinpath(self._path) with ref.open(encoding='utf-8') as fp: json_data = json.load(fp) return FieldData(None, None, json_data) def get_reader(connector, path, reader=None): """Create and configure the reader. :param connector: A Connector instance :param path: sub-URI path to the resource. :param reader: Reader to use to fetch JSON data. :returns: the reader """ if reader is None: reader = JsonDataReader() reader.set_connection(connector, path) return reader class ResourceBase(metaclass=abc.ABCMeta): redfish_version = None """The Redfish version""" _oem_vendors = Field('Oem', adapter=list) """The list of OEM extension names for this resource.""" links = LinksField('Links') _log_resource_body = True """Whether to log the whole resource body in debug mode.""" def __init__(self, connector, path='', redfish_version=None, registries=None, reader=None, json_doc=None, root=None): """A class representing the base of any Redfish resource Invokes the ``refresh()`` method of resource for the first time from here (constructor). :param connector: A Connector instance :param path: sub-URI path to the resource. :param redfish_version: The version of Redfish. Used to construct the object according to schema of the given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param reader: Reader to use to fetch JSON data. :param json_doc: parsed JSON document in form of Python types. :param root: Sushy root object. Empty for Sushy root itself. """ self._conn = connector self._path = path self._json = None self.redfish_version = redfish_version self._registries = registries # Note(deray): Indicates if the resource holds stale data or not. # Starting off with True and eventually gets set to False when # attribute values are fetched. self._is_stale = True self._reader = get_reader(connector, path, reader) self._root = root self.refresh(json_doc=json_doc) def _get_value(self, val): """Iterate through the input to get values for all attributes :param val: Either a value or a resource :returns: Attribute value, which may be a dictionary """ if isinstance(val, dict): subfields = {} for key, s_val in val.items(): subfields[key] = self._get_value(s_val) return subfields elif isinstance(val, list): return [self._get_value(val[i]) for i in range(len(val))] elif (isinstance(val, DictionaryField) or isinstance(val, CompositeField) or isinstance(val, ListField)): subfields = {} for attr, field in val._subfields.items(): subfields[attr] = self._get_value(val.__getitem__(attr)) return subfields return val def _get_registry(self, identity, language='en', description='registry'): """Get a registry with the given identity. :param identity: The registry identity. :param language: RFC 5646 language code for Message Registries. Indicates language of registry to be used. Defaults to 'en'. :param description: Human-readable description to use in logging. :returns: the corresponding registry object or None. """ registries = self._registries if not registries: LOG.info('No %s is available', description) return None for key, registry in registries.items(): if (registry and identity in (key, registry.identity)): # NOTE(iurygregory): some registries may have "en-US" # as their language, in this case we can check if the # registry language starts with the requested language. registry_language = registry.language.lower().split('-', 1)[0] if (language != registry.language and language.lower() != registry_language): LOG.debug('Found %(descr)s but its language %(reg_lang)s ' 'does not match the requested %(lang)s', {'descr': description, 'lang': language, 'reg_lang': registry.language}) continue return registry avail = ', '.join(f'{reg.identity} ({reg.language})' for reg in registries.values()) LOG.info('%(descr)s %(registry)s not available for language %(lang)s; ' 'available are: %(avail)s', {'descr': description, 'registry': self._attribute_registry, 'lang': language, 'avail': avail}) return None def _parse_attributes(self, json_doc): """Parse the attributes of a resource. Parsed JSON fields are set to `self` as declared in the class. :param json_doc: parsed JSON document in form of Python types :returns: dictionary of attribute/values after parsing """ settings = {} for attr, field in _collect_fields(self): # Hide the Field object behind the real value setattr(self, attr, field._load(json_doc, self)) # Get the attribute/value pairs that have been parsed settings[attr] = self._get_value(getattr(self, attr)) return settings def _get_etag(self): """Returns the ETag of the HTTP request if any was specified. :returns ETag or None """ return self._get_headers().get('ETag') def _get_headers(self): """Returns the HTTP headers of the request for the resource. :returns: dict of HTTP headers """ return self._reader.get_data()._headers def _allow_patch(self): """Returns if the resource supports the PATCH HTTP method. If the resource supports the PATCH HTTP method for updates, it will return it in the Allow HTTP header. :returns: Boolean flag if PATCH is supported or not """ allow_header = self._get_headers().get('Allow', '') methods = set([h.strip().upper() for h in allow_header.split(',')]) return "PATCH" in methods def refresh(self, force=True, json_doc=None): """Refresh the resource Freshly retrieves/fetches the resource attributes and invokes ``_parse_attributes()`` method on successful retrieval. It is recommended not to override this method in concrete ResourceBase classes. Resource classes can place their refresh specific operations in ``_do_refresh()`` method, if needed. This method represents the template method in the paradigm of Template design pattern. :param force: if set to False, will only refresh if the resource is marked as stale, otherwise neither it nor its subresources will be refreshed. :param json_doc: parsed JSON document in form of Python types. :raises: ResourceNotFoundError :raises: ConnectionError :raises: HTTPError """ # Note(deray): Don't re-fetch / invalidate the sub-resources if the # resource is "_not_ stale" (i.e. fresh) OR _not_ forced. if not self._is_stale and not force: return if json_doc: self._json = json_doc else: self._json = self._reader.get_data().json_doc attributes = self._parse_attributes(self._json) LOG.debug('Received representation of %(type)s %(path)s: %(json)s', {'type': self.__class__.__name__, 'path': self._path, 'json': (attributes if self._log_resource_body else '')}) self._do_refresh(force) # Mark it fresh self._is_stale = False def _do_refresh(self, force): """Primitive method to be overridden by refresh related activities. Derived classes are supposed to override this method with the resource specific refresh operations to be performed. This is a primitive method in the paradigm of Template design pattern. As for the base implementation of this method the approach taken is: On refresh, all sub-resources are marked as stale. That means invalidate (or undefine) the exposed attributes for nested resources for fresh evaluation in subsequent calls to those exposed attributes. In other words greedy-refresh is not done for them, unless forced by ``force`` argument. :param force: should force refresh the resource and its sub-resources, if set to True. :raises: ResourceNotFoundError :raises: ConnectionError :raises: HTTPError """ utils.cache_clear(self, force_refresh=force) def invalidate(self, force_refresh=False): """Mark the resource as stale, prompting refresh() before getting used. If ``force_refresh`` is set to True, then it invokes ``refresh()`` on the resource. :param force_refresh: will invoke refresh on the resource, if set to True. :raises: ResourceNotFoundError :raises: ConnectionError :raises: HTTPError """ self._is_stale = True if force_refresh: self.refresh() @property def oem_vendors(self): return list( set((self._oem_vendors or []) + (self.links.oem_vendors or [])) ) @property def json(self): return self._json @property def path(self): return self._path def clone_resource(self, new_resource, path=''): """Instantiate given resource using existing BMC connection context""" return new_resource( self._conn, path or self.path, redfish_version=self.redfish_version, reader=self._reader, root=self.root) @property def resource_name(self): return utils.camelcase_to_underscore_joined(self.__class__.__name__) def get_oem_extension(self, vendor): """Get the OEM extension instance for this resource by OEM vendor :param vendor: the OEM vendor string which is the vendor-specific extensibility identifier. Examples are 'Contoso', 'Hpe'. Possible value can be got from ``oem_vendors`` attribute. :returns: the Redfish resource OEM extension instance. :raises: OEMExtensionNotFoundError """ return oem.get_resource_extension_by_vendor( self.resource_name, vendor, self) @property def registries(self): return self._registries @property def root(self): return self._root class ResourceLinksBase(ResourceBase, metaclass=abc.ABCMeta): def __init__(self, connector, path, redfish_version=None, registries=None, root=None): """A class representing the base of any Redfish resource collection It gets inherited from ``ResourceBase`` and invokes the base class constructor. :param connector: A Connector instance :param path: sub-URI path to the resource collection. :param redfish_version: The version of Redfish. Used to construct the object according to schema of the given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages. :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__(connector, path, redfish_version, registries, root=root) LOG.debug('Received %(count)d member(s) for %(type)s %(path)s', {'count': len(self.members_identities), 'type': self.__class__.__name__, 'path': self._path}) @property @abc.abstractmethod def members_identities(self): """A sequence with members identities""" @property @abc.abstractmethod def _resource_type(self): """The resource class that the collection contains. Override this property to specify the resource class that the collection contains. """ def get_member(self, identity): """Given the identity return a ``_resource_type`` object :param identity: The identity of the ``_resource_type`` :returns: The ``_resource_type`` object :raises: ResourceNotFoundError """ return self._resource_type( self._conn, identity, redfish_version=self.redfish_version, registries=self.registries, root=self.root) @utils.cache_it def get_members(self): """Return a list of ``_resource_type`` objects present in collection :returns: A list of ``_resource_type`` objects """ return [self.get_member(id_) for id_ in self.members_identities] class ResourceCollectionBase(ResourceLinksBase): name = Field('Name') """The name of the collection""" members_identities = Field('Members', default=[], adapter=utils.get_members_identities) """A tuple with the members identities""" class MutableResourceCollectionBase(ResourceCollectionBase): def _create_member(self, values): """Create a new member of this collection. :param values: Fields to set on creation. :return: Created resource or None if it was not returned by the server. """ response = self._conn.post(self._path, data=values) self.invalidate(force_refresh=True) location = response.headers.get('Location') if location is None: return None return self._resource_type( self._conn, location, redfish_version=self.redfish_version, registries=self.registries, root=self.root) def delete_member(self, identity): """Delete the given member of the collection.""" self._conn.delete(identity) self.invalidate(force_refresh=True) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6435146 sushy-5.5.0/sushy/resources/certificateservice/0000775000175000017500000000000000000000000021747 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/certificateservice/__init__.py0000664000175000017500000000000000000000000024046 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/certificateservice/certificate.py0000664000175000017500000001014700000000000024606 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/v1/Certificate.v1_4_0.json # https://redfish.dmtf.org/schemas/v1/CertificateCollection.json from dateutil import parser from sushy.resources import base from sushy.resources.certificateservice import constants as cert_cons class Identifier(base.CompositeField): """The identifier information about a certificate.""" city = base.Field('City') common_name = base.Field('CommonName') country = base.Field('Country') email = base.Field('Email') organization = base.Field('Organization') organizational_unit = base.Field('OrganizationalUnit') state = base.Field('State') class Certificate(base.ResourceBase): identity = base.Field('Id', required=True) """The certificate identity string""" name = base.Field('Name') """The certificate name""" certificate_string = base.Field('CertificateString') """Certificate in the format defined by certificate_type""" certificate_type = base.MappedField('CertificateType', cert_cons.CertificateType) """The format of the certificate""" certificate_usage_type = base.MappedField('CertificateUsageType', cert_cons.CertificateUsageType) """The types or purposes for this certificate""" description = base.Field('Description') """Certificate description""" fingerprint = base.Field('Fingerprint') """The fingerprint of the certificate""" fingerprint_hash_algorithm = base.Field('FingerprintHashAlgorithm') """The hash algorithm for the fingerprint of the certificate""" issuer = Identifier('Issuer') """The issuer of the certificate""" key_usage = base.MappedListField('KeyUsage', cert_cons.KeyUsage) """The key usage extension, which defines the purpose of the public keys in this certificate""" serial_number = base.Field('SerialNumber') """The serial number of the certificate""" signature_algorithm = base.Field('SignatureAlgorithm') """The algorithm used for creating the signature of the certificate""" subject = Identifier('Subject') """The subject of the certificate""" uefi_signature_owner = base.Field('UefiSignatureOwner') """The UEFI signature owner for this certificate""" valid_not_after = base.Field('ValidNotAfter', adapter=parser.parse) """The date when the certificate is no longer valid""" valid_not_before = base.Field('ValidNotBefore', adapter=parser.parse) """The date when the certificate becomes valid""" # TODO(dtantsur): actions def delete(self): """Delete this certificate.""" self._conn.delete(self._path) # Yes, certificate collection is not the same thing as certificate locations. # The latter is only used in CertificateService, while the former - in every # place where certificates are supported, e.g. virtual media. For this reason # there is no link from CertificateService to CertificateCollection. class CertificateCollection(base.MutableResourceCollectionBase): _resource_type = Certificate def create_member(self, certificate_string, certificate_type): """Create a new member of this collection. :param certificate_string: the contents of the new certificate. :param certificate_type: the type of the new certificate, one of :py:class:`sushy.CertificateType`. """ return self._create_member(dict( CertificateString=certificate_string, CertificateType=cert_cons.CertificateType(certificate_type).value, )) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/certificateservice/certificateservice.py0000664000175000017500000000701700000000000026171 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from sushy import exceptions from sushy.resources import base from sushy.resources.certificateservice import certificate from sushy.resources.certificateservice import constants from sushy.resources import common from sushy import utils class ActionsField(base.CompositeField): generate_csr = common.ActionField('#CertificateService.GenerateCSR') replace_certificate = common.ActionField( '#CertificateService.ReplaceCertificate') class CertificateLocations(base.ResourceLinksBase): name = base.Field('Name') """The name of the collection""" _resource_type = certificate.Certificate @property def members_identities(self): return utils.get_sub_resource_path_by( self, ["Links", "Certificates"], is_collection=True) class CertificateService(base.ResourceBase): identity = base.Field('Id', required=True) """The certificate service identity""" name = base.Field('Name') """The certificate service name""" _actions = ActionsField('Actions') _certificate_locations_path = base.Field( ['CertificateLocations', '@odata.id']) def _get_replace_certificate_action_element(self): reset_action = self._actions.replace_certificate if not reset_action: raise exceptions.MissingActionError( action='#CertificateService.ReplaceCertificate', resource=self._path) return reset_action @property @utils.cache_it def certificate_locations(self): """Property to reference certificate locations instance""" if not self._certificate_locations_path: raise exceptions.MissingAttributeError( attribute='CertificateLocations/@odata.id', resource=self._path) return CertificateLocations( self._conn, self._certificate_locations_path, redfish_version=self.redfish_version, registries=self.registries, root=self.root) def replace_certificate(self, certificate_uri, certificate_string, certificate_type): """Replace an existing certificate in the service. :param certificate_uri: URI of an existing certificate. :param certificate_string: the contents of the new certificate. :param certificate_type: the type of the new certificate, one of :py:class:`sushy.CertificateType`. """ try: certificate_type = constants.CertificateType(certificate_type) except ValueError: raise exceptions.InvalidParameterValueError( parameter='certificate_type', value=certificate_type, valid_values=list(constants.CertificateType)) target_uri = self._get_replace_certificate_action_element().target_uri body = {'CertificateUri': certificate_uri, 'CertificateString': certificate_string, 'CertificateType': certificate_type.value} self._conn.post(target_uri, data=body) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/certificateservice/constants.py0000664000175000017500000000610200000000000024334 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import enum class CertificateType(enum.Enum): PEM = 'PEM' """A Privacy Enhanced Mail (PEM)-encoded single certificate.""" PEM_CHAIN = 'PEMchain' """A Privacy Enhanced Mail (PEM)-encoded certificate chain.""" PKCS7 = 'PKCS7' """A Privacy Enhanced Mail (PEM)-encoded PKCS7 certificate.""" class CertificateUsageType(enum.Enum): USER = 'User' """This certificate is a user certificate like those associated with a manager account.""" WEB = 'Web' """This certificate is a web or HTTPS certificate like those used for event destinations.""" SSH = 'SSH' """This certificate is used for SSH.""" DEVICE = 'Device' """This certificate is a device type certificate like those associated with SPDM and other standards.""" PLATFORM = 'Platform' """This certificate is a platform type certificate like those associated with SPDM and other standards.""" BIOS = 'BIOS' """This certificate is a BIOS certificate like those associated with UEFI.""" class KeyUsage(enum.Enum): DIGITAL_SIGNATURE = 'DigitalSignature' """Verifies digital signatures, other than signatures on certificates and CRLs.""" NON_REPUDIATION = 'NonRepudiation' """Verifies digital signatures, other than signatures on certificates and CRLs, and provides a non-repudiation service that protects against the signing entity falsely denying some action.""" KEY_ENCIPHERMENT = 'KeyEncipherment' """Enciphers private or secret keys.""" DATA_ENCIPHERMENT = 'DataEncipherment' """Directly enciphers raw user data without an intermediate symmetric cipher.""" KEY_AGREEMENT = 'KeyAgreement' """Key agreement.""" KEY_CERT_SIGN = 'KeyCertSign' """Verifies signatures on public key certificates.""" CRL_SIGNING = 'CRLSigning' """Verifies signatures on certificate revocation lists (CRLs).""" ENCIPHER_ONLY = 'EncipherOnly' """Enciphers data while performing a key agreement.""" DECIPHER_ONLY = 'DecipherOnly' """Deciphers data while performing a key agreement.""" SERVER_AUTHENTICATION = 'ServerAuthentication' """TLS WWW server authentication.""" CLIENT_AUTHENTICATION = 'ClientAuthentication' """TLS WWW client authentication.""" CODE_SIGNING = 'CodeSigning' """Signs downloadable executable code.""" EMAIL_PROTECTION = 'EmailProtection' """Email protection.""" TIMESTAMPING = 'Timestamping' """Binds the hash of an object to a time.""" OCSP_SIGNING = 'OCSPSigning' """Signs OCSP responses.""" ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6435146 sushy-5.5.0/sushy/resources/chassis/0000775000175000017500000000000000000000000017541 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/chassis/__init__.py0000664000175000017500000000000000000000000021640 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/chassis/chassis.py0000664000175000017500000002754200000000000021562 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is referred from Redfish standard schema. # http://redfish.dmtf.org/schemas/v1/Chassis.v1_8_0.json import logging from sushy import exceptions from sushy.resources import base from sushy.resources.chassis import constants as cha_cons from sushy.resources.chassis.power import power from sushy.resources.chassis.thermal import thermal from sushy.resources import common from sushy.resources import constants as res_cons from sushy.resources.manager import manager from sushy.resources.system.network import adapter from sushy import utils LOG = logging.getLogger(__name__) class ActionsField(base.CompositeField): reset = common.ResetActionField('#Chassis.Reset') class PhysicalSecurity(base.CompositeField): intrusion_sensor = base.MappedField('IntrusionSensor', cha_cons.IntrusionSensor) """IntrusionSensor This indicates the known state of the physical security sensor, such as if it is hardware intrusion detected. """ intrusion_sensor_number = base.Field('IntrusionSensorNumber') """A numerical identifier to represent the physical security sensor""" intrusion_sensor_re_arm = base.MappedField('IntrusionSensorReArm', cha_cons.IntrusionSensorReArm) """This indicates how the Normal state to be restored""" class Chassis(base.ResourceBase): """Chassis resource The Chassis represents the physical components of a system. This resource represents the sheet-metal confined spaces and logical zones such as racks, enclosures, chassis and all other containers. """ chassis_type = base.MappedField('ChassisType', cha_cons.ChassisType, required=True) """The type of physical form factor of the chassis""" identity = base.Field('Id', required=True) """Identifier for the chassis""" name = base.Field('Name', required=True) """The chassis name""" asset_tag = base.Field('AssetTag') """The user assigned asset tag of this chassis""" depth_mm = base.Field('DepthMm') """Depth in millimeters The depth of the chassis. The value of this property shall represent the depth (length) of the chassis (in millimeters) as specified by the manufacturer. """ description = base.Field('Description') """The chassis description""" height_mm = base.Field('HeightMm') """Height in millimeters The height of the chassis. The value of this property shall represent the height of the chassis (in millimeters) as specified by the manufacturer. """ indicator_led = base.MappedField('IndicatorLED', res_cons.IndicatorLED) """The state of the indicator LED, used to identify the chassis""" manufacturer = base.Field('Manufacturer') """The manufacturer of this chassis""" model = base.Field('Model') """The model number of the chassis""" part_number = base.Field('PartNumber') """The part number of the chassis""" physical_security = PhysicalSecurity('PhysicalSecurity') """PhysicalSecurity This value of this property shall contain the sensor state of the physical security. """ power_state = base.MappedField('PowerState', res_cons.PowerState) """The current power state of the chassis""" serial_number = base.Field('SerialNumber') """The serial number of the chassis""" sku = base.Field('SKU') """Stock-keeping unit number (SKU) The value of this property shall be the stock-keeping unit number for this chassis. """ status = common.StatusField('Status') """Status and Health This property describes the status and health of the chassis and its children. """ uuid = base.Field('UUID') """The Universal Unique Identifier (UUID) for this Chassis.""" weight_kg = base.Field('WeightKg') """Weight in kilograms The value of this property shall represent the published mass (commonly referred to as weight) of the chassis (in kilograms). """ width_mm = base.Field('WidthMm') """Width in millimeters The value of this property shall represent the width of the chassis (in millimeters) as specified by the manufacturer. """ _actions = ActionsField('Actions') def __init__(self, connector, identity, redfish_version=None, registries=None, root=None): """A class representing a Chassis :param connector: A Connector instance :param identity: The identity of the Chassis resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of the given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, identity, redfish_version=redfish_version, registries=registries, root=root) def _get_reset_action_element(self): reset_action = self._actions.reset if not reset_action: raise exceptions.MissingActionError(action='#Chassis.Reset', resource=self._path) return reset_action def get_allowed_reset_chassis_values(self): """Get the allowed values for resetting the chassis. :returns: A set of allowed values. :raises: MissingAttributeError, if Actions/#Chassis.Reset attribute not present. """ reset_action = self._get_reset_action_element() if not reset_action.allowed_values: LOG.warning('Could not figure out the allowed values for the ' 'reset chassis action for Chassis %s', self.identity) return set(res_cons.ResetType) return {v for v in res_cons.ResetType if v.value in reset_action.allowed_values} def reset_chassis(self, value): """Reset the chassis. :param value: The target value. :raises: InvalidParameterValueError, if the target value is not allowed. """ valid_resets = self.get_allowed_reset_chassis_values() if value not in valid_resets: raise exceptions.InvalidParameterValueError( parameter='value', value=value, valid_values=valid_resets) value = res_cons.ResetType(value).value target_uri = self._get_reset_action_element().target_uri LOG.debug('Resetting the Chassis %s ...', self.identity) self._conn.post(target_uri, data={'ResetType': value}) LOG.info('The Chassis %s is being reset', self.identity) def set_indicator_led(self, state): """Set IndicatorLED to the given state. :param state: Desired LED state, an IndicatorLED value. :raises: InvalidParameterValueError, if any information passed is invalid. """ try: state = res_cons.IndicatorLED(state).value except ValueError: raise exceptions.InvalidParameterValueError( parameter='state', value=state, valid_values=' ,'.join(i.value for i in res_cons.IndicatorLED)) etag = self._get_etag() data = {'IndicatorLED': state} self._conn.patch(self.path, data=data, etag=etag) self.invalidate() @property @utils.cache_it def managers(self): """A list of managers for this chassis. Returns a list of `Manager` objects representing the managers that manage this chassis. :raises: MissingAttributeError if '@odata.id' field is missing. :returns: A list of `Manager` instances """ paths = utils.get_sub_resource_path_by( self, ["Links", "ManagedBy"], is_collection=True) return [manager.Manager(self._conn, path, redfish_version=self.redfish_version, registries=self.registries, root=self.root) for path in paths] @property @utils.cache_it def systems(self): """A list of systems residing in this chassis. Returns a list of `System` objects representing systems being mounted in this chassis/cabinet. :raises: MissingAttributeError if '@odata.id' field is missing. :returns: A list of `System` instances """ paths = utils.get_sub_resource_path_by( self, ["Links", "ComputerSystems"], is_collection=True) from sushy.resources.system import system return [system.System(self._conn, path, redfish_version=self.redfish_version, registries=self.registries, root=self.root) for path in paths] @property @utils.cache_it def power(self): """Property to reference `Power` instance It is set once when the first time it is queried. On refresh, this property is marked as stale (greedy-refresh not done). Here the actual refresh of the sub-resource happens, if stale. """ return power.Power( self._conn, utils.get_sub_resource_path_by(self, 'Power'), redfish_version=self.redfish_version, registries=self.registries, root=self.root) @property @utils.cache_it def thermal(self): """Property to reference `Thermal` instance It is set once when the first time it is queried. On refresh, this property is marked as stale (greedy-refresh not done). Here the actual refresh of the sub-resource happens, if stale. """ return thermal.Thermal( self._conn, utils.get_sub_resource_path_by(self, 'Thermal'), redfish_version=self.redfish_version, registries=self.registries, root=self.root) @property @utils.cache_it def network_adapters(self): """Property to reference `NetworkAdapterCollection` instance It is set once when the first time it is queried. On refresh, this property is marked as stale (greedy-refresh not done). Here the actual refresh of the sub-resource happens, if stale. """ return adapter.NetworkAdapterCollection( self._conn, utils.get_sub_resource_path_by(self, "NetworkAdapters"), redfish_version=self.redfish_version, registries=self.registries, root=self.root) class ChassisCollection(base.ResourceCollectionBase): @property def _resource_type(self): return Chassis def __init__(self, connector, path, redfish_version=None, registries=None, root=None): """A class representing a ChassisCollection :param connector: A Connector instance :param path: The canonical path to the Chassis collection resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of the given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, path, redfish_version=redfish_version, registries=registries, root=root) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/chassis/constants.py0000664000175000017500000001276200000000000022137 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # Values comes from the Redfish Chassis json-schema: # https://redfish.dmtf.org/schemas/v1/Chassis.v1_17_0.json import enum class ChassisType(enum.Enum): """Chassis Types constants""" RACK = 'Rack' """An equipment rack, typically a 19-inch wide freestanding unit.""" BLADE = 'Blade' """An enclosed or semi-enclosed, typically vertically-oriented, system chassis that must be plugged into a multi-system chassis to function normally.""" ENCLOSURE = 'Enclosure' """A generic term for a chassis that does not fit any other description.""" STAND_ALONE = 'StandAlone' """A single, free-standing system, commonly called a tower or desktop chassis.""" RACK_MOUNT = 'RackMount' """A single-system chassis designed specifically for mounting in an equipment rack.""" CARD = 'Card' """A loose device or circuit board intended to be installed in a system or other enclosure.""" CARTRIDGE = 'Cartridge' """A small self-contained system intended to be plugged into a multi- system chassis.""" ROW = 'Row' """A collection of equipment racks.""" POD = 'Pod' """A collection of equipment racks in a large, likely transportable, container.""" EXPANSION = 'Expansion' """A chassis that expands the capabilities or capacity of another chassis.""" SIDECAR = 'Sidecar' """A chassis that mates mechanically with another chassis to expand its capabilities or capacity.""" ZONE = 'Zone' """A logical division or portion of a physical chassis that contains multiple devices or systems that cannot be physically separated.""" SLED = 'Sled' """An enclosed or semi-enclosed, system chassis that must be plugged into a multi-system chassis to function normally similar to a blade type chassis.""" SHELF = 'Shelf' """An enclosed or semi-enclosed, typically horizontally-oriented, system chassis that must be plugged into a multi-system chassis to function normally.""" DRAWER = 'Drawer' """An enclosed or semi-enclosed, typically horizontally-oriented, system chassis that can be slid into a multi-system chassis.""" MODULE = 'Module' """A small, typically removable, chassis or card that contains devices for a particular subsystem or function.""" COMPONENT = 'Component' """A small chassis, card, or device that contains devices for a particular subsystem or function.""" IP_BASED_DRIVE = 'IPBasedDrive' """A chassis in a drive form factor with IP-based network connections.""" RACK_GROUP = 'RackGroup' """A group of racks that form a single entity or share infrastructure.""" STORAGE_ENCLOSURE = 'StorageEnclosure' """A chassis that encloses storage.""" OTHER = 'Other' """A chassis that does not fit any of these definitions.""" # Backward compatibility CHASSIS_TYPE_RACK = ChassisType.RACK CHASSIS_TYPE_BLADE = ChassisType.BLADE CHASSIS_TYPE_ENCLOSURE = ChassisType.ENCLOSURE CHASSIS_TYPE_STAND_ALONE = ChassisType.STAND_ALONE CHASSIS_TYPE_RACK_MOUNT = ChassisType.RACK_MOUNT CHASSIS_TYPE_CARD = ChassisType.CARD CHASSIS_TYPE_CARTRIDGE = ChassisType.CARTRIDGE CHASSIS_TYPE_ROW = ChassisType.ROW CHASSIS_TYPE_POD = ChassisType.POD CHASSIS_TYPE_EXPANSION = ChassisType.EXPANSION CHASSIS_TYPE_SIDECAR = ChassisType.SIDECAR CHASSIS_TYPE_ZONE = ChassisType.ZONE CHASSIS_TYPE_SLED = ChassisType.SLED CHASSIS_TYPE_SHELF = ChassisType.SHELF CHASSIS_TYPE_DRAWER = ChassisType.DRAWER CHASSIS_TYPE_MODULE = ChassisType.MODULE CHASSIS_TYPE_COMPONENT = ChassisType.COMPONENT CHASSIS_TYPE_IP_BASED_DRIVE = ChassisType.IP_BASED_DRIVE CHASSIS_TYPE_RACK_GROUP = ChassisType.RACK_GROUP CHASSIS_TYPE_STORAGE_ENCLOSURE = ChassisType.STORAGE_ENCLOSURE CHASSIS_TYPE_OTHER = ChassisType.OTHER class IntrusionSensor(enum.Enum): """Chassis IntrusionSensor constants""" NORMAL = 'Normal' """No abnormal physical security condition is detected at this time.""" HARDWARE_INTRUSION = 'HardwareIntrusion' """A door, lock, or other mechanism protecting the internal system hardware from being accessed is detected to be in an insecure state.""" TAMPERING_DETECTED = 'TamperingDetected' """Physical tampering of the monitored entity is detected.""" # Backward compatibility CHASSIS_INTRUSION_SENSOR_NORMAL = IntrusionSensor.NORMAL CHASSIS_INTRUSION_SENSOR_HARDWARE_INTRUSION = \ IntrusionSensor.HARDWARE_INTRUSION CHASSIS_INTRUSION_SENSOR_TAMPERING_DETECTED = \ IntrusionSensor.TAMPERING_DETECTED class IntrusionSensorReArm(enum.Enum): """Chassis IntrusionSensorReArm constants""" MANUAL = 'Manual' """A manual re-arm of this sensor restores it to the normal state.""" AUTOMATIC = 'Automatic' """Because no abnormal physical security condition is detected, this sensor is automatically restored to the normal state.""" # Backward compatibility CHASSIS_INTRUSION_SENSOR_RE_ARM_MANUAL = IntrusionSensorReArm.MANUAL CHASSIS_INTRUSION_SENSOR_RE_ARM_AUTOMATIC = IntrusionSensorReArm.AUTOMATIC ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6475148 sushy-5.5.0/sushy/resources/chassis/power/0000775000175000017500000000000000000000000020675 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/chassis/power/__init__.py0000664000175000017500000000000000000000000022774 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/chassis/power/constants.py0000664000175000017500000000602100000000000023262 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # Values comes from the Redfish Chassis json-schema: # https://redfish.dmtf.org/schemas/v1/Power.v1_7_1.json import enum class PowerSupplyType(enum.Enum): UNKNOWN = 'Unknown' """The power supply type cannot be determined.""" AC = 'AC' """Alternating Current (AC) power supply.""" DC = 'DC' """Direct Current (DC) power supply.""" AC_OR_DC = 'ACorDC' """The power supply supports both DC or AC.""" # Backward compatibility POWER_SUPPLY_TYPE_UNKNOWN = PowerSupplyType.UNKNOWN POWER_SUPPLY_TYPE_AC = PowerSupplyType.AC POWER_SUPPLY_TYPE_DC = PowerSupplyType.DC POWER_SUPPLY_TYPE_ACDC = PowerSupplyType.AC_OR_DC class LineInputVoltageType(enum.Enum): UNKNOWN = 'Unknown' """The power supply line input voltage type cannot be determined.""" AC_LOW_LINE = 'ACLowLine' """100-127V AC input.""" AC_MID_LINE = 'ACMidLine' """200-240V AC input.""" AC_HIGH_LINE = 'ACHighLine' """277V AC input.""" DC_NEG48V = 'DCNeg48V' """-48V DC input.""" DC_380V = 'DC380V' """High Voltage DC input (380V).""" AC_120V = 'AC120V' """AC 120V nominal input.""" AC_240V = 'AC240V' """AC 240V nominal input.""" AC_277V = 'AC277V' """AC 277V nominal input.""" AC_AND_DC_WIDE_RANGE = 'ACandDCWideRange' """Wide range AC or DC input.""" AC_WIDE_RANGE = 'ACWideRange' """Wide range AC input.""" DC_240V = 'DC240V' """DC 240V nominal input.""" # Backward compatibility LINE_INPUT_VOLTAGE_TYPE_UNKNOWN = LineInputVoltageType.UNKNOWN LINE_INPUT_VOLTAGE_TYPE_ACLOW = LineInputVoltageType.AC_LOW_LINE LINE_INPUT_VOLTAGE_TYPE_ACMID = LineInputVoltageType.AC_MID_LINE LINE_INPUT_VOLTAGE_TYPE_ACHIGH = LineInputVoltageType.AC_HIGH_LINE LINE_INPUT_VOLTAGE_TYPE_DCNEG48 = LineInputVoltageType.DC_NEG48V LINE_INPUT_VOLTAGE_TYPE_DC380V = LineInputVoltageType.DC_380V LINE_INPUT_VOLTAGE_TYPE_AC120V = LineInputVoltageType.AC_120V LINE_INPUT_VOLTAGE_TYPE_AC240V = LineInputVoltageType.AC_240V LINE_INPUT_VOLTAGE_TYPE_AC277V = LineInputVoltageType.AC_277V LINE_INPUT_VOLTAGE_TYPE_ACDCWIDE = LineInputVoltageType.AC_AND_DC_WIDE_RANGE LINE_INPUT_VOLTAGE_TYPE_ACWIDE = LineInputVoltageType.AC_WIDE_RANGE LINE_INPUT_VOLTAGE_TYPE_DC240V = LineInputVoltageType.DC_240V class PowerInputType(enum.Enum): AC = 'AC' """Alternating Current (AC) input range.""" DC = 'DC' """Direct Current (DC) input range.""" # Backward compatibility INPUT_TYPE_AC = PowerInputType.AC INPUT_TYPE_DC = PowerInputType.DC ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/chassis/power/power.py0000664000175000017500000001077500000000000022415 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is referred from Redfish standard schema. # http://redfish.dmtf.org/schemas/v1/Power.v1_3_0.json from sushy.resources import base from sushy.resources.chassis.power import constants as pow_cons from sushy.resources import common from sushy.resources import constants as res_cons from sushy import utils class InputRangeListField(base.ListField): """This type describes an input range for a power supply""" input_type = base.MappedField('InputType', pow_cons.PowerInputType) """The Input type (AC or DC)""" maximum_frequency_hz = base.Field('MaximumFrequencyHz', adapter=utils.int_or_none) """The maximum line input frequency at which this power supply input range is effective""" maximum_voltage = base.Field('MaximumVoltage', adapter=utils.int_or_none) """The maximum line input voltage at which this power supply input range is effective""" minimum_frequency_hz = base.Field('MinimumFrequencyHz', adapter=utils.int_or_none) """The minimum line input frequency at which this power supply input range is effective""" minimum_voltage = base.Field('MinimumVoltage', adapter=utils.int_or_none) """The minimum line input voltage at which this power supply input range is effective""" output_wattage = base.Field('OutputWattage', adapter=utils.int_or_none) """The maximum capacity of this Power Supply when operating in this input range""" class PowerSupplyListField(base.ListField): """The power supplies associated with this Power resource""" firmware_version = base.Field('FirmwareVersion') """The firmware version for this Power Supply""" identity = base.Field('MemberId') """Identifier of the Power Supply""" indicator_led = base.MappedField('IndicatorLed', res_cons.IndicatorLED) """The state of the indicator LED, used to identify the power supply""" input_ranges = InputRangeListField('InputRanges', default=[]) """This is the input ranges that the power supply can use""" last_power_output_watts = base.Field('LastPowerOutputWatts', adapter=utils.int_or_none) """The average power output of this Power Supply""" line_input_voltage = base.Field('LineInputVoltage', adapter=utils.int_or_none) """The line input voltage at which the Power Supply is operating""" line_input_voltage_type = base.MappedField('LineInputVoltageType', pow_cons.LineInputVoltageType) """The line voltage type supported as an input to this Power Supply""" manufacturer = base.Field('Manufacturer') """This is the manufacturer of this power supply""" model = base.Field('Model') """The model number for this Power Supply""" name = base.Field('Name') """Name of the Power Supply""" part_number = base.Field('PartNumber') """The part number for this Power Supply""" power_capacity_watts = base.Field('PowerCapacityWatts', adapter=utils.int_or_none) """The maximum capacity of this Power Supply""" power_supply_type = base.MappedField('PowerSupplyType', pow_cons.PowerSupplyType) """The Power Supply type (AC or DC)""" serial_number = base.Field('SerialNumber') """The serial number for this Power Supply""" spare_part_number = base.Field('SparePartNumber') """The spare part number for this Power Supply""" status = common.StatusField('Status') """Status of the sensor""" class Power(base.ResourceBase): """This class represents a Power resource.""" identity = base.Field('Id', required=True) """Identifier of the resource""" name = base.Field('Name', required=True) """The name of the resource""" power_supplies = PowerSupplyListField('PowerSupplies', default=[]) """Details of a power supplies associated with this system or device""" ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6475148 sushy-5.5.0/sushy/resources/chassis/thermal/0000775000175000017500000000000000000000000021175 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/chassis/thermal/__init__.py0000664000175000017500000000000000000000000023274 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/chassis/thermal/constants.py0000664000175000017500000000203400000000000023562 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # Values comes from the Redfish Chassis json-schema: # https://redfish.dmtf.org/schemas/v1/Thermal.v1_7_1.json import enum class FanReadingUnit(enum.Enum): RPM = 'RPM' """The fan reading and thresholds are measured in revolutions per minute.""" PERCENT = 'Percent' """The fan reading and thresholds are measured as a percentage.""" # Backward compatibility FAN_READING_UNIT_PERCENTAGE = FanReadingUnit.PERCENT FAN_READING_UNIT_RPM = FanReadingUnit.RPM ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/chassis/thermal/thermal.py0000664000175000017500000001145500000000000023211 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is referred from Redfish standard schema. # http://redfish.dmtf.org/schemas/v1/Thermal.v1_3_0.json from sushy.resources import base from sushy.resources.chassis.thermal import constants as the_cons from sushy.resources import common from sushy.resources import constants as res_cons from sushy import utils class Sensor(base.ListField): """The sensor device/s associated with Thermal.""" identity = base.Field('MemberId', required=True) """Identifier of the Sensor""" lower_threshold_critical = base.Field('LowerThresholdCritical', adapter=utils.int_or_none) """Below normal range but not yet fatal""" lower_threshold_fatal = base.Field('LowerThresholdFatal', adapter=utils.int_or_none) """Below normal range and is fatal""" lower_threshold_non_critical = base.Field('LowerThresholdNonCritical', adapter=utils.int_or_none) """Below normal range""" name = base.Field('Name') """The name of this sensor""" physical_context = base.Field('PhysicalContext') """Area or device associated with this sensor""" status = common.StatusField('Status') """Status of the sensor""" upper_threshold_critical = base.Field('UpperThresholdCritical', adapter=utils.int_or_none) """Above normal range but not yet fatal""" upper_threshold_fatal = base.Field('UpperThresholdFatal', adapter=utils.int_or_none) """Above normal range and is fatal""" upper_threshold_non_critical = base.Field('UpperThresholdNonCritical', adapter=utils.int_or_none) """Above normal range""" class FansListField(Sensor): """The Fan device/s associated with Thermal.""" indicator_led = base.MappedField('IndicatorLED', res_cons.IndicatorLED) """The state of the indicator LED, used to identify the fan""" manufacturer = base.Field('Manufacturer') """This is the manufacturer of this Fan""" max_reading_range = base.Field('MaxReadingRange', adapter=utils.int_or_none) """Maximum value for Reading""" min_reading_range = base.Field('MinReadingRange', adapter=utils.int_or_none) """Minimum value for Reading""" model = base.Field('Model') """The model of this Fan""" part_number = base.Field('PartNumber') """Part number of this Fan""" reading = base.Field('Reading', adapter=utils.int_or_none) """Current Fan Speed""" reading_units = base.MappedField('ReadingUnits', the_cons.FanReadingUnit) """Units in which the reading and thresholds are measured""" serial_number = base.Field('SerialNumber') """Serial number of this Fan""" class TemperaturesListField(Sensor): """The Temperature device/s associated with Thermal.""" max_allowable_operating_value = base.Field('MaxAllowableOperatingValue', adapter=utils.int_or_none) """Maximum allowable operating temperature for this equipment""" min_allowable_operating_value = base.Field('MinAllowableOperatingValue', adapter=utils.int_or_none) """Minimum allowable operating temperature for this equipment""" max_reading_range_temp = base.Field('MaxReadingRangeTemp') """Maximum value for ReadingCelsius""" min_reading_range_temp = base.Field('MinReadingRangeTemp') """Minimum value for ReadingCelsius""" reading_celsius = base.Field('ReadingCelsius') """Temperature""" sensor_number = base.Field('SensorNumber', adapter=utils.int_or_none) """A numerical identifier to represent the temperature sensor""" class Thermal(base.ResourceBase): """This class represents a Thermal resource.""" identity = base.Field('Id') """Identifier of the resource""" name = base.Field('Name') """The name of the resource""" status = common.StatusField('Status') """Status of the resource""" fans = FansListField('Fans', default=[]) """A tuple of Fan identities""" temperatures = TemperaturesListField('Temperatures', default=[]) """A tuple of Temperature identities""" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/common.py0000664000175000017500000000703000000000000017746 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from dateutil import parser from sushy.resources import base from sushy.resources import constants class IdRefField(base.CompositeField): """Reference to the resource odata identity field.""" resource_uri = base.Field('@odata.id') """The unique identifier for a resource""" class OperationApplyTimeSupportField(base.CompositeField): def __init__(self): super().__init__(path="@Redfish.OperationApplyTimeSupport") maintenance_window_duration_in_seconds = base.Field( 'MaintenanceWindowDurationInSeconds', adapter=int) """The expiry time of maintenance window in seconds""" _maintenance_window_resource = IdRefField('MaintenanceWindowResource') """The location of the maintenance window settings""" maintenance_window_start_time = base.Field( 'MaintenanceWindowStartTime', adapter=parser.parse) """The start time of a maintenance window""" supported_values = base.Field('SupportedValues', required=True, adapter=list) """The types of apply times that the client is allowed request when performing a create, delete, or action operation returned as an unmapped list Deprecated: Use `mapped_supported_values`. """ mapped_supported_values = base.MappedListField( 'SupportedValues', constants.ApplyTime, required=True) """The types of apply times that the client is allowed request when performing a create, delete, or action operation returned as a mapped list""" class ActionField(base.CompositeField): target_uri = base.Field('target', required=True) operation_apply_time_support = OperationApplyTimeSupportField() class ResetActionField(ActionField): allowed_values = base.Field('ResetType@Redfish.AllowableValues', adapter=list) class InitializeActionField(ActionField): allowed_values = base.Field('InitializeType@Redfish.AllowableValues', adapter=list) class StatusField(base.CompositeField): """This Field describes the status of a resource and its children. This field shall contain any state or health properties of a resource. """ health = base.MappedField('Health', constants.Health) """Represents health of resource w/o considering its dependent resources""" health_rollup = base.MappedField('HealthRollup', constants.Health) """Represents health state of resource and its dependent resources""" state = base.MappedField('State', constants.State) """Indicates the known state of the resource, such as if it is enabled.""" class IdentifiersListField(base.ListField): """This type describes any additional identifiers for a resource.""" durable_name = base.Field('DurableName') """This indicates the world wide, persistent name of the resource.""" durable_name_format = base.MappedField('DurableNameFormat', constants.DurableNameFormat) """This represents the format of the DurableName property.""" ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6475148 sushy-5.5.0/sushy/resources/compositionservice/0000775000175000017500000000000000000000000022030 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/compositionservice/__init__.py0000664000175000017500000000000000000000000024127 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/compositionservice/compositionservice.py0000664000175000017500000000766300000000000026342 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # This is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/CompositionService.v1_1_0.json import logging from sushy import exceptions from sushy.resources import base from sushy.resources import common from sushy.resources.compositionservice import resourceblock from sushy.resources.compositionservice import resourcezone from sushy import utils LOG = logging.getLogger(__name__) class CompositionService(base.ResourceBase): allow_overprovisioning = base.Field('AllowOverprovisioning') """This indicates whether this service is allowed to overprovision""" allow_zone_affinity = base.Field('AllowZoneAffinity') """This indicates whether a client is allowed to request that given composition request""" description = base.Field('Description') """The composition service description""" identity = base.Field('Id', required=True) """The composition service identity string""" name = base.Field('Name', required=True) """The composition service name""" status = common.StatusField('Status') """The status of composition service""" service_enabled = base.Field('ServiceEnabled') """The status of composition service is enabled""" def __init__(self, connector, identity, redfish_version=None, registries=None, root=None): """A class representing a CompositionService :param connector: A connector instance :param identity: The identity of the CompositionService resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of given version :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, identity, redfish_version=redfish_version, registries=registries, root=root) def _get_resource_blocks_collection_path(self): """Helper function to find the ResourceBlockCollections path""" res_block_col = self.json.get('ResourceBlocks') if not res_block_col: raise exceptions.MissingAttributeError( attribute='ResourceBlocks', resource=self._path) return res_block_col.get('@odata.id') def _get_resource_zones_collection_path(self): """Helper function to find the ResourceZoneCollections path""" res_zone_col = self.json.get('ResourceZones') if not res_zone_col: raise exceptions.MissingAttributeError( attribute='ResourceZones', resource=self._path) return res_zone_col.get('@odata.id') @property @utils.cache_it def resource_blocks(self): """Property to reference `ResourceBlockCollection` instance""" return resourceblock.ResourceBlockCollection( self._conn, self._get_resource_blocks_collection_path, redfish_version=self.redfish_version, registries=self.registries, root=self.root) @property @utils.cache_it def resource_zones(self): """Property to reference `ResourceZoneCollection` instance""" return resourcezone.ResourceZoneCollection( self._conn, self._get_resource_zones_collection_path, redfish_version=self.redfish_version, registries=self.registries, root=self.root) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/compositionservice/constants.py0000664000175000017500000000635700000000000024431 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # Values come from the Redfish ResourceBlock json-schema. # https://redfish.dmtf.org/schemas/ResourceBlock.v1_3_0.json import enum class CompositionState(enum.Enum): COMPOSING = 'Composing' """Intermediate state indicating composition is in progress.""" COMPOSED_AND_AVAILABLE = 'ComposedAndAvailable' """Indicates the Resource Block is currently participating in one or more compositions, and is available to be used in more compositions.""" COMPOSED = 'Composed' """Final successful state of a Resource Block which has participated in composition.""" UNUSED = 'Unused' """Indicates the Resource Block is free and can participate in composition.""" FAILED = 'Failed' """The final composition resulted in failure and manual intervention may be required to fix it.""" UNAVAILABLE = 'Unavailable' """Indicates the Resource Block has been made unavailable by the service, such as due to maintenance being performed on the Resource Block.""" # Backward compatibility COMPOSITION_STATE_COMPOSING = CompositionState.COMPOSING COMPOSITION_STATE_COMPOSED_AND_AVAILABLE = \ CompositionState.COMPOSED_AND_AVAILABLE COMPOSITION_STATE_COMPOSED = CompositionState.COMPOSED COMPOSITION_STATE_UNUSED = CompositionState.UNUSED COMPOSITION_STATE_FAILED = CompositionState.FAILED COMPOSITION_STATE_UNAVAILABLE = CompositionState.UNAVAILABLE class ResourceBlockType(enum.Enum): COMPUTE = 'Compute' """This Resource Block contains both Processor and Memory resources in a manner that creates a compute complex.""" PROCESSOR = 'Processor' """This Resource Block contains Processor resources.""" MEMORY = 'Memory' """This Resource Block contains Memory resources.""" NETWORK = 'Network' """This Resource Block contains Network resources, such as Ethernet Interfaces.""" STORAGE = 'Storage' """This Resource Block contains Storage resources, such as Storage and Simple Storage.""" COMPUTER_SYSTEM = 'ComputerSystem' """This Resource Block contains ComputerSystem resources.""" EXPANSION = 'Expansion' """This Resource Block is capable of changing over time based on its configuration. Different types of devices within this Resource Block can be added and removed over time.""" # Backward compatibility RESOURCE_BLOCK_TYPE_COMPUTE = ResourceBlockType.COMPUTE RESOURCE_BLOCK_TYPE_PROCESSOR = ResourceBlockType.PROCESSOR RESOURCE_BLOCK_TYPE_MEMORY = ResourceBlockType.MEMORY RESOURCE_BLOCK_TYPE_NETWORK = ResourceBlockType.NETWORK RESOURCE_BLOCK_TYPE_STORAGE = ResourceBlockType.STORAGE RESOURCE_BLOCK_TYPE_COMPUTERSYSTEM = ResourceBlockType.COMPUTER_SYSTEM RESOURCE_BLOCK_TYPE_EXPANSION = ResourceBlockType.EXPANSION ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/compositionservice/resourceblock.py0000664000175000017500000001031200000000000025241 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # This is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/ResourceBlock.v1_1_0.json import logging from sushy.resources import base from sushy.resources import common from sushy.resources.compositionservice import constants LOG = logging.getLogger(__name__) class CompositionStatusField(base.CompositeField): composition_state = base.MappedField( 'CompositionState', constants.CompositionState, required=True) """Inform the client, state of the resource block""" max_compositions = base.Field('MaxCompositions') """The maximum number of compositions""" number_of_compositions = base.Field('NumberOfCompositions') """The number of compositions""" reserved_state = base.Field('Reserved') """Inform the resource block has been identified by a client""" sharing_capable = base.Field('SharingCapable') """Indicates if this Resource Block is capable of participating in multiple compositions simultaneously""" sharing_enabled = base.Field('SharingEnabled') """Indicates if this Resource Block is allowed to participate in multiple compositions simultaneously""" class ResourceBlock(base.ResourceBase): composition_status = CompositionStatusField( 'CompositionStatus', required=True) """The composition state of resource block""" description = base.Field('Description') """The resource block description""" identity = base.Field('Id', required=True) """The resource block identity string""" name = base.Field('Name', required=True) """The resource block name""" resource_block_type = base.MappedField( 'ResourceBlockType', constants.ResourceBlockType, required=True) """The type of resource block""" status = common.StatusField('Status') """The status of resource block""" def __init__(self, connector, identity, redfish_version=None, registries=None, root=None): """A class representing a ResourceBlock :param connector: A Connector instance :param identity: The identity of the ResourceBlock resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, identity, redfish_version=redfish_version, registries=registries, root=root) class ResourceBlockCollection(base.ResourceCollectionBase): name = base.Field('Name') """The resource block collection name""" description = base.Field('Description') """The resource block collection description""" @property def _resource_type(self): return ResourceBlock def __init__(self, connector, identity, redfish_version=None, registries=None, root=None): """A class representing a ResourceBlockCollection :param connector: A Connector instance :param identity: A identity of the ResourceBlock resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, identity, redfish_version=redfish_version, registries=registries, root=root) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/compositionservice/resourcezone.py0000664000175000017500000000700600000000000025130 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # This is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/Zone.v1_2_0.json import logging from sushy.resources import base from sushy.resources import common LOG = logging.getLogger(__name__) class LinksField(base.CompositeField): endpoints = base.Field('Endpoints') """The references to the endpoints that are contained in this zone""" involved_switches = base.Field('InvolvedSwitches') """The references to the switches in this zone""" resource_blocks = base.Field('ResourceBlocks') """The references to the Resource Blocks that are used in this zone""" class ResourceZone(base.ResourceBase): # Note(dnuka): This patch doesn't contain 100% of the ResourceZone description = base.Field('Description') """The resources zone description""" identity = base.Field('Id', required=True) """The resource zone identity string""" links = LinksField('Links') """The references to other resources that are related to this resource""" name = base.Field('Name', required=True) """The resource zone name""" status = common.StatusField('Status') """The resource zone status""" def __init__(self, connector, identity, redfish_version=None, registries=None, root=None): """A class representing a ResourceZone :param connector: A Connector instance :param identity: The identity of the ResourceZone resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, identity, redfish_version=redfish_version, registries=registries, root=root) class ResourceZoneCollection(base.ResourceCollectionBase): name = base.Field('Name') """The resource zone collection name""" description = base.Field('Description') """The resource zone collection description""" @property def _resource_type(self): return ResourceZone def __init__(self, connector, identity, redfish_version=None, registries=None, root=None): """A class representing a ResourceZoneCollection :param connector: A Connector instance :param identity: The identity of the ResourceZone resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, identity, redfish_version=redfish_version, registries=registries, root=root) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/constants.py0000664000175000017500000003027500000000000020501 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # Values comes from the Redfish System json-schema: # http://redfish.dmtf.org/schemas/v1/Resource.json and # https://redfish.dmtf.org/schemas/v1/Settings.v1_3_3.json and # https://redfish.dmtf.org/schemas/v1/Protocol.json import enum from sushy.resources.registry import constants as reg_cons from sushy.resources.taskservice import constants as ts_cons class Health(enum.Enum): """Health related constants.""" OK = 'OK' """Normal.""" WARNING = 'Warning' """A condition requires attention.""" CRITICAL = 'Critical' """A critical condition requires immediate attention.""" # Backward compatibility HEALTH_OK = Health.OK HEALTH_WARNING = Health.WARNING HEALTH_CRITICAL = Health.CRITICAL class State(enum.Enum): """State related constants.""" ENABLED = 'Enabled' """This function or resource is enabled.""" DISABLED = 'Disabled' """This function or resource is disabled.""" STANDBY_OFFLINE = 'StandbyOffline' """This function or resource is enabled but awaits an external action to activate it.""" STANDBY_SPARE = 'StandbySpare' """This function or resource is part of a redundancy set and awaits a failover or other external action to activate it.""" IN_TEST = 'InTest' """This function or resource is undergoing testing, or is in the process of capturing information for debugging.""" STARTING = 'Starting' """This function or resource is starting.""" ABSENT = 'Absent' """This function or resource is either not present or detected.""" UNAVAILABLE_OFFLINE = 'UnavailableOffline' """This function or resource is present but cannot be used.""" DEFERRING = 'Deferring' """The element does not process any commands but queues new requests.""" QUIESCED = 'Quiesced' """The element is enabled but only processes a restricted set of commands.""" UPDATING = 'Updating' """The element is updating and might be unavailable or degraded.""" QUALIFIED = 'Qualified' """The element quality is within the acceptable range of operation.""" # Backward compatibility STATE_ENABLED = State.ENABLED STATE_DISABLED = State.DISABLED STATE_ABSENT = State.ABSENT STATE_STANDBYOFFLINE = State.STANDBY_OFFLINE STATE_STANDBYSPARE = State.STANDBY_SPARE STATE_INTEST = State.IN_TEST STATE_STARTING = State.STARTING STATE_UNAVAILABLEOFFLINE = State.UNAVAILABLE_OFFLINE STATE_DEFERRING = State.DEFERRING STATE_QUIESCED = State.QUIESCED STATE_UPDATING = State.UPDATING # Backward compatibility, the type moved to taskservice.constants TASK_STATE_NEW = ts_cons.TaskState.NEW TASK_STATE_STARTING = ts_cons.TaskState.STARTING TASK_STATE_RUNNING = ts_cons.TaskState.RUNNING TASK_STATE_SUSPENDED = ts_cons.TaskState.SUSPENDED TASK_STATE_INTERRUPTED = ts_cons.TaskState.INTERRUPTED TASK_STATE_PENDING = ts_cons.TaskState.PENDING TASK_STATE_STOPPING = ts_cons.TaskState.STOPPING TASK_STATE_COMPLETED = ts_cons.TaskState.COMPLETED TASK_STATE_KILLED = ts_cons.TaskState.KILLED TASK_STATE_EXCEPTION = ts_cons.TaskState.EXCEPTION TASK_STATE_SERVICE = ts_cons.TaskState.SERVICE TASK_STATE_CANCELLING = ts_cons.TaskState.CANCELLING TASK_STATE_CANCELLED = ts_cons.TaskState.CANCELLED # Backward compatibility, the type moved to registry.constants PARAMTYPE_STRING = reg_cons.MessageParamType.STRING PARAMTYPE_NUMBER = reg_cons.MessageParamType.NUMBER # Backward compatibility (Severity is an alias of Health after 1.1.0) Severity = Health SEVERITY_OK = Severity.OK SEVERITY_WARNING = Severity.WARNING SEVERITY_CRITICAL = Severity.CRITICAL class IndicatorLED(enum.Enum): """Indicator LED Constants""" LIT = 'Lit' """The Indicator LED is lit""" BLINKING = 'Blinking' """The Indicator LED is blinking""" OFF = 'Off' """The Indicator LED is off""" UNKNOWN = 'Unknown' """The state of the Indicator LED cannot be determine""" # Backward compatibility INDICATOR_LED_LIT = IndicatorLED.LIT INDICATOR_LED_BLINKING = IndicatorLED.BLINKING INDICATOR_LED_OFF = IndicatorLED.OFF INDICATOR_LED_UNKNOWN = IndicatorLED.UNKNOWN class PowerState(enum.Enum): """System PowerState constants""" ON = 'On' """The resource is powered on""" OFF = 'Off' """The resource is powered off, although some components may continue to have AUX power such as management controller""" POWERING_ON = 'PoweringOn' """A temporary state between Off and On. This temporary state can be very short""" POWERING_OFF = 'PoweringOff' """A temporary state between On and Off. The power off action can take time while the OS is in the shutdown process""" PAUSED = 'Paused' """The resource is paused.""" # Backward compatibility POWER_STATE_ON = PowerState.ON POWER_STATE_OFF = PowerState.OFF POWER_STATE_POWERING_ON = PowerState.POWERING_ON POWER_STATE_POWERING_OFF = PowerState.POWERING_OFF class ResetType(enum.Enum): """Reset action constants""" ON = 'On' """Turn on the unit.""" FORCE_OFF = 'ForceOff' """Turn off the unit immediately (non-graceful shutdown).""" GRACEFUL_SHUTDOWN = 'GracefulShutdown' """Shut down gracefully and power off.""" GRACEFUL_RESTART = 'GracefulRestart' """Shut down gracefully and restart the system.""" FORCE_RESTART = 'ForceRestart' """Shut down immediately and non-gracefully and restart the system.""" NMI = 'Nmi' """Generate a diagnostic interrupt, which is usually an NMI on x86 systems, to stop normal operations, complete diagnostic actions, and, typically, halt the system.""" FORCE_ON = 'ForceOn' """Turn on the unit immediately.""" PUSH_POWER_BUTTON = 'PushPowerButton' """Simulate the pressing of the physical power button on this unit.""" POWER_CYCLE = 'PowerCycle' """Power cycle the unit. Behaves like a full power removal, followed by a power restore to the resource.""" SUSPEND = 'Suspend' """Write the state of the unit to disk before powering off. This allows for the state to be restored when powered back on.""" PAUSE = 'Pause' """Pause execution on the unit but do not remove power. This is typically a feature of virtual machine hypervisors.""" RESUME = 'Resume' """Resume execution on the paused unit. This is typically a feature of virtual machine hypervisors.""" # Backward compatibility RESET_TYPE_ON = ResetType.ON RESET_TYPE_FORCE_OFF = ResetType.FORCE_OFF RESET_TYPE_GRACEFUL_SHUTDOWN = ResetType.GRACEFUL_SHUTDOWN RESET_TYPE_GRACEFUL_RESTART = ResetType.GRACEFUL_RESTART RESET_TYPE_FORCE_RESTART = ResetType.FORCE_RESTART RESET_TYPE_NMI = ResetType.NMI RESET_TYPE_FORCE_ON = ResetType.FORCE_ON RESET_TYPE_PUSH_POWER_BUTTON = ResetType.PUSH_POWER_BUTTON RESET_TYPE_POWER_CYCLE = ResetType.POWER_CYCLE class Protocol(enum.Enum): """Protocol type constants""" PCIe = 'PCIe' """PCI Express.""" AHCI = 'AHCI' """Advanced Host Controller Interface (AHCI).""" UHCI = 'UHCI' """Universal Host Controller Interface (UHCI).""" SAS = 'SAS' """Serial Attached SCSI.""" SATA = 'SATA' """Serial AT Attachment.""" USB = 'USB' """Universal Serial Bus (USB).""" NVMe = 'NVMe' """Non-Volatile Memory Express (NVMe).""" FC = 'FC' """Fibre Channel.""" iSCSI = 'iSCSI' """Internet SCSI.""" FCoE = 'FCoE' """Fibre Channel over Ethernet (FCoE).""" FCP = 'FCP' """Fibre Channel Protocol for SCSI.""" FICON = 'FICON' """FIbre CONnection (FICON).""" NVMe_OVER_FABRICS = 'NVMeOverFabrics' """NVMe over Fabrics.""" SMB = 'SMB' """Server Message Block (SMB). Also known as the Common Internet File System (CIFS).""" NFSv3 = 'NFSv3' """Network File System (NFS) version 3.""" NFSv4 = 'NFSv4' """Network File System (NFS) version 4.""" HTTP = 'HTTP' """Hypertext Transport Protocol (HTTP).""" HTTPS = 'HTTPS' """Hypertext Transfer Protocol Secure (HTTPS).""" FTP = 'FTP' """File Transfer Protocol (FTP).""" SFTP = 'SFTP' """SSH File Transfer Protocol (SFTP).""" iWARP = 'iWARP' """Internet Wide Area RDMA Protocol (iWARP).""" RoCE = 'RoCE' """RDMA over Converged Ethernet Protocol.""" RoCEv2 = 'RoCEv2' """RDMA over Converged Ethernet Protocol Version 2.""" I2C = 'I2C' """Inter-Integrated Circuit Bus.""" TCP = 'TCP' """Transmission Control Protocol (TCP).""" UDP = 'UDP' """User Datagram Protocol (UDP).""" TFTP = 'TFTP' """Trivial File Transfer Protocol (TFTP).""" GEN_Z = 'GenZ' """GenZ.""" MULTI_PROTOCOL = 'MultiProtocol' """Multiple Protocols.""" INFINI_BAND = 'InfiniBand' """InfiniBand.""" ETHERNET = 'Ethernet' """Ethernet.""" NVLINK = 'NVLink' """NVLink.""" OEM = 'OEM' """OEM-specific.""" DISPLAY_PORT = 'DisplayPort' """DisplayPort.""" HDMI = 'HDMI' """HDMI.""" VGA = 'VGA' """VGA.""" DVI = 'DVI' """DVI.""" # Backward compatibility PROTOCOL_TYPE_AHCI = Protocol.AHCI PROTOCOL_TYPE_FC = Protocol.FC PROTOCOL_TYPE_FCP = Protocol.FCP PROTOCOL_TYPE_FCoE = Protocol.FCoE PROTOCOL_TYPE_FICON = Protocol.FICON PROTOCOL_TYPE_FTP = Protocol.FTP PROTOCOL_TYPE_HTTP = Protocol.HTTP PROTOCOL_TYPE_HTTPS = Protocol.HTTPS PROTOCOL_TYPE_I2C = Protocol.I2C PROTOCOL_TYPE_NFSv3 = Protocol.NFSv3 PROTOCOL_TYPE_NFSv4 = Protocol.NFSv4 PROTOCOL_TYPE_NVMe = Protocol.NVMe PROTOCOL_TYPE_NVMeOverFabrics = Protocol.NVMe_OVER_FABRICS PROTOCOL_TYPE_OEM = Protocol.OEM PROTOCOL_TYPE_PCIe = Protocol.PCIe PROTOCOL_TYPE_RoCE = Protocol.RoCE PROTOCOL_TYPE_RoCEv2 = Protocol.RoCEv2 PROTOCOL_TYPE_SAS = Protocol.SAS PROTOCOL_TYPE_SATA = Protocol.SATA PROTOCOL_TYPE_SFTP = Protocol.SFTP PROTOCOL_TYPE_SMB = Protocol.SMB PROTOCOL_TYPE_TFTP = Protocol.TFTP PROTOCOL_TYPE_UHCI = Protocol.UHCI PROTOCOL_TYPE_USB = Protocol.USB PROTOCOL_TYPE_iSCSI = Protocol.iSCSI PROTOCOL_TYPE_iWARP = Protocol.iWARP # These values are not in the Protocol enum, using them is probably wrong! PROTOCOL_TYPE_CIFS = Protocol.SMB PROTOCOL_TYPE_NFS = Protocol.NFSv4 PROTOCOL_TYPE_SCP = Protocol.SFTP class DurableNameFormat(enum.Enum): """Durable name format constants""" NAA = 'NAA' """The Name Address Authority (NAA) format.""" iQN = 'iQN' """The iSCSI Qualified Name (iQN).""" FC_WWN = 'FC_WWN' """The Fibre Channel (FC) World Wide Name (WWN).""" UUID = 'UUID' """The Universally Unique Identifier (UUID).""" EUI = 'EUI' """The IEEE-defined 64-bit Extended Unique Identifier (EUI).""" NQN = 'NQN' """The NVMe Qualified Name (NQN).""" NSID = 'NSID' """The NVM Namespace Identifier (NSID).""" NGUID = 'NGUID' """The Namespace Globally Unique Identifier (NGUID).""" # Backward compatibility DURABLE_NAME_FORMAT_NAA = DurableNameFormat.NAA DURABLE_NAME_FORMAT_iQN = DurableNameFormat.iQN DURABLE_NAME_FORMAT_FC_WWN = DurableNameFormat.FC_WWN DURABLE_NAME_FORMAT_UUID = DurableNameFormat.UUID DURABLE_NAME_FORMAT_EUI = DurableNameFormat.EUI DURABLE_NAME_FORMAT_NQN = DurableNameFormat.NQN DURABLE_NAME_FORMAT_NSID = DurableNameFormat.NSID class ApplyTime(enum.Enum): """Apply time constants""" IMMEDIATE = 'Immediate' """Apply immediately.""" ON_RESET = 'OnReset' """Apply on a reset.""" AT_MAINTENANCE_WINDOW_START = 'AtMaintenanceWindowStart' """Apply during a maintenance window as specified by an administrator.""" IN_MAINTENANCE_WINDOW_ON_RESET = 'InMaintenanceWindowOnReset' """Apply after a reset but within maintenance window as specified by an administrator.""" # Backward compatibility APPLY_TIME_IMMEDIATE = ApplyTime.IMMEDIATE APPLY_TIME_ON_RESET = ApplyTime.ON_RESET APPLY_TIME_MAINT_START = ApplyTime.AT_MAINTENANCE_WINDOW_START APPLY_TIME_MAINT_RESET = ApplyTime.IN_MAINTENANCE_WINDOW_ON_RESET ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6475148 sushy-5.5.0/sushy/resources/eventservice/0000775000175000017500000000000000000000000020606 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/eventservice/__init__.py0000664000175000017500000000000000000000000022705 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/eventservice/constants.py0000664000175000017500000000333700000000000023202 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # EventType constants # http://redfish.dmtf.org/schemas/v1/Event.json#/definitions/EventType # https://redfish.dmtf.org/schemas/v1/EventService.v1_0_6.json import enum class EventType(enum.Enum): STATUS_CHANGE = 'StatusChange' """The status of a resource has changed.""" RESOURCE_UPDATED = 'ResourceUpdated' """A resource has been updated.""" RESOURCE_ADDED = 'ResourceAdded' """A resource has been added.""" RESOURCE_REMOVED = 'ResourceRemoved' """A resource has been removed.""" ALERT = 'Alert' """A condition requires attention.""" METRIC_REPORT = 'MetricReport' """The telemetry service is sending a metric report.""" OTHER = 'Other' """Because EventType is deprecated as of Redfish Specification v1.6, the event is based on a registry or resource but not an EventType.""" # Backward compatibility EVENT_TYPE_STATUS_CHANGE = EventType.STATUS_CHANGE EVENT_TYPE_RESOURCE_UPDATED = EventType.RESOURCE_UPDATED EVENT_TYPE_RESOURCE_ADDED = EventType.RESOURCE_ADDED EVENT_TYPE_RESOURCE_REMOVED = EventType.RESOURCE_REMOVED EVENT_TYPE_ALERT = EventType.ALERT EVENT_TYPE_METRIC_REPORT = EventType.METRIC_REPORT EVENT_TYPE_OTHER = EventType.OTHER ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/eventservice/eventdestination.py0000664000175000017500000001060500000000000024545 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # Redfish standard schema. # https://redfish.dmtf.org/schemas/v1/EventDestination.v1_0_0.json import logging from sushy.resources import base LOG = logging.getLogger(__name__) class EventDestination(base.ResourceBase): identity = base.Field('Id', required=True) """The EventDestination resource identity""" name = base.Field('Name', required=True) """The EventDestination resource name""" context = base.Field('Context') """A client-supplied string that is stored with the event destination subscription""" description = base.Field('Description') """The description of the EventDestination resource""" destination = base.Field('Destination') """The URI of the destination Event Service""" event_types = base.Field('EventTypes', adapter=list) """The types of events that shall be sent to the destination""" protocol = base.Field('Protocol') """Contain the protocol type that the event will use for sending the event to the destination. A value of Redfish shall be used to indicate that the event type shall adhere to that defined in the Redfish specification""" http_headers = base.Field('HttpHeaders', adapter=list) """This is for setting HTTP headers, such as authorization information. This object will be null on a GET.""" def __init__(self, connector, identity, redfish_version=None, registries=None, root=None): """A class representing an EventDestination :param connector: A Connector instance :param identity: The identity of the EventDestination resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of given version. :param registries: Dict of registries to be used in any resource that needs registries to parse messages. :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, identity, redfish_version=redfish_version, registries=registries, root=root) def delete(self): """Delete an EventDestination :raises: ConnectionError :raises: HTTPError """ self._conn.delete(self._path) class EventDestinationCollection(base.ResourceCollectionBase): name = base.Field('Name') """The EventDestination collection name""" description = base.Field('Description') """The EventDestination collection description""" @property def _resource_type(self): return EventDestination def __init__(self, connector, identity, redfish_version=None, registries=None, root=None): """A class representing a EventDestinationCollection :param connector: A Connector instance :param identity: The identity of the EventDestination resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of given version. :param registries: Dict of registries to be used in any resource that needs registries to parse messages. :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, identity, redfish_version=redfish_version, registries=registries, root=root) def _create(self, payload): r = self._conn.post(self._path, data=payload) location = r.headers.get('Location') return r, location def create(self, payload): """Create a Subscription :param payload: The payload representing the subscription. :raises: ConnectionError :raises: HTTPError :returns: The new subscription """ r, location = self._create(payload) if r.status_code == 201: if location: self.refresh() return self.get_member(location) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/eventservice/eventservice.py0000664000175000017500000001364700000000000023675 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # Redfish standard schema. # https://redfish.dmtf.org/schemas/v1/EventService.v1_0_8.json import logging from sushy import exceptions from sushy.resources import base from sushy.resources import common from sushy.resources.eventservice import constants from sushy.resources.eventservice import eventdestination LOG = logging.getLogger(__name__) class ActionsField(base.CompositeField): submit_test_event = common.ActionField( '#EventService.SubmitTestEvent') class EventService(base.ResourceBase): identity = base.Field('Id', required=True) """The EventService resource identity""" name = base.Field('Name', required=True) """The EventService resource name""" delivery_retry_attempts = base.Field('DeliveryRetryAttempts') """Number of attempts an event posting is retried before the subscription is terminated. This retry is at the service level, meaning the HTTP POST to the Event Destination was returned by the HTTP operation as unsuccessful (4xx or 5xx return code) or an HTTP timeout occurred this many times before the Event Destination subscription is terminated """ delivery_retry_interval = base.Field('DeliveryRetryIntervalSeconds') """Number of seconds between retry attempts for sending any given Event""" event_types_for_subscription = base.Field('EventTypesForSubscription', adapter=list) """Types of Events that can be subscribed to""" service_enabled = base.Field('ServiceEnabled', adapter=bool) """Indicates whether the EventService is enabled""" status = common.StatusField('Status') """The status of the EventService""" _actions = ActionsField('Actions', required=True) def __init__(self, connector, identity, redfish_version=None, registries=None, root=None): """A class representing a EventService :param connector: A Connector instance :param identity: The identity of the EventService resource :param redfish_version: The version of Redfish. Used to construct the object according to schema of given version :param registries: Dict of registries to be used in any resource that needs registries to parse messages. :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, identity, redfish_version=redfish_version, registries=registries, root=root) def _get_submit_test_event(self): submit_test_event_action = self._actions.submit_test_event if not submit_test_event_action: raise exceptions.MissingActionError( action='#EventService.SubmitTestEvent', resource=self._path) return submit_test_event_action def submit_test_event(self, event_id, event_timestamp, event_type, message, message_args, message_id, origin, severity): """Submit Test Event is used to to send a test event to the BMC :param event_id: ID of event to be added. :param event_timestamp: time stamp of event to be added. :param event_type: type of event to be added. :param message: human readable message of event to be added. :param message_args: array of message arguments of the event to be added. :param message_id: message ID of event to be added. :param origin: string of the URL within the OriginOfCondition property of the event to be added :param severity: the Severity of event to be added. :param target: The link to invoke action. :raises: MissingActionError if the EvenService does not have the action. """ target_uri = self._get_submit_test_event().target_uri data = { 'EventId': event_id, 'EventTimestamp': event_timestamp, 'EventType': event_type, 'Message': message, 'MessageArgs': message_args, 'MessageId': message_id, 'OriginOfCondition': origin, 'Severity': severity } self._conn.post(target_uri, data=data) def get_event_types_for_subscription(self): """Get the Types of Events that can be subscribed to :returns: A set with the types of Events that can be subscribed to. """ if not self.event_types_for_subscription: LOG.warning('Could not figure out the Event types supported by ' 'the EventService %s', self.identity) return set(constants.EventType) return {v for v in constants.EventType if v.value in self.event_types_for_subscription} def _get_subscriptions_collection_path(self): """Helper function to find the EventDestinationCollections path""" subscriptions = self.json.get('Subscriptions') if not subscriptions: raise exceptions.MissingAttributeError( attribute='Subscriptions', resource=self._path) return subscriptions.get('@odata.id') @property def subscriptions(self): """Reference to a collection of Event Destination resources""" return eventdestination.EventDestinationCollection( self._conn, self._get_subscriptions_collection_path(), redfish_version=self.redfish_version, registries=self.registries, root=self.root) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6515148 sushy-5.5.0/sushy/resources/fabric/0000775000175000017500000000000000000000000017332 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/fabric/__init__.py0000664000175000017500000000000000000000000021431 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/fabric/constants.py0000664000175000017500000000641400000000000021725 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # Values come from the Redfish Fabric json-schema: # https://redfish.dmtf.org/schemas/v1/Fabric.v1_2_2.json # https://redfish.dmtf.org/schemas/v1/Endpoint.v1_6_1.json import enum class EntityRole(enum.Enum): """Entity role constants""" INITIATOR = 'Initiator' """The entity sends commands, messages, or other types of requests to other entities on the fabric, but cannot receive commands from other entities.""" TARGET = 'Target' """The entity receives commands, messages, or other types of requests from other entities on the fabric, but cannot send commands to other entities.""" BOTH = 'Both' """The entity can both send and receive commands, messages, and other requests to or from other entities on the fabric.""" # Backward compatibility ENTITY_ROLE_INITIATOR = EntityRole.INITIATOR ENTITY_ROLE_TARGET = EntityRole.TARGET ENTITY_ROLE_BOTH = EntityRole.BOTH class EntityType(enum.Enum): """Entity type constants""" STORAGE_INITIATOR = 'StorageInitiator' """The entity is a storage initiator.""" ROOT_COMPLEX = 'RootComplex' """The entity is a PCI(e) root complex.""" NETWORK_CONTROLLER = 'NetworkController' """The entity is a network controller.""" DRIVE = 'Drive' """The entity is a drive.""" STORAGE_EXPANDER = 'StorageExpander' """The entity is a storage expander.""" DISPLAY_CONTROLLER = 'DisplayController' """The entity is a display controller.""" BRIDGE = 'Bridge' """The entity is a PCI(e) bridge.""" PROCESSOR = 'Processor' """The entity is a processor.""" VOLUME = 'Volume' """The entity is a volume.""" ACCELERATION_FUNCTION = 'AccelerationFunction' """The entity is an acceleration function realized through a device, such as an FPGA.""" MEDIA_CONTROLLER = 'MediaController' """The entity is a media controller.""" MEMORY_CHUNK = 'MemoryChunk' """The entity is a memory chunk.""" SWITCH = 'Switch' """The entity is a switch, not an expander. Use `Expander` for expanders.""" FABRIC_BRIDGE = 'FabricBridge' """The entity is a fabric bridge.""" MANAGER = 'Manager' """The entity is a manager.""" STORAGE_SUBSYSTEM = 'StorageSubsystem' """The entity is a storage subsystem.""" # Backward compatibility ENTITY_TYPE_STORAGE_INITIATOR = EntityType.STORAGE_INITIATOR ENTITY_TYPE_ROOT_COMPLEX = EntityType.ROOT_COMPLEX ENTITY_TYPE_NETWORK_CONTROLLER = EntityType.NETWORK_CONTROLLER ENTITY_TYPE_DRIVE = EntityType.DRIVE ENTITY_TYPE_STORAGE_EXPANDER = EntityType.STORAGE_EXPANDER ENTITY_TYPE_DISPLAY_CONTROLLER = EntityType.DISPLAY_CONTROLLER ENTITY_TYPE_PCI_BRIDGE = EntityType.BRIDGE ENTITY_TYPE_PROCESSOR = EntityType.PROCESSOR ENTITY_TYPE_VOLUME = EntityType.VOLUME ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/fabric/endpoint.py0000664000175000017500000001275500000000000021536 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/Endpoint.v1_3_0.json import logging from sushy.resources import base from sushy.resources import common from sushy.resources import constants as res_cons from sushy.resources.fabric import constants as fab_cons from sushy.resources import ipaddresses from sushy import utils LOG = logging.getLogger(__name__) class IPv4AddressField(base.CompositeField): address = base.Field('Address') """This is the IPv4 Address.""" gateway = base.Field('Gateway') """This is the IPv4 gateway for this address.""" subnet_mask = base.Field('SubnetMask') """This is the IPv4 Subnet mask.""" address_origin = base.MappedField('AddressOrigin', ipaddresses.IPv4AddressOrigin) """This indicates how the address was determined.""" class IPv6AddressField(base.CompositeField): address = base.Field('Address') """This is the IPv6 Address.""" prefix_length = base.Field('PrefixLength', adapter=utils.int_or_none) """This is the IPv6 Address Prefix Length.""" address_origin = base.MappedField('AddressOrigin', ipaddresses.IPv6AddressOrigin) """This indicates how the address was determined.""" address_state = base.MappedField('AddressState', ipaddresses.AddressState) """The current state of this address as defined in RFC 4862.""" class IPTransportDetailsListField(base.ListField): """IP transport details This array contains details for each IP transport supported by this endpoint. The array structure can be used to model multiple IP addresses for this endpoint. """ port = base.Field('Port', adapter=utils.int_or_none) """The UDP or TCP port number used by the Endpoint.""" transport_protocol = base.MappedField('TransportProtocol', res_cons.Protocol) """The protocol used by the connection entity.""" ipv4_address = IPv4AddressField('IPv4Address') """The IPv4 address object.""" ipv6_address = IPv6AddressField('IPv6Address') """The IPv6 address object.""" class PciIdField(base.CompositeField): device_id = base.Field('DeviceId') """The Device ID of this PCIe function.""" subsystem_id = base.Field('SubsystemId') """The Subsystem ID of this PCIefunction.""" subsystem_vendor_id = base.Field('SubsystemVendorId') """The Subsystem Vendor ID of thisPCIe function.""" vendor_id = base.Field('VendorId') """The Vendor ID of this PCIe function.""" class ConnectedEntitiesListField(base.ListField): """All the entities connected to this endpoint.""" pci_class_code = base.Field('PciClassCode') """The Class Code, Subclass code, and Programming Interface code of this PCIe function.""" pci_function_number = base.Field('PciFunctionNumber', adapter=utils.int_or_none) """The PCI ID of the connected entity.""" entity_pci_id = PciIdField('EntityPciId') """The PCI ID of the connected entity.""" identifiers = common.IdentifiersListField('Identifiers', default=[]) """Identifiers for the remote entity.""" entity_role = base.MappedField('EntityRole', fab_cons.EntityRole) """The role of the connected entity.""" entity_type = base.MappedField('EntityType', fab_cons.EntityType) """The type of the connected entity.""" class Endpoint(base.ResourceBase): """This class represents a fabric endpoint. It represents the properties of an entity that sends or receives protocol defined messages over a transport. """ identity = base.Field('Id', required=True) """Identifier for the endpoint""" name = base.Field('Name', required=True) """The endpoint name""" description = base.Field('Description') """The endpoint description""" status = common.StatusField('Status') """The endpoint status""" host_reservation_memory_bytes = base.Field('HostReservationMemoryBytes', adapter=utils.int_or_none) """The amount of memory in Bytes that the Host should allocate to connect to this endpoint. """ endpoint_protocol = base.MappedField('EndpointProtocol', res_cons.Protocol) """The protocol supported by this endpoint.""" pci_id = PciIdField('PciId') """The PCI ID of the endpoint.""" IP_transport_details = IPTransportDetailsListField('IPTransportDetails') """This array contains details for each IP transport supported by this endpoint. The array structure can be used to model multiple IP addresses for this endpoint.""" connected_entities = ConnectedEntitiesListField('ConnectedEntities') """All entities connected to this endpoint.""" class EndpointCollection(base.ResourceCollectionBase): """Represents a collection of endpoints associated with the fabric.""" @property def _resource_type(self): return Endpoint ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/fabric/fabric.py0000664000175000017500000000705500000000000021141 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is referred from Redfish standard schema. # http://redfish.dmtf.org/schemas/v1/Fabric.v1_0_4.json import logging from sushy.resources import base from sushy.resources import common from sushy.resources import constants as res_cons from sushy.resources.fabric import endpoint as fab_endpoint from sushy import utils LOG = logging.getLogger(__name__) class Fabric(base.ResourceBase): """Fabric resource The Fabric represents a simple fabric consisting of one or more switches, zero or more endpoints, and zero or more zones. """ identity = base.Field('Id', required=True) """Identifier for the fabric""" name = base.Field('Name', required=True) """The fabric name""" description = base.Field('Description') """The fabric description""" max_zones = base.Field('MaxZones', adapter=utils.int_or_none) """The maximum number of zones the switch can currently configure""" status = common.StatusField('Status') """The fabric status""" fabric_type = base.MappedField('FabricType', res_cons.Protocol) """The protocol being sent over this fabric""" def __init__(self, connector, identity, redfish_version=None, registries=None, root=None): """A class representing a Fabric :param connector: A Connector instance :param identity: The identity of the Fabric resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of the given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, identity, redfish_version=redfish_version, registries=registries, root=root) @property @utils.cache_it def endpoints(self): return fab_endpoint.EndpointCollection( self._conn, utils.get_sub_resource_path_by(self, 'Endpoints'), redfish_version=self.redfish_version, registries=self.registries, root=self.root) class FabricCollection(base.ResourceCollectionBase): @property def _resource_type(self): return Fabric def __init__(self, connector, path, redfish_version=None, registries=None, root=None): """A class representing a FabricCollection :param connector: A Connector instance :param path: The canonical path to the Fabric collection resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of the given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, path, redfish_version=redfish_version, registries=registries, root=root) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/ipaddresses.py0000664000175000017500000000530500000000000020767 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # Values come from the Redfish json-schema: # https://redfish.dmtf.org/schemas/v1/IPAddresses.v1_1_3.json import enum class AddressState(enum.Enum): PREFERRED = 'Preferred' """This address is currently within both its RFC4862-defined valid and preferred lifetimes.""" DEPRECATED = 'Deprecated' """This address is currently within its valid lifetime but is now outside its RFC4862-defined preferred lifetime.""" TENTATIVE = 'Tentative' """This address is currently undergoing Duplicate Address Detection (DAD) testing, as defined in RFC4862, section 5.4.""" FAILED = 'Failed' """This address has failed Duplicate Address Detection (DAD) testing, as defined in RFC4862, section 5.4, and is not currently in use.""" # Backward compatibility ADDRESS_STATE_PREFERRED = AddressState.PREFERRED ADDRESS_STATE_DEPRECATED = AddressState.DEPRECATED ADDRESS_STATE_TENTATIVE = AddressState.TENTATIVE ADDRESS_STATE_FAILED = AddressState.FAILED class IPv4AddressOrigin(enum.Enum): STATIC = 'Static' """A user-configured static address.""" DHCP = 'DHCP' """A DHCPv4 service-provided address.""" BOOTP = 'BOOTP' """A BOOTP service-provided address.""" LINK_LOCAL = 'IPv4LinkLocal' """The address is valid for only this network segment, or link.""" # Backward compatibility ADDRESS_ORIGIN_IPv4_BOOTP = IPv4AddressOrigin.BOOTP ADDRESS_ORIGIN_IPv4_DHCP = IPv4AddressOrigin.DHCP ADDRESS_ORIGIN_IPv4_IPv4LINKLOCAL = IPv4AddressOrigin.LINK_LOCAL ADDRESS_ORIGIN_IPv4_STATIC = IPv4AddressOrigin.STATIC class IPv6AddressOrigin(enum.Enum): STATIC = 'Static' """A static user-configured address.""" DHCP = 'DHCPv6' """A DHCPv6 service-provided address.""" LINK_LOCAL = 'LinkLocal' """The address is valid for only this network segment, or link.""" SLAAC = 'SLAAC' """A stateless autoconfiguration (SLAAC) service-provided address.""" # Backward compatibility ADDRESS_ORIGIN_IPv6_DHCPv6 = IPv6AddressOrigin.DHCP ADDRESS_ORIGIN_IPv6_LINKLOCAL = IPv6AddressOrigin.LINK_LOCAL ADDRESS_ORIGIN_IPv6_SLAAC = IPv6AddressOrigin.SLAAC ADDRESS_ORIGIN_IPv6_STATIC = IPv6AddressOrigin.STATIC ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6515148 sushy-5.5.0/sushy/resources/manager/0000775000175000017500000000000000000000000017516 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/manager/__init__.py0000664000175000017500000000000000000000000021615 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/manager/constants.py0000664000175000017500000001265000000000000022110 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # Values comes from the Redfish System json-schema 1.0.0: # http://redfish.dmtf.org/schemas/v1/Manager.v1_0_0.json#/definitions/Manager # noqa import enum from sushy.resources import constants as res_cons # Manager Reset action constants RESET_MANAGER_GRACEFUL_RESTART = res_cons.ResetType.GRACEFUL_RESTART """Perform a graceful shutdown followed by a restart of the system""" RESET_MANAGER_FORCE_RESTART = res_cons.ResetType.FORCE_RESTART """Perform an immediate (non-graceful) shutdown, followed by a restart""" class ManagerType(enum.Enum): """Manager Type constants""" MANAGEMENT_CONTROLLER = 'ManagementController' """A controller that primarily monitors or manages the operation of a device or system.""" ENCLOSURE_MANAGER = 'EnclosureManager' """A controller that provides management functions for a chassis or group of devices or systems.""" BMC = 'BMC' """A controller that provides management functions for a single computer system.""" RACK_MANAGER = 'RackManager' """A controller that provides management functions for a whole or part of a rack.""" AUXILIARY_CONTROLLER = 'AuxiliaryController' """A controller that provides management functions for a particular subsystem or group of devices.""" SERVICE = 'Service' """A software-based service that provides management functions.""" # Backward compatibility MANAGER_TYPE_MANAGEMENT_CONTROLLER = ManagerType.MANAGEMENT_CONTROLLER MANAGER_TYPE_ENCLOSURE_MANAGER = ManagerType.ENCLOSURE_MANAGER MANAGER_TYPE_BMC = ManagerType.BMC MANAGER_TYPE_RACK_MANAGER = ManagerType.RACK_MANAGER MANAGER_TYPE_AUXILIARY_CONTROLLER = ManagerType.AUXILIARY_CONTROLLER class GraphicalConnectType(enum.Enum): """Graphical Console constants""" KVMIP = 'KVMIP' """The controller supports a graphical console connection through a KVM- IP (redirection of Keyboard, Video, Mouse over IP) protocol.""" OEM = 'Oem' """The controller supports a graphical console connection through an OEM-specific protocol.""" # Backward compatibility GRAPHICAL_CONSOLE_KVMIP = GraphicalConnectType.KVMIP GRAPHICAL_CONSOLE_OEM = GraphicalConnectType.OEM class SerialConnectType(enum.Enum): """Serial Console constants""" SSH = 'SSH' """The controller supports a serial console connection through the SSH protocol.""" TELNET = 'Telnet' """The controller supports a serial console connection through the Telnet protocol.""" IPMI = 'IPMI' """The controller supports a serial console connection through the IPMI Serial Over LAN (SOL) protocol.""" OEM = 'Oem' """The controller supports a serial console connection through an OEM- specific protocol.""" # Backward compatibility SERIAL_CONSOLE_SSH = SerialConnectType.SSH SERIAL_CONSOLE_TELNET = SerialConnectType.TELNET SERIAL_CONSOLE_IPMI = SerialConnectType.IPMI SERIAL_CONSOLE_OEM = SerialConnectType.OEM class CommandConnectType(enum.Enum): """Command Shell constants""" SSH = 'SSH' """The controller supports a command shell connection through the SSH protocol.""" TELNET = 'Telnet' """The controller supports a command shell connection through the Telnet protocol.""" IPMI = 'IPMI' """The controller supports a command shell connection through the IPMI Serial Over LAN (SOL) protocol.""" OEM = 'Oem' """The controller supports a command shell connection through an OEM- specific protocol.""" # Backward compatibility COMMAND_SHELL_SSH = CommandConnectType.SSH COMMAND_SHELL_TELNET = CommandConnectType.TELNET COMMAND_SHELL_IPMI = CommandConnectType.IPMI COMMAND_SHELL_OEM = CommandConnectType.OEM class VirtualMediaType(enum.Enum): """Supported Virtual Media Type constants""" CD = 'CD' """A CD-ROM format (ISO) image.""" FLOPPY = 'Floppy' """A floppy disk image.""" USB_STICK = 'USBStick' """An emulation of a USB storage device.""" DVD = 'DVD' """A DVD-ROM format image.""" # Backward compatibility VIRTUAL_MEDIA_CD = VirtualMediaType.CD VIRTUAL_MEDIA_DVD = VirtualMediaType.DVD VIRTUAL_MEDIA_FLOPPY = VirtualMediaType.FLOPPY VIRTUAL_MEDIA_USBSTICK = VirtualMediaType.USB_STICK class ConnectedVia(enum.Enum): """Connected Via constants""" NOT_CONNECTED = 'NotConnected' """No current connection.""" URI = 'URI' """Connected to a URI location.""" APPLET = 'Applet' """Connected to a client application.""" OEM = 'Oem' """Connected through an OEM-defined method.""" # Backward compatibility CONNECTED_VIA_NOT_CONNECTED = ConnectedVia.NOT_CONNECTED CONNECTED_VIA_URI = ConnectedVia.URI CONNECTED_VIA_APPLET = ConnectedVia.APPLET CONNECTED_VIA_OEM = ConnectedVia.OEM class TransferMethod(enum.Enum): """Transfer methods""" STREAM = 'Stream' """Stream image file data from the source URI.""" UPLOAD = 'Upload' """Upload the entire image file from the source URI to the service.""" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/manager/manager.py0000664000175000017500000002454600000000000021515 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/Manager.v1_4_0.json import logging from sushy import exceptions from sushy.resources import base from sushy.resources import common from sushy.resources import constants as res_cons from sushy.resources.manager import constants as mgr_cons from sushy.resources.manager import virtual_media from sushy.resources.system import ethernet_interface from sushy import utils LOG = logging.getLogger(__name__) class ActionsField(base.CompositeField): reset = common.ResetActionField('#Manager.Reset') class RemoteAccessField(base.CompositeField): service_enabled = base.Field('ServiceEnabled') max_concurrent_sessions = base.Field('MaxConcurrentSessions') connect_types_supported = base.Field('ConnectTypesSupported', adapter=list) class Manager(base.ResourceBase): auto_dst_enabled = base.Field('AutoDSTEnabled') """Indicates whether the manager is configured for automatic DST adjustment""" firmware_version = base.Field('FirmwareVersion') """The manager firmware version""" graphical_console = RemoteAccessField('GraphicalConsole') """A dictionary containing the remote access support service via graphical console (e.g. KVMIP) and max concurrent sessions """ serial_console = RemoteAccessField('SerialConsole') """A dictionary containing the remote access support service via serial console (e.g. Telnet, SSH, IPMI) and max concurrent sessions """ command_shell = RemoteAccessField('CommandShell') """A dictionary containing the remote access support service via command shell (e.g. Telnet, SSH) and max concurrent sessions """ description = base.Field('Description') """The manager description""" identity = base.Field('Id', required=True) """The manager identity string""" name = base.Field('Name') """The manager name""" model = base.Field('Model') """The manager model""" manager_type = base.MappedField('ManagerType', mgr_cons.ManagerType) """The manager type""" uuid = base.Field('UUID') """The manager UUID""" _actions = ActionsField('Actions') def __init__(self, connector, identity, redfish_version=None, registries=None, root=None): """A class representing a Manager :param connector: A Connector instance :param identity: The identity of the Manager resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of the given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, identity, redfish_version=redfish_version, registries=registries, root=root) def get_supported_graphical_console_types(self): """Get the supported values for Graphical Console connection types. :returns: A set of supported values. """ if (not self.graphical_console or not self.graphical_console.connect_types_supported): LOG.warning('Could not figure out the supported values for ' 'remote access via graphical console for Manager %s', self.identity) return set(mgr_cons.GraphicalConnectType) return {v for v in mgr_cons.GraphicalConnectType if v.value in self.graphical_console.connect_types_supported} def get_supported_serial_console_types(self): """Get the supported values for Serial Console connection types. :returns: A set of supported values. """ if (not self.serial_console or not self.serial_console.connect_types_supported): LOG.warning('Could not figure out the supported values for ' 'remote access via serial console for Manager %s', self.identity) return set(mgr_cons.SerialConnectType) return {v for v in mgr_cons.SerialConnectType if v.value in self.serial_console.connect_types_supported} def get_supported_command_shell_types(self): """Get the supported values for Command Shell connection types. :returns: A set of supported values. """ if (not self.command_shell or not self.command_shell.connect_types_supported): LOG.warning('Could not figure out the supported values for ' 'remote access via command shell for Manager %s', self.identity) return set(mgr_cons.CommandConnectType) return {v for v in mgr_cons.CommandConnectType if v.value in self.command_shell.connect_types_supported} def _get_reset_action_element(self): reset_action = self._actions.reset if not reset_action: raise exceptions.MissingActionError(action='#Manager.Reset', resource=self._path) return reset_action def get_allowed_reset_manager_values(self): """Get the allowed values for resetting the manager. :returns: A set of allowed values. :raises: MissingAttributeError, if Actions/#Manager.Reset attribute not present. """ reset_action = self._get_reset_action_element() if not reset_action.allowed_values: LOG.warning('Could not figure out the allowed values for the ' 'reset manager action for Manager %s', self.identity) return set(res_cons.ResetType) return {v for v in res_cons.ResetType if v.value in reset_action.allowed_values} def reset_manager(self, value): """Reset the manager. :param value: The target value. :raises: InvalidParameterValueError, if the target value is not allowed. """ valid_resets = self.get_allowed_reset_manager_values() if value not in valid_resets: raise exceptions.InvalidParameterValueError( parameter='value', value=value, valid_values=valid_resets) value = res_cons.ResetType(value).value target_uri = self._get_reset_action_element().target_uri LOG.debug('Resetting the Manager %s ...', self.identity) self._conn.post(target_uri, data={'ResetType': value}) LOG.info('The Manager %s is being reset', self.identity) @property @utils.cache_it def virtual_media(self): return virtual_media.VirtualMediaCollection( self._conn, utils.get_sub_resource_path_by(self, 'VirtualMedia'), redfish_version=self.redfish_version, registries=self.registries, root=self.root) @property @utils.cache_it def systems(self): """A list of systems managed by this manager. Returns a list of `System` objects representing systems being managed by this manager. :raises: MissingAttributeError if '@odata.id' field is missing. :returns: A list of `System` instances """ paths = utils.get_sub_resource_path_by( self, ["Links", "ManagerForServers"], is_collection=True) from sushy.resources.system import system return [system.System(self._conn, path, redfish_version=self.redfish_version, registries=self.registries, root=self.root) for path in paths] @property @utils.cache_it def chassis(self): """A list of chassis managed by this manager. Returns a list of `Chassis` objects representing the chassis or cabinets managed by this manager. :raises: MissingAttributeError if '@odata.id' field is missing. :returns: A list of `Chassis` instances """ paths = utils.get_sub_resource_path_by( self, ["Links", "ManagerForChassis"], is_collection=True) from sushy.resources.chassis import chassis return [chassis.Chassis(self._conn, path, redfish_version=self.redfish_version, registries=self.registries, root=self.root) for path in paths] @property @utils.cache_it def ethernet_interfaces(self): """Property to reference `EthernetInterfaceCollection` instance It is set once when the first time it is queried. On refresh, this property is marked as stale (greedy-refresh not done). Here the actual refresh of the sub-resource happens, if stale. """ return ethernet_interface.EthernetInterfaceCollection( self._conn, utils.get_sub_resource_path_by(self, "EthernetInterfaces"), redfish_version=self.redfish_version, registries=self.registries, root=self.root) class ManagerCollection(base.ResourceCollectionBase): @property def _resource_type(self): return Manager def __init__(self, connector, path, redfish_version=None, registries=None, root=None): """A class representing a ManagerCollection :param connector: A Connector instance :param path: The canonical path to the Manager collection resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of the given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, path, redfish_version=redfish_version, registries=registries, root=root) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/manager/virtual_media.py0000664000175000017500000002756300000000000022732 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/VirtualMedia.v1_2_0.json from http import client as http_client from sushy import exceptions from sushy.resources import base from sushy.resources.certificateservice import certificate from sushy.resources import common from sushy.resources.manager import constants as mgr_cons from sushy import utils class ActionsField(base.CompositeField): insert_media = common.ActionField("#VirtualMedia.InsertMedia") eject_media = common.ActionField("#VirtualMedia.EjectMedia") class VirtualMedia(base.ResourceBase): identity = base.Field('Id', required=True) """Virtual Media resource identity string""" name = base.Field('Name', required=True) """The name of resource""" connected_via = base.MappedField('ConnectedVia', mgr_cons.ConnectedVia) """Current virtual media connection methods Applet: Connected to a client application NotConnected: No current connection Oem: Connected via an OEM-defined method URI: Connected to a URI location """ image = base.Field('Image') """A URI providing the location of the selected image""" image_name = base.Field('ImageName') """The image name""" inserted = base.Field('Inserted') """Indicates if virtual media is inserted in the virtual device""" media_types = base.MappedListField( 'MediaTypes', mgr_cons.VirtualMediaType, default=[]) """List of supported media types as virtual media""" status = common.StatusField('Status') """The virtual media status""" transfer_method = base.MappedField('TransferMethod', mgr_cons.TransferMethod) """The transfer method to use with the Image""" user_name = base.Field('UserName') """The user name to access the Image parameter-specified URI""" verify_certificate = base.Field('VerifyCertificate', adapter=bool) """Whether to verify the certificate of the server for the Image""" write_protected = base.Field('WriteProtected') """Indicates the media is write protected""" _actions = ActionsField('Actions') """Insert/eject action for virtual media""" _certificates_path = base.Field(['Certificates', '@odata.id']) def _get_insert_media_uri(self): insert_media = self._actions.insert_media if self._actions else None use_patch = False if not insert_media: insert_uri = self.path use_patch = self._allow_patch() if not use_patch: raise exceptions.MissingActionError( action='#VirtualMedia.InsertMedia', resource=self._path) else: insert_uri = insert_media.target_uri return insert_uri, use_patch def _get_eject_media_uri(self): eject_media = self._actions.eject_media if self._actions else None use_patch = False if not eject_media: eject_uri = self.path use_patch = self._allow_patch() if not use_patch: raise exceptions.MissingActionError( action='#VirtualMedia.EjectMedia', resource=self._path) else: eject_uri = eject_media.target_uri return eject_uri, use_patch def is_transfer_protocol_required(self, error=None): """Check the response code and body and in case of failure Try to determine if it happened due to missing TransferProtocolType. """ if (error.code.endswith('GeneralError') and 'TransferProtocolType' in error.detail): return True return ( (error.code.endswith(".ActionParameterMissing") or error.code.endswith(".PropertyMissing")) and (("#/TransferProtocolType" in error.related_properties) or ("/TransferProtocolType" in error.related_properties)) ) def is_transfer_method_required(self, error=None): """Check the response code and body and in case of failure Try to determine if it happened due to missing TransferMethod """ if (error.code.endswith('GeneralError') and 'TransferMethod' in error.detail): return True return False def is_credentials_required(self, error=None): """Check the response code and body and in case of failure Try to determine if it happened due to missing Credentials """ if (error.code.endswith('GeneralError') and 'UserName' in error.detail): return True return False def insert_media(self, image, inserted=True, write_protected=True, username=None, password=None, transfer_method=None): """Attach remote media to virtual media :param image: a URI providing the location of the selected image :param inserted: specify if the image is to be treated as inserted upon completion of the action. :param write_protected: indicates the media is write protected :param username: User name for the image URI. :param password: Password for the image URI. :param transfer_method: Transfer method (stream or upload) to use for the image. """ target_uri, use_patch = self._get_insert_media_uri() # NOTE(janders) Inserted and WriteProtected attributes are optional # as per Redfish schema 2021.1. However - some BMCs (e.g. Lenovo SD530 # which is using PATCH method as opposed to InsertMedia action) will # not attach vMedia if Inserted is not specified. # On the other hand, machines such as SuperMicro X11 will return # an error if Inserted or WriteProtected are specified. In order to # make both work, we remove Inserted and WriteProtected from payload # for BMCs which don't use PATCH if their values are set to defaults # as per the spec (True, True). We continue to set Inserted and # WriteProtected in payload if PATCH method is used. payload = {'Image': image} if username is not None: payload['UserName'] = username if password is not None: payload['Password'] = password if transfer_method is not None: try: payload['TransferMethod'] = \ mgr_cons.TransferMethod(transfer_method).value except ValueError: raise exceptions.InvalidParameterValueError( parameter='transfer_method', value=transfer_method, valid_values=', '.join(map(str, mgr_cons.TransferMethod))) if use_patch: payload['Inserted'] = inserted payload['WriteProtected'] = write_protected headers = None etag = self._get_etag() if etag is not None: headers = {"If-Match": etag} self._conn.patch(target_uri, data=payload, headers=headers) else: # NOTE(janders) only include Inserted and WriteProtected # in request payload if values other than defaults (True,True) # are set (fix for SuperMicro X11/X12). if not inserted: payload['Inserted'] = False if not write_protected: payload['WriteProtected'] = False # Called a Max of 4 times, 1 plain + 1 for each of the 3 errors for _ in range(4): try: self._conn.post(target_uri, data=payload) break except exceptions.HTTPError as error: # NOTE(janders) attempting to detect whether attachment # failure is due to absence of TransferProtocolType param # and if so adding it if payload.get('TransferProtocolType') is None and \ self.is_transfer_protocol_required(error): if payload['Image'].startswith('https://'): payload['TransferProtocolType'] = "HTTPS" elif payload['Image'].startswith('http://'): payload['TransferProtocolType'] = "HTTP" continue # NOTE (iurygregory) we try to handle the case where a # a TransferMethod is also required in the payload. if payload.get('TransferMethod') != "Stream" and \ self.is_transfer_method_required(error): payload['TransferMethod'] = "Stream" continue # NOTE (derekh) we try to handle the case where # credentials are required in the payload. # Seen on nvidia dgx (Bug 2071945) if payload.get('UserName') is None and \ self.is_credentials_required(error): payload['UserName'] = "none" payload['Password'] = "none" # noqa:S105 continue raise self.invalidate() def eject_media(self): """Detach remote media from virtual media After ejecting media inserted will be False and image_name will be empty. """ try: target_uri, use_patch = self._get_eject_media_uri() if use_patch: payload = { "Image": None, "Inserted": False } headers = None etag = self._get_etag() if etag is not None: headers = {"If-Match": etag} self._conn.patch(target_uri, data=payload, headers=headers) else: self._conn.post(target_uri) except exceptions.HTTPError as response: # Some vendors like HPE iLO has this kind of implementation. # It needs to pass an empty dict. if response.status_code in ( http_client.UNSUPPORTED_MEDIA_TYPE, http_client.BAD_REQUEST): self._conn.post(target_uri, data={}) self.invalidate() def set_verify_certificate(self, verify_certificate): """Enable or disable certificate validation.""" if not isinstance(verify_certificate, bool): raise exceptions.InvalidParameterValueError( parameter='verify_certificate', value=verify_certificate, valid_values='boolean (True, False)') etag = self._get_etag() self._conn.patch(self.path, data={'VerifyCertificate': verify_certificate}, etag=etag) self.invalidate() @property @utils.cache_it def certificates(self): """Get the collection of certificates for this device.""" if not self._certificates_path: raise exceptions.MissingAttributeError( attribute='Certificates/@odata.id', resource=self._path) return certificate.CertificateCollection( self._conn, self._certificates_path, redfish_version=self.redfish_version, registries=self.registries, root=self.root) class VirtualMediaCollection(base.ResourceCollectionBase): """A collection of virtual media attached to a Manager""" @property def _resource_type(self): return VirtualMedia ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6515148 sushy-5.5.0/sushy/resources/oem/0000775000175000017500000000000000000000000016664 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/oem/__init__.py0000664000175000017500000000123200000000000020773 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from sushy.resources.oem.common import get_resource_extension_by_vendor __all__ = ('get_resource_extension_by_vendor',) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/oem/base.py0000664000175000017500000000532400000000000020154 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging from sushy.resources import base LOG = logging.getLogger(__name__) class OEMResourceBase(base.ResourceBase): def __init__(self, connector, path='', redfish_version=None, registries=None, reader=None, root=None): """Class representing an OEM vendor extension :param connector: A Connector instance :param path: sub-URI path to the resource. :param redfish_version: The version of Redfish. Used to construct the object according to schema of the given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ self._parent_resource = None self._vendor_id = None super().__init__( connector, path, redfish_version=redfish_version, registries=registries, reader=reader, root=root) def set_parent_resource(self, parent_resource, vendor_id): self._parent_resource = parent_resource self._vendor_id = vendor_id # NOTE(etingof): this is required to pull OEM subtree self.invalidate(force_refresh=True) return self def _parse_attributes(self, json_doc): """Parse the attributes of a resource. Parsed JSON fields are set to `self` as declared in the class. :param json_doc: parsed JSON document in form of Python types """ # Too early to parse, need to call set_parent_resource first that # assigns vendor_id and re-parses attributes if self._vendor_id is None: return oem_json = json_doc.get( 'Oem', {}).get(self._vendor_id, {}) # NOTE(etingof): temporary copy Actions into Oem subtree for parsing # all fields at once oem_json = oem_json.copy() oem_actions_json = { 'Actions': json_doc.get( 'Actions', {}).get('Oem', {}) } oem_json.update(oem_actions_json) super()._parse_attributes(oem_json) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/oem/common.py0000664000175000017500000001034500000000000020531 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging import stevedore from sushy import exceptions from sushy import utils LOG = logging.getLogger(__name__) _global_extn_mgrs_by_resource = {} def _raise(m, ep, e): raise exceptions.ExtensionError( error=f'Failed to load entry point target: {e}') def _create_extension_manager(namespace): """Create the resource specific ExtensionManager instance. Use stevedore to find all vendor extensions of resource from their namespace and return the ExtensionManager instance. :param namespace: The namespace for the entry points. It maps to a specific Sushy resource type. :returns: the ExtensionManager instance :raises ExtensionError: on resource OEM extension load error. """ # namespace format is: # ``sushy.resources..oems`` resource_name = namespace.split('.')[-2] extension_manager = ( stevedore.ExtensionManager(namespace=namespace, propagate_map_exceptions=True, on_load_failure_callback=_raise)) LOG.debug('Resource OEM extensions for "%(resource)s" under namespace ' '"%(namespace)s":', {'resource': resource_name, 'namespace': namespace}) for extension in extension_manager: LOG.debug('Found vendor: %(name)s target: %(target)s', {'name': extension.name, 'target': extension.entry_point_target}) if not extension_manager.names(): m = (f'No extensions found for "{resource_name}" under namespace ' f'"{namespace}"') LOG.error(m) raise exceptions.ExtensionError(error=m) return extension_manager @utils.synchronized def _get_extension_manager_of_resource(resource_name): """Get the resource specific ExtensionManager instance. :param resource_name: The name of the resource e.g. 'system' / 'ethernet_interface' / 'update_service' :returns: the ExtensionManager instance :raises ExtensionError: on resource OEM extension load error. """ global _global_extn_mgrs_by_resource if resource_name not in _global_extn_mgrs_by_resource: resource_namespace = 'sushy.resources.' + resource_name + '.oems' _global_extn_mgrs_by_resource[resource_name] = ( _create_extension_manager(resource_namespace) ) return _global_extn_mgrs_by_resource[resource_name] def get_resource_extension_by_vendor( resource_name, vendor, resource): """Helper method to get Resource specific OEM extension object for vendor :param resource_name: The underscore joined name of the resource e.g. 'system' / 'ethernet_interface' / 'update_service' :param vendor: This is the OEM vendor string which is the vendor-specific extensibility identifier. Examples are: 'Contoso', 'Hpe'. As a matter of fact the lowercase of this string will be the plugin entry point name. :param resource: The Sushy resource instance :returns: The object returned by ``plugin(*args, **kwds)`` of extension. :raises OEMExtensionNotFoundError: if no valid resource OEM extension found. """ if resource_name in _global_extn_mgrs_by_resource: resource_extn_mgr = _global_extn_mgrs_by_resource[resource_name] else: resource_extn_mgr = _get_extension_manager_of_resource(resource_name) try: resource_vendor_extn = resource_extn_mgr[vendor.lower()] except KeyError: raise exceptions.OEMExtensionNotFoundError( resource=resource_name, name=vendor.lower()) oem_resource = resource_vendor_extn.plugin() return resource.clone_resource( oem_resource).set_parent_resource(resource, vendor) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/oem/fake.py0000664000175000017500000000253600000000000020152 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging from sushy.resources import base from sushy.resources import common from sushy.resources.oem import base as oem_base LOG = logging.getLogger(__name__) class ProductionLocationField(base.CompositeField): facility_name = base.Field('FacilityName') country = base.Field('Country') class ContosoActionsField(base.CompositeField): reset = common.ResetActionField('#Contoso.Reset') class FakeOEMSystemExtension(oem_base.OEMResourceBase): data_type = base.Field('@odata.type') name = base.Field('Name', required=True) production_location = ProductionLocationField('ProductionLocation') _actions = ContosoActionsField('Actions') def get_reset_system_path(self): return self._actions.reset.target_uri def get_extension(*args, **kwargs): return FakeOEMSystemExtension ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6515148 sushy-5.5.0/sushy/resources/registry/0000775000175000017500000000000000000000000017754 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/registry/__init__.py0000664000175000017500000000000000000000000022053 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/registry/attribute_registry.py0000664000175000017500000000654600000000000024274 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # The Redfish standard schema that defines the AttributeRegistry is at: # https://redfish.dmtf.org/schemas/v1/AttributeRegistry.v1_3_5.json import logging from sushy.resources import base LOG = logging.getLogger(__name__) class AttributeListField(base.ListField): name = base.Field('AttributeName', required=True) """The unique name for the attribute""" default_value = base.Field('DefaultValue') """The default value for the attribute""" attribute_type = base.Field('Type') """The attribute type""" unique = base.Field('IsSystemUniqueProperty', adapter=bool) """Indicates whether this attribute is unique for this system""" display_name = base.Field('DisplayName') """User-readable display string for attribute in the defined language""" immutable = base.Field('Immutable', adapter=bool) """An indication of whether this attribute is immutable""" read_only = base.Field('ReadOnly', adapter=bool) """An indication of whether this attribute is read-only""" reset_required = base.Field('ResetRequired', adapter=bool) """Whether a System reset is required to change this attribute""" lower_bound = base.Field('LowerBound') """The lower limit for an integer attribute""" max_length = base.Field('MaxLength') """The maximum character length of the string attribute""" min_length = base.Field('MinLength') """The minimum character length of the string attribute""" upper_bound = base.Field('UpperBound') """The upper limit for an integer attribute""" allowable_values = base.Field('Value') """An array of the possible values for enumerated attribute values""" class AttributeRegistryEntryField(base.CompositeField): attributes = AttributeListField('Attributes') """List of attributes in this registry""" # Vendors may have additional items such as Dependencies, Menus, etc. # Only get the attributes. class AttributeRegistry(base.ResourceBase): identity = base.Field('Id', required=True) """The Attribute registry identity string""" name = base.Field('Name', required=True) """The name of the attribute registry""" description = base.Field('Description') """Human-readable description of the registry""" language = base.Field('Language', required=True) """RFC 5646 compliant language code for the registry""" owning_entity = base.Field('OwningEntity', required=True) """Organization or company that publishes this registry""" registry_version = base.Field('RegistryVersion', required=True) """The version of this registry""" supported_systems = base.Field('SupportedSystems') """The system that this registry supports""" registry_entries = AttributeRegistryEntryField('RegistryEntries') """Field containing Attributes, Dependencies, Menus etc.""" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/registry/constants.py0000664000175000017500000000151100000000000022340 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # Values come from the Redfish schema: # https://redfish.dmtf.org/schemas/v1/MessageRegistry.v1_4_2.json import enum class MessageParamType(enum.Enum): """Message Registry message parameter type related constants.""" STRING = "string" NUMBER = "number" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/registry/message_registry.py0000664000175000017500000001245600000000000023712 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/v1/MessageRegistry.v1_1_1.json import logging from sushy.resources import base from sushy.resources import constants as res_cons from sushy.resources.registry import constants as reg_cons LOG = logging.getLogger(__name__) class MessageDictionaryField(base.DictionaryField): description = base.Field('Description', required=True, default='') """Indicates how and when the message is returned by the Redfish service""" message = base.Field('Message', required=True) """Template text of the message Template can include placeholders for message arguments in form % where denotes a position passed from MessageArgs. """ number_of_args = base.Field('NumberOfArgs', required=True) """Number of arguments to be expected to be passed in as MessageArgs for this message """ param_types = base.Field('ParamTypes', adapter=lambda x: [reg_cons.MessageParamType(v.lower()) for v in x]) """Mapped MessageArg types, in order, for the message""" resolution = base.Field('Resolution', required=True) """Suggestions on how to resolve the situation that caused the error""" severity = base.MappedField('Severity', res_cons.Severity, required=True, default=res_cons.Severity.WARNING) """Mapped severity of the message""" class MessageRegistry(base.ResourceBase): _log_resource_body = False identity = base.Field('Id', required=True) """The Message registry identity string""" name = base.Field('Name', required=True) """The name of the message registry""" description = base.Field('Description') """Human-readable description of the message registry""" language = base.Field('Language', required=True) """RFC 5646 compliant language code for the registry""" owning_entity = base.Field('OwningEntity', required=True) """Organization or company that publishes this registry""" registry_prefix = base.Field('RegistryPrefix', required=True) """Prefix used in messageIDs which uniquely identifies all of the messages in this registry as belonging to this registry """ registry_version = base.Field('RegistryVersion', required=True) """Message registry version which is used in the middle portion of a messageID """ messages = MessageDictionaryField('Messages') """List of messages in this registry""" def parse_message(message_registries, message_field): """Parse the messages in registries and substitute any params Check only registries that support messages. :param message_registries: dict of Message Registries :param message_field: settings.MessageListField to parse :returns: parsed settings.MessageListField with missing attributes filled """ reg_msg = None if '.' in message_field.message_id: registry, msg_key = message_field.message_id.rsplit('.', 1) if (registry in message_registries and hasattr(message_registries[registry], "messages") and msg_key in message_registries[registry].messages): reg_msg = message_registries[registry].messages[msg_key] else: # Some firmware only reports the MessageKey and no RegistryName. # Fall back to the MessageRegistryFile with Id of Messages next, and # BaseMessages as a last resort registry = 'unknown' msg_key = message_field.message_id mrf_ids = ['Messages', 'BaseMessages'] for mrf_id in mrf_ids: if (mrf_id in message_registries and msg_key in message_registries[mrf_id].messages): reg_msg = message_registries[mrf_id].messages[msg_key] break if not reg_msg: LOG.warning( 'Unable to find message for registry %(registry)s, ' 'message ID %(msg_key)s', { 'registry': registry, 'msg_key': msg_key}) if message_field.message is None: message_field.message = 'unknown' return message_field msg = reg_msg.message for i in range(1, reg_msg.number_of_args + 1): if i <= len(message_field.message_args): msg = msg.replace('%%%i' % i, str(message_field.message_args[i - 1])) else: msg = msg.replace('%%%i' % i, 'unknown') message_field.message = msg if not message_field.severity: message_field.severity = reg_msg.severity if not message_field.resolution: message_field.resolution = reg_msg.resolution return message_field ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/registry/message_registry_file.py0000664000175000017500000001646500000000000024715 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/v1/MessageRegistryFileCollection.json # https://redfish.dmtf.org/schemas/v1/MessageRegistryFile.v1_1_0.json import logging from sushy.resources import base from sushy.resources.registry import attribute_registry from sushy.resources.registry import message_registry LOG = logging.getLogger(__name__) class LocationListField(base.ListField): """Location for each registry file of languages supported There are 3 options where the file can be hosted: * locally as a single file, * locally as a part of archive (zip or other), * publicly on the Internet. """ language = base.Field('Language') """File's RFC5646 language code or the string 'default'""" uri = base.Field('Uri') """Location URI for co-located registry file with the Redfish service""" archive_uri = base.Field('ArchiveUri') """Location URI for archive file""" archive_file = base.Field('ArchiveFile') """File name for registry if using archive_uri""" publication_uri = base.Field('PublicationUri') """Location URI of publicly available schema""" class RegistryType(base.ResourceBase): _odata_type = base.Field('@odata.type', required=True) class MessageRegistryFile(base.ResourceBase): identity = base.Field('Id', required=True) """Identity of Message Registry file resource""" description = base.Field('Description') """Description of Message Registry file resource""" name = base.Field('Name', required=True) """Name of Message Registry file resource""" languages = base.Field('Languages', required=True) """List of RFC 5646 language codes supported by this resource""" registry = base.Field('Registry', required=True, default='UNKNOWN.0.0') """Prefix for MessageId used for messages from this resource This attribute is in form Registry_name.Major_version.Minor_version """ location = LocationListField('Location', required=True) """List of locations of Registry files for each supported language""" def get_message_registry(self, language, public_connector): """Get a Message Registry from the location :param language: RFC 5646 language code for registry files :param public_connector: connector to use when downloading registry from the Internet :returns: a MessageRegistry or None if not found """ return self._get_registry(language, public_connector, 'MessageRegistry', message_registry.MessageRegistry) def get_attribute_registry(self, language, public_connector): """Get an Attribute Registry from the location :param language: RFC 5646 language code for registry files :param public_connector: connector to use when downloading registry from the Internet :returns: an AttributeRegistry or None if not found """ return self._get_registry(language, public_connector, 'AttributeRegistry', attribute_registry.AttributeRegistry) def _get_registry(self, language, public_connector, requested_type, registry_class): """Load registry file depending on the registry type Will try to find requested_type based on `odata.type` property, location, and provided language. If desired language is not found, will pick a registry that has 'default' language. :param language: RFC 5646 language code for registry files :param public_connector: connector to use when downloading registry from the Internet :param requested_type: string identifying registry :param registry_class: registry class :returns: registry or None if not found """ # NOTE (etingof): as per RFC5646, languages are case-insensitive language = language.lower() # NOTE(iurygregory): some registries may have "en-US" as their # language, in this case we can check if the registry language # starts with the requested language. locations = [ loc for loc in self.location if loc.language.lower().split('-', 1)[0] == language or loc.language == language ] locations += [ loc for loc in self.location if loc.language.lower() == 'default'] for location in locations: if location.uri: args = self._conn, kwargs = { 'path': location.uri, 'reader': None, 'redfish_version': self.redfish_version } elif location.archive_uri: args = self._conn, kwargs = { 'path': location.archive_uri, 'reader': base.JsonArchiveReader(location.archive_file), 'redfish_version': self.redfish_version } elif location.publication_uri: args = public_connector, kwargs = { 'path': location.publication_uri, 'reader': base.JsonPublicFileReader(), 'redfish_version': self.redfish_version } else: LOG.warning('Incomplete location for language %(language)s', {'language': language}) continue try: registry_type = RegistryType(*args, **kwargs) except Exception as exc: LOG.warning( 'Cannot load registry type from location ' '%(location)s: %(error)s', { 'location': kwargs['path'], 'error': exc}) continue if registry_type._odata_type.endswith(requested_type): try: return registry_class(*args, **kwargs) except Exception as exc: LOG.warning( 'Cannot load registry %(type)s from location ' '%(location)s: %(error)s', { 'type': requested_type, 'location': kwargs['path'], 'error': exc}) continue LOG.debug('Ignoring unsupported flavor of registry %(registry)s', {'registry': registry_type._odata_type}) return LOG.warning('No registry found for %(language)s or default', {'language': language}) class MessageRegistryFileCollection(base.ResourceCollectionBase): """Collection of Message Registry Files""" @property def _resource_type(self): return MessageRegistryFile ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6515148 sushy-5.5.0/sushy/resources/sessionservice/0000775000175000017500000000000000000000000021150 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/sessionservice/__init__.py0000664000175000017500000000000000000000000023247 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/sessionservice/session.py0000664000175000017500000000616100000000000023211 0ustar00zuulzuul00000000000000# Copyright 2017 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. # This is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/Session.v1_1_0.json import logging from sushy.resources import base LOG = logging.getLogger(__name__) class Session(base.ResourceBase): description = base.Field('Description') """The session service description""" identity = base.Field('Id', required=True) """The session service identify string""" name = base.Field('Name', required=True) """The session service name""" username = base.Field('UserName') """The UserName for the account for this session.""" def __init__(self, connector, identity, redfish_version=None, registries=None, root=None): """A class representing a Session :param connector: A Connector instance :param identity: The identity of the Session resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, identity, redfish_version=redfish_version, registries=registries, root=root) def delete(self): """Method for deleting a Session. :raises: ServerSideError """ self._conn.delete(self.path) class SessionCollection(base.ResourceCollectionBase): name = base.Field('Name') """The session collection name""" description = base.Field('Description') """The session collection description""" @property def _resource_type(self): return Session def __init__(self, connector, identity, redfish_version=None, registries=None, root=None): """A class representing a SessionCollection :param connector: A Connector instance :param identity: The identity of the Session resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, identity, redfish_version=redfish_version, registries=registries, root=root) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/sessionservice/sessionservice.py0000664000175000017500000001150400000000000024567 0ustar00zuulzuul00000000000000# Copyright 2017 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. # This is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/SessionService.v1_1_3.json import logging from sushy import exceptions from sushy.resources import base from sushy.resources.sessionservice import session from sushy import utils LOG = logging.getLogger(__name__) class SessionService(base.ResourceBase): description = base.Field('Description') """The session service description""" identity = base.Field('Id', required=True) """The session service identify string""" name = base.Field('Name', required=True) """The session service name""" service_enabled = base.Field('ServiceEnabled') """Tells us if session service is enabled""" session_timeout = base.Field('SessionTimeout') """The session service timeout""" def __init__(self, connector, identity, redfish_version=None, registries=None, root=None): """A class representing a SessionService Warning: This class should only be invoked with a connector which has already established authentication. :param connector: A Connector instance :param identity: The identity of the SessionService resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ # Populating the base resource so session interactions can # occur based on the contents of it. super().__init__( connector, identity, redfish_version=redfish_version, registries=registries, root=root) def _get_sessions_collection_path(self): """Helper function to find the SessionCollections path""" sessions_col = self.json.get('Sessions') if not sessions_col: raise exceptions.MissingAttributeError( attribute='Sessions', resource=self._path) return sessions_col.get('@odata.id') @property @utils.cache_it def sessions(self): """Property to provide reference to the `SessionCollection` instance It is calculated once when the first time it is queried. On refresh, this property gets reset. """ return session.SessionCollection( self._conn, self._get_sessions_collection_path(), redfish_version=self.redfish_version, registries=self.registries, root=self.root) def close_session(self, session_uri): """This function is for closing a session based on its id. :raises: ServerSideError """ self._conn.delete(session_uri) def create_session(self, username, password, target_uri=None): """This function will try to create a session. Create a session and return the associated key and URI. :param username: the username of the user requesting a new session :param password: the password associated to the user requesting a new session :param target_uri: the "Sessions" uri, usually in the form: '/redfish/v1/SessionService/Sessions' :returns: A session key and uri in the form of a tuple :raises: MissingXAuthToken :raises: ConnectionError :raises: AccessError :raises: HTTPError """ if not target_uri: try: target_uri = self._get_sessions_collection_path() except Exception: # Defaulting to /Sessions target_uri = self.path + '/Sessions' data = {'UserName': username, 'Password': password} LOG.debug("Requesting new session from %s.", target_uri) rsp = self._conn.post(target_uri, data=data) session_key = rsp.headers.get('X-Auth-Token') if session_key is None: raise exceptions.MissingXAuthToken( method='POST', url=target_uri, response=rsp) session_uri = rsp.headers.get('Location') if session_uri is None: LOG.warning("Received X-Auth-Token but NO session uri.") return session_key, session_uri ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/settings.py0000664000175000017500000001457500000000000020332 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/Settings.v1_2_0.json import logging from dateutil import parser from sushy.resources import base from sushy.resources import common from sushy.resources import constants as res_cons from sushy.resources.registry import message_registry # Settings update statuses UPDATE_UNKNOWN = 0 """Update status unknown""" UPDATE_SUCCESS = 1 """Update was successful""" UPDATE_FAILURE = 2 """Update encountered errors""" UPDATE_PENDING = 3 """Update waiting for being applied""" NO_UPDATES = 4 """No updates made""" class SettingsUpdate: """Contains Settings update status and details of the update""" def __init__(self, status, messages): self._status = status self._messages = messages @property def status(self): """The status of the update""" return self._status @property def messages(self): """List of :class:`.MessageListField` with messages from the update""" return self._messages LOG = logging.getLogger(__name__) class MaintenanceWindowField(base.CompositeField): maintenance_window_duration_in_seconds = base.Field( 'MaintenanceWindowDurationInSeconds', required=True) """The expiry time of maintenance window in seconds""" maintenance_window_start_time = base.Field( 'MaintenanceWindowStartTime', required=True, adapter=parser.parse) """The start time of a maintenance window""" class SettingsApplyTimeField(base.CompositeField): def __init__(self): super().__init__( path="@Redfish.SettingsApplyTime") apply_time = base.Field('ApplyTime', adapter=str) """When the future configuration should be applied""" apply_time_allowable_values = base.Field( 'ApplyTime@Redfish.AllowableValues', adapter=list) """The list of allowable ApplyTime values""" maintenance_window_start_time = base.Field('MaintenanceWindowStartTime', adapter=parser.parse) """The start time of a maintenance window""" maintenance_window_duration_in_seconds = base.Field( 'MaintenanceWindowDurationInSeconds', adapter=int) """The expiry time of maintenance window in seconds""" class SettingsField(base.CompositeField): """The settings of a resource Represents the future state and configuration of the resource. The field is added to resources that support future state and configuration. This field includes several properties to help clients monitor when the resource is consumed by the service and determine the results of applying the values, which may or may not have been successful. """ def __init__(self): super().__init__(path="@Redfish.Settings") time = base.Field('Time') """Indicates the time the settings were applied to the server""" _etag = base.Field('ETag') """The ETag of the resource to which the settings were applied, after the application """ _settings_object_idref = common.IdRefField("SettingsObject") """Reference to the resource the client may PUT/PATCH in order to change this resource """ _supported_apply_times = base.MappedListField( 'SupportedApplyTimes', res_cons.ApplyTime) """List of supported apply times""" @property def maintenance_window(self): """MaintenanceWindow field Indicates if a given resource has a maintenance window assignment for applying settings or operations """ LOG.warning('The @Redfish.MaintenanceWindow annotation does not ' 'appear within @Redfish.Settings. Instead use the ' 'maintenance_window property in the target resource ' '(e.g. System resource)') return None messages = base.MessageListField("Messages") """Represents the results of the last time the values of the Settings resource were applied to the server""" @property def operation_apply_time_support(self): """OperationApplyTimeSupport field Indicates if a client is allowed to request for a specific apply time of a create, delete, or action operation of a given resource """ LOG.warning('Redfish ApplyTime annotations do not appear within ' '@Redfish.Settings. Instead use the apply_time_settings ' 'property in the target resource (e.g. Bios resource)') return None def commit(self, connector, value): """Commits new settings values The new values will be applied when the system or a service restarts. :param connector: A Connector instance :param value: Value representing JSON whose structure is specific to each resource and the caller must format it correctly :returns: Response object """ return connector.patch(self.resource_uri, data=value, etag=self._etag) @property def resource_uri(self): return self._settings_object_idref.resource_uri def get_status(self, registries): """Determines the status of last update based Uses message id-s and severity to determine the status. :param registries: registries to use to parse message :returns: :class:`.SettingsUpdate` object containing status and any messages """ if not self.time: return SettingsUpdate(NO_UPDATES, None) parsed_msgs = [] for m in self.messages: parsed_msgs.append( message_registry.parse_message(registries, m)) any_errors = any(m for m in parsed_msgs if m.severity != res_cons.Severity.OK) if any_errors: status = UPDATE_FAILURE else: status = UPDATE_SUCCESS return SettingsUpdate(status, parsed_msgs) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6555147 sushy-5.5.0/sushy/resources/system/0000775000175000017500000000000000000000000017430 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/system/__init__.py0000664000175000017500000000000000000000000021527 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/system/bios.py0000664000175000017500000002253100000000000020741 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/Bios.v1_0_3.json from http import client as http_client import logging from sushy import exceptions from sushy.resources import base from sushy.resources import common from sushy.resources import settings from sushy import utils LOG = logging.getLogger(__name__) class ActionsField(base.CompositeField): change_password = common.ActionField('#Bios.ChangePassword') reset_bios = common.ActionField('#Bios.ResetBios') class Bios(base.ResourceBase): def __init__(self, connector, path, redfish_version=None, registries=None, root=None): """A class representing a Bios :param connector: A Connector instance :param path: Sub-URI path to the Bios resource :param registries: Dict of message registries to be used when parsing messages of attribute update status :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, path, redfish_version=redfish_version, registries=registries, root=root) identity = base.Field('Id', required=True) """The Bios resource identity string""" name = base.Field('Name') """The name of the resource""" description = base.Field('Description') """Human-readable description of the BIOS resource""" _attribute_registry = base.Field('AttributeRegistry') """The Resource ID of the Attribute Registry for the BIOS Attributes resource """ _settings = settings.SettingsField() """Results of last BIOS attribute update""" attributes = base.Field('Attributes') """Vendor-specific key-value dict of effective BIOS attributes Attributes cannot be updated directly. To update use :py:func:`~set_attribute` or :py:func:`~set_attributes` """ maintenance_window = settings.MaintenanceWindowField( '@Redfish.MaintenanceWindow') """Indicates if a given resource has a maintenance window assignment for applying settings or operations""" _actions = ActionsField('Actions') _apply_time_settings = settings.SettingsApplyTimeField() @property @utils.cache_it def _pending_settings_resource(self): """Pending BIOS settings resource""" return Bios( self._conn, self._settings.resource_uri, registries=None, redfish_version=self.redfish_version, root=self.root) @property def pending_attributes(self): """Pending BIOS attributes BIOS attributes that have been committed to the system, but for them to take effect system restart is necessary """ return self._pending_settings_resource.attributes @property def apply_time_settings(self): return self._pending_settings_resource._apply_time_settings def set_attribute(self, key, value, apply_time=None, maint_window_start_time=None, maint_window_duration=None): """Update an attribute Attribute update is not immediate but requires system restart. Committed attributes can be checked at :py:attr:`~pending_attributes` property :param key: Attribute name :param value: Attribute value :param apply_time: When to update the attribute. Optional. An :py:class:`sushy.ApplyTime` value. :param maint_window_start_time: The start time of a maintenance window, datetime. Required when updating during maintenance window and default maintenance window not set by the system. :param maint_window_duration: Duration of maintenance time since maintenance window start time in seconds. Required when updating during maintenance window and default maintenance window not set by the system. """ self.set_attributes({key: value}, apply_time, maint_window_start_time, maint_window_duration) def set_attributes(self, value, apply_time=None, maint_window_start_time=None, maint_window_duration=None): """Update many attributes at once Attribute update is not immediate but requires system restart. Committed attributes can be checked at :py:attr:`~pending_attributes` property :param value: Key-value pairs for attribute name and value :param apply_time: When to update the attributes. Optional. An :py:class:`sushy.ApplyTime` value. :param maint_window_start_time: The start time of a maintenance window, datetime. Required when updating during maintenance window and default maintenance window not set by the system. :param maint_window_duration: Duration of maintenance time since maintenance window start time in seconds. Required when updating during maintenance window and default maintenance window not set by the system. """ payload = {'Attributes': value} payload = utils.process_apply_time_input( payload, apply_time, maint_window_start_time, maint_window_duration) # NOTE(vanou): To retrieve current ETag value of @Redfish.Settings # but not update cached _pending_settings_resource, because cached # property is only this one and re-cache is not required self.refresh(force=False) self._settings.commit(self._conn, payload) utils.cache_clear(self, force_refresh=False, only_these=['_pending_settings_resource']) def _get_reset_bios_action_element(self): actions = self._actions if not actions: raise exceptions.MissingAttributeError(attribute="Actions", resource=self._path) reset_bios_action = actions.reset_bios if not reset_bios_action: raise exceptions.MissingActionError(action='#Bios.ResetBios', resource=self._path) return reset_bios_action def _get_change_password_element(self): actions = self._actions if not actions: raise exceptions.MissingAttributeError(attribute="Actions", resource=self._path) change_password_action = actions.change_password if not change_password_action: raise exceptions.MissingActionError(action='#Bios.ChangePassword', resource=self._path) return change_password_action def reset_bios(self): """Reset the BIOS attributes to default""" target_uri = self._get_reset_bios_action_element().target_uri LOG.debug('Resetting BIOS attributes %s ...', self.identity) try: self._conn.post(target_uri) except exceptions.HTTPError as resp: # Send empty payload, if BMC expects body if resp.status_code in [http_client.UNSUPPORTED_MEDIA_TYPE, http_client.BAD_REQUEST]: self._conn.post(target_uri, data={}) else: raise LOG.info('BIOS attributes %s is being reset', self.identity) def change_password(self, new_password, old_password, password_name): """Change BIOS password""" target_uri = self._get_change_password_element().target_uri LOG.debug('Changing BIOS password %s ...', self.identity) self._conn.post(target_uri, data={'NewPassword': new_password, 'OldPassword': old_password, 'PasswordName': password_name}) LOG.info('BIOS password %s is being changed', self.identity) @property def update_status(self): """Status of the last attribute update :returns: :class:`sushy.resources.settings.SettingsUpdate` object containing status and any messages """ return self._settings.get_status(self._registries) @property def supported_apply_times(self): """List of supported BIOS update apply times :returns: List of supported update apply time names """ return self._settings._supported_apply_times def get_attribute_registry(self, language='en'): """Get the Attribute Registry associated with this BIOS instance :param language: RFC 5646 language code for Message Registries. Indicates language of registry to be used. Defaults to 'en'. :returns: the BIOS Attribute Registry """ return self._get_registry(self._attribute_registry, language=language, description='BIOS attribute registry') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/system/constants.py0000664000175000017500000003317300000000000022025 0ustar00zuulzuul00000000000000# Copyright 2017 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. # Values come from the Redfish System json-schema 1.0.0: # http://redfish.dmtf.org/schemas/v1/ComputerSystem.v1_0_0.json#/definitions/ComputerSystem # noqa import enum from sushy.resources import constants as res_cons # Reset action constants RESET_ON = res_cons.ResetType.ON RESET_FORCE_OFF = res_cons.ResetType.FORCE_OFF RESET_GRACEFUL_SHUTDOWN = res_cons.ResetType.GRACEFUL_SHUTDOWN RESET_GRACEFUL_RESTART = res_cons.ResetType.GRACEFUL_RESTART RESET_FORCE_RESTART = res_cons.ResetType.FORCE_RESTART RESET_NMI = res_cons.ResetType.NMI RESET_FORCE_ON = res_cons.ResetType.FORCE_ON RESET_PUSH_POWER_BUTTON = res_cons.ResetType.PUSH_POWER_BUTTON # System' PowerState constants SYSTEM_POWER_STATE_ON = res_cons.POWER_STATE_ON """The system is powered on""" SYSTEM_POWER_STATE_OFF = res_cons.POWER_STATE_OFF """The system is powered off, although some components may continue to have AUX power such as management controller""" SYSTEM_POWER_STATE_POWERING_ON = res_cons.POWER_STATE_POWERING_ON """A temporary state between Off and On. This temporary state can be very short""" SYSTEM_POWER_STATE_POWERING_OFF = res_cons.POWER_STATE_POWERING_OFF """A temporary state between On and Off. The power off action can take time while the OS is in the shutdown process""" class BootSource(enum.Enum): """Boot source target constants""" NONE = 'None' """Boot from the normal boot device.""" PXE = 'Pxe' """Boot from the Pre-Boot EXecution (PXE) environment.""" FLOPPY = 'Floppy' """Boot from the floppy disk drive.""" CD = 'Cd' """Boot from the CD or DVD.""" USB = 'Usb' """Boot from a system BIOS-specified USB device.""" HDD = 'Hdd' """Boot from a hard drive.""" BIOS_SETUP = 'BiosSetup' """Boot to the BIOS setup utility.""" UTILITIES = 'Utilities' """Boot to the manufacturer's utilities program or programs.""" DIAGS = 'Diags' """Boot to the manufacturer's diagnostics program.""" UEFI_SHELL = 'UefiShell' """Boot to the UEFI Shell.""" UEFI_TARGET = 'UefiTarget' """Boot to the UEFI device specified in the UefiTargetBootSourceOverride property.""" SD_CARD = 'SDCard' """Boot from an SD card.""" UEFI_HTTP = 'UefiHttp' """Boot from a UEFI HTTP network location.""" REMOTE_DRIVE = 'RemoteDrive' """Boot from a remote drive, such as an iSCSI target.""" UEFI_BOOT_NEXT = 'UefiBootNext' """Boot to the UEFI device that the BootNext property specifies.""" USB_CD = 'UsbCd' """Boot from a USB CD device as specified by the system BIOS. **This is NOT a standard value!** On SuperMicro X11 and X12 machines, virtual media is presented as an USB CD drive as opposed to a CD drive. Both are present in the list of boot devices, however only selecting UsbCd as the boot source results in a successful boot from vMedia. If CD is selected, boot fails even if vMedia is inserted.""" # Backward compatibility BOOT_SOURCE_TARGET_NONE = BootSource.NONE BOOT_SOURCE_TARGET_PXE = BootSource.PXE BOOT_SOURCE_TARGET_FLOPPY = BootSource.FLOPPY BOOT_SOURCE_TARGET_CD = BootSource.CD BOOT_SOURCE_TARGET_USB = BootSource.USB BOOT_SOURCE_TARGET_HDD = BootSource.HDD BOOT_SOURCE_TARGET_BIOS_SETUP = BootSource.BIOS_SETUP BOOT_SOURCE_TARGET_UTILITIES = BootSource.UTILITIES BOOT_SOURCE_TARGET_DIAGS = BootSource.DIAGS BOOT_SOURCE_TARGET_UEFI_SHELL = BootSource.UEFI_SHELL BOOT_SOURCE_TARGET_UEFI_TARGET = BootSource.UEFI_TARGET BOOT_SOURCE_TARGET_SD_CARD = BootSource.SD_CARD BOOT_SOURCE_TARGET_UEFI_HTTP = BootSource.UEFI_HTTP BOOT_SOURCE_TARGET_USB_CD = BootSource.USB_CD class BootSourceOverrideMode(enum.Enum): """Boot source mode constants""" LEGACY = 'Legacy' """The system boots in non-UEFI boot mode to the boot source override target.""" UEFI = 'UEFI' """The system boots in UEFI boot mode to the boot source override target.""" # Backward compatibility BOOT_SOURCE_MODE_BIOS = BootSourceOverrideMode.LEGACY BOOT_SOURCE_MODE_UEFI = BootSourceOverrideMode.UEFI class BootSourceOverrideEnabled(enum.Enum): """Boot source enabled constants""" DISABLED = 'Disabled' """The system boots normally.""" ONCE = 'Once' """On its next boot cycle, the system boots one time to the boot source override target. Then, the BootSourceOverrideEnabled value is reset to `Disabled`.""" CONTINUOUS = 'Continuous' """The system boots to the target specified in the BootSourceOverrideTarget property until this property is `Disabled`.""" # Backward compatibility BOOT_SOURCE_ENABLED_DISABLED = BootSourceOverrideEnabled.DISABLED BOOT_SOURCE_ENABLED_ONCE = BootSourceOverrideEnabled.ONCE BOOT_SOURCE_ENABLED_CONTINUOUS = BootSourceOverrideEnabled.CONTINUOUS class BootProgressStates(enum.Enum): """Boot System Progress Indicator constants""" # Added in ComputerSystem 1.15.0 NONE = 'None' """The system is not booting.""" PRIMARY_PROCESSOR = 'PrimaryProcessorInitializationStarted' """Initialization of the Primary Processor has started.""" BUS = 'BusInitializationStarted' """Initialization of the buses has started.""" MEMORY = 'MemoryInitializationStarted' """Initialization of memory has started.""" SECONDARY_PROCESSOR = 'SecondaryProcessorInitializationStarted' """Secondary Prcessors have started initialization.""" PCI_RESOURCE_CONFIG = 'PCIResourceConfigStarted' """Initalizatoin of PCI Resources has started.""" HARDWARE_COMPLETE = 'SystemHardwareInitializationComplete' """Hardware Initialization is completed.""" SETUP = 'SetupEntered' """System is in the Setup utility.""" OS_BOOT_STARTED = 'OSBootStarted' """Boot of the Operating System has started.""" OS_RUNNING = 'OSRunning' """Operating System Running.""" OEM = 'OEM' """OEM Defined Boot Progress State.""" class SystemType(enum.Enum): """System type constants""" PHYSICAL = 'Physical' """A computer system.""" VIRTUAL = 'Virtual' """A virtual machine instance running on this system.""" OS = 'OS' """An operating system instance.""" PHYSICALLY_PARTITIONED = 'PhysicallyPartitioned' """A hardware-based partition of a computer system.""" VIRTUALLY_PARTITIONED = 'VirtuallyPartitioned' """A virtual or software-based partition of a computer system.""" COMPOSED = 'Composed' """A computer system constructed by binding resource blocks together.""" DPU = 'DPU' """A computer system that performs the functions of a data processing unit, such as a SmartNIC.""" # Backward compatibility SYSTEM_TYPE_PHYSICAL = SystemType.PHYSICAL SYSTEM_TYPE_VIRTUAL = SystemType.VIRTUAL SYSTEM_TYPE_OS = SystemType.OS SYSTEM_TYPE_PHYSICALLY_PARTITIONED = SystemType.PHYSICALLY_PARTITIONED SYSTEM_TYPE_VIRTUALLY_PARTITIONED = SystemType.VIRTUALLY_PARTITIONED SYSTEM_TYPE_COMPOSED = SystemType.COMPOSED SYSTEM_TYPE_DPU = SystemType.DPU # Processor related constants # Values comes from the Redfish Processor json-schema 1.3.0: # http://redfish.dmtf.org/schemas/v1/Processor.v1_3_0.json class ProcessorArchitecture(enum.Enum): """Processor Architecture constants""" X86 = 'x86' """x86 or x86-64.""" IA_64 = 'IA-64' """Intel Itanium.""" ARM = 'ARM' """ARM.""" MIPS = 'MIPS' """MIPS.""" POWER = 'Power' """Power.""" OEM = 'OEM' """OEM-defined.""" # Backward compatibility PROCESSOR_ARCH_x86 = ProcessorArchitecture.X86 PROCESSOR_ARCH_IA_64 = ProcessorArchitecture.IA_64 PROCESSOR_ARCH_ARM = ProcessorArchitecture.ARM PROCESSOR_ARCH_MIPS = ProcessorArchitecture.MIPS PROCESSOR_ARCH_OEM = ProcessorArchitecture.OEM class ProcessorType(enum.Enum): """Processor type constants""" CPU = 'CPU' """A CPU.""" GPU = 'GPU' """A GPU.""" FPGA = 'FPGA' """An FPGA.""" DSP = 'DSP' """A DSP.""" ACCELERATOR = 'Accelerator' """An accelerator.""" CORE = 'Core' """A core in a processor.""" THREAD = 'Thread' """A thread in a processor.""" OEM = 'OEM' """An OEM-defined processing unit.""" # Backward compatibility PROCESSOR_TYPE_CPU = ProcessorType.CPU PROCESSOR_TYPE_GPU = ProcessorType.GPU PROCESSOR_TYPE_FPGA = ProcessorType.FPGA PROCESSOR_TYPE_DSP = ProcessorType.DSP PROCESSOR_TYPE_ACCELERATOR = ProcessorType.ACCELERATOR PROCESSOR_TYPE_CORE = ProcessorType.CORE PROCESSOR_TYPE_THREAD = ProcessorType.THREAD PROCESSOR_TYPE_OEM = ProcessorType.OEM class InstructionSet(enum.Enum): """Processor InstructionSet constants""" X86 = 'x86' """x86 32-bit.""" X86_64 = 'x86-64' """x86 64-bit.""" IA_64 = 'IA-64' """Intel IA-64.""" ARM_A32 = 'ARM-A32' """ARM 32-bit.""" ARM_A64 = 'ARM-A64' """ARM 64-bit.""" MIPS32 = 'MIPS32' """MIPS 32-bit.""" MIPS64 = 'MIPS64' """MIPS 64-bit.""" POWER_ISA = 'PowerISA' """PowerISA-64 or PowerISA-32.""" OEM = 'OEM' """OEM-defined.""" PROCESSOR_INSTRUCTIONSET_ARM_A32 = InstructionSet.ARM_A32 PROCESSOR_INSTRUCTIONSET_ARM_A64 = InstructionSet.ARM_A64 PROCESSOR_INSTRUCTIONSET_IA_64 = InstructionSet.IA_64 PROCESSOR_INSTRUCTIONSET_MIPS32 = InstructionSet.MIPS32 PROCESSOR_INSTRUCTIONSET_MIPS64 = InstructionSet.MIPS64 PROCESSOR_INSTRUCTIONSET_OEM = InstructionSet.OEM PROCESSOR_INSTRUCTIONSET_x86 = InstructionSet.X86 PROCESSOR_INSTRUCTIONSET_x86_64 = InstructionSet.X86_64 # Secure boot constants from SecureBoot schema version 1.1.0 # https://redfish.dmtf.org/schemas/v1/SecureBoot.v1_1_0.json # Some names were altered for clarity class SecureBootCurrentBoot(enum.Enum): ENABLED = 'Enabled' """UEFI Secure Boot is currently enabled.""" DISABLED = 'Disabled' """UEFI Secure Boot is currently disabled.""" # Backward compatibility SECURE_BOOT_ENABLED = SecureBootCurrentBoot.ENABLED SECURE_BOOT_DISABLED = SecureBootCurrentBoot.DISABLED class SecureBootMode(enum.Enum): SETUP = 'SetupMode' """UEFI Secure Boot is currently in Setup Mode.""" USER = 'UserMode' """UEFI Secure Boot is currently in User Mode.""" AUDIT = 'AuditMode' """UEFI Secure Boot is currently in Audit Mode.""" DEPLOYED = 'DeployedMode' """UEFI Secure Boot is currently in Deployed Mode.""" # Backward compatibility SECURE_BOOT_MODE_SETUP = SecureBootMode.SETUP SECURE_BOOT_MODE_USER = SecureBootMode.USER SECURE_BOOT_MODE_AUDIT = SecureBootMode.AUDIT SECURE_BOOT_MODE_DEPLOYED = SecureBootMode.DEPLOYED class SecureBootResetKeysType(enum.Enum): RESET_ALL_KEYS_TO_DEFAULT = 'ResetAllKeysToDefault' """Reset the contents of all UEFI Secure Boot key databases, including the PK key database, to the default values.""" DELETE_ALL_KEYS = 'DeleteAllKeys' """Delete the contents of all UEFI Secure Boot key databases, including the PK key database. This puts the system in Setup Mode.""" DELETE_PK = 'DeletePK' """Delete the contents of the PK UEFI Secure Boot database. This puts the system in Setup Mode.""" # Internal constant based on # https://redfish.dmtf.org/schemas/v1/SecureBootDatabase.v1_0_1.json _SECURE_BOOT_DATABASE_RESET_KEYS = frozenset([ SecureBootResetKeysType.RESET_ALL_KEYS_TO_DEFAULT, SecureBootResetKeysType.DELETE_ALL_KEYS, ]) # Backward compatibility SECURE_BOOT_RESET_KEYS_TO_DEFAULT = \ SecureBootResetKeysType.RESET_ALL_KEYS_TO_DEFAULT SECURE_BOOT_RESET_KEYS_DELETE_ALL = SecureBootResetKeysType.DELETE_ALL_KEYS SECURE_BOOT_RESET_KEYS_DELETE_PK = SecureBootResetKeysType.DELETE_PK class SecureBootDatabaseId(enum.Enum): # This enumeration is hand-written based on the database schema # https://redfish.dmtf.org/schemas/v1/SecureBootDatabase.v1_0_1.json # and the UEFI specification. PLATFORM_KEY = "PK" KEY_EXCHANGE_KEYS = "KEK" ALLOWED_KEYS_DATABASE = "db" DENIED_KEYS_DATABASE = "dbx" RECOVERY_KEYS_DATABASE = "dbr" TIMESTAMP_DATABASE = "dbt" DEFAULT_PLATFORM_KEY = "PKDefault" DEFAULT_KEY_EXCHANGE_KEYS = "KEKDefault" DEFAULT_ALLOWED_KEYS_DATABASE = "dbDefault" DEFAULT_DENIED_KEYS_DATABASE = "dbxDefault" DEFAULT_RECOVERY_KEYS_DATABASE = "dbrDefault" DEFAULT_TIMESTAMP_DATABASE = "dbtDefault" # Backward compatibility SECURE_BOOT_PLATFORM_KEY = SecureBootDatabaseId.PLATFORM_KEY SECURE_BOOT_KEY_EXCHANGE_KEYS = SecureBootDatabaseId.KEY_EXCHANGE_KEYS SECURE_BOOT_ALLOWED_KEYS_DATABASE = SecureBootDatabaseId.ALLOWED_KEYS_DATABASE SECURE_BOOT_DENIED_KEYS_DATABASE = SecureBootDatabaseId.DENIED_KEYS_DATABASE SECURE_BOOT_RECOVERY_KEYS_DATABASE = \ SecureBootDatabaseId.RECOVERY_KEYS_DATABASE SECURE_BOOT_TIMESTAMP_DATABASE = SecureBootDatabaseId.TIMESTAMP_DATABASE SECURE_BOOT_DEFAULT_PLATFORM_KEY = SecureBootDatabaseId.DEFAULT_PLATFORM_KEY SECURE_BOOT_DEFAULT_KEY_EXCHANGE_KEYS = \ SecureBootDatabaseId.DEFAULT_KEY_EXCHANGE_KEYS SECURE_BOOT_DEFAULT_ALLOWED_KEYS_DATABASE = \ SecureBootDatabaseId.DEFAULT_ALLOWED_KEYS_DATABASE SECURE_BOOT_DEFAULT_DENIED_KEYS_DATABASE = \ SecureBootDatabaseId.DEFAULT_DENIED_KEYS_DATABASE SECURE_BOOT_DEFAULT_RECOVERY_KEYS_DATABASE = \ SecureBootDatabaseId.DEFAULT_RECOVERY_KEYS_DATABASE SECURE_BOOT_DEFAULT_TIMESTAMP_DATABASE = \ SecureBootDatabaseId.DEFAULT_TIMESTAMP_DATABASE ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/system/ethernet_interface.py0000664000175000017500000000473000000000000023644 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/EthernetInterface.v1_4_0.json import logging from sushy.resources import base from sushy.resources import common from sushy.resources import constants as res_cons from sushy import utils LOG = logging.getLogger(__name__) class EthernetInterface(base.ResourceBase): """This class adds the EthernetInterface resource""" identity = base.Field('Id') """The Ethernet Interface identity string""" name = base.Field('Name') """The name of the resource or array element""" description = base.Field('Description') """Description""" permanent_mac_address = base.Field('PermanentMACAddress') """This is the permanent MAC address assigned to this interface (port) """ mac_address = base.Field('MACAddress') """This is the currently configured MAC address of the interface.""" speed_mbps = base.Field('SpeedMbps') """This is the current speed in Mbps of this interface.""" status = common.StatusField("Status") """Describes the status and health of this interface.""" class EthernetInterfaceCollection(base.ResourceCollectionBase): @property def _resource_type(self): return EthernetInterface @property @utils.cache_it def summary(self): """Summary of MAC addresses and interfaces state This filters the MACs whose health is OK, which means the MACs in both 'Enabled' and 'Disabled' States are returned. :returns: dictionary in the format {'aa:bb:cc:dd:ee:ff': sushy.State.ENABLED, 'aa:bb:aa:aa:aa:aa': sushy.State.DISABLED} """ mac_dict = {} for eth in self.get_members(): if eth.mac_address and eth.status is not None: if eth.status.health == res_cons.Health.OK: mac_dict[eth.mac_address] = eth.status.state return mac_dict ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6555147 sushy-5.5.0/sushy/resources/system/network/0000775000175000017500000000000000000000000021121 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/system/network/__init__.py0000664000175000017500000000000000000000000023220 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/system/network/adapter.py0000664000175000017500000000734200000000000023121 0ustar00zuulzuul00000000000000# Copyright (c) 2021 Anexia Internetdienstleistungs GmbH # 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 is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/v1/NetworkAdapter.v1_3_0.json from sushy.resources import base from sushy.resources import common from sushy.resources.system.network import device_function from sushy.resources.system.network import port as network_port from sushy.resources.system import port from sushy import utils class NetworkAdapter(base.ResourceBase): description = base.Field('Description') """Human-readable description of the resource""" identity = base.Field('Id', required=True) """The network adapter identity string""" manufacturer = base.Field("Manufacturer") """The manufacturer of this network adapter""" model = base.Field("Model") """The model of this network adapter""" name = base.Field('Name') """The name of the network adapter""" part_number = base.Field("PartNumber") """The part number of the network adapter""" serial_number = base.Field("SerialNumber") """The serial number of the network adapter""" status = common.StatusField("Status") """The status""" @property @utils.cache_it def network_device_functions(self): """Property to reference `NetworkDeviceFunctionCollection` instance It is set once when the first time it is queried. On refresh, this property is marked as stale (greedy-refresh not done). Here the actual refresh of the sub-resource happens, if stale. """ return device_function.NetworkDeviceFunctionCollection( self._conn, path=utils.get_sub_resource_path_by( self, "NetworkDeviceFunctions" ), redfish_version=self.redfish_version, registries=self.registries, root=self.root ) @property @utils.cache_it def network_ports(self): """Property to reference `NetworkPortCollection` instance It is set once when the first time it is queried. On refresh, this property is marked as stale (greedy-refresh not done). Here the actual refresh of the sub-resource happens, if stale. """ return network_port.NetworkPortCollection( self._conn, path=utils.get_sub_resource_path_by(self, "NetworkPorts"), redfish_version=self.redfish_version, registries=self.registries, root=self.root ) @property @utils.cache_it def ports(self): """Property to reference `PortCollection` instance It is set once when the first time it is queried. On refresh, this property is marked as stale (greedy-refresh not done). Here the actual refresh of the sub-resource happens, if stale. """ return port.PortCollection( self._conn, path=utils.get_sub_resource_path_by(self, "Ports"), redfish_version=self.redfish_version, registries=self.registries, root=self.root ) class NetworkAdapterCollection(base.ResourceCollectionBase): @property def _resource_type(self): return NetworkAdapter ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/system/network/constants.py0000664000175000017500000001033700000000000023513 0ustar00zuulzuul00000000000000# Copyright (c) 2021 Anexia Internetdienstleistungs GmbH # 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. # Values comes from the Redfish System json-schema: # https://redfish.dmtf.org/schemas/v1/NetworkDeviceFunction.v1_7_0.json # https://redfish.dmtf.org/schemas/v1/NetworkPort.v1_4_1.json # Using Network prefix since some names are too generic. import enum class NetworkAuthenticationMethod(enum.Enum): NONE = 'None' """No iSCSI authentication is used.""" CHAP = 'CHAP' """iSCSI Challenge Handshake Authentication Protocol (CHAP) authentication is used.""" MUTUAL_CHAP = 'MutualCHAP' """iSCSI Mutual Challenge Handshake Authentication Protocol (CHAP) authentication is used.""" class NetworkBootMode(enum.Enum): DISABLED = 'Disabled' """Do not indicate to UEFI/BIOS that this device is bootable.""" PXE = 'PXE' """Boot this device by using the embedded PXE support. Only applicable if the NetDevFuncType is `Ethernet` or `InfiniBand`.""" SCSI = 'iSCSI' """Boot this device by using the embedded iSCSI boot support and configuration. Only applicable if the NetDevFuncType is `iSCSI` or `Ethernet`.""" FIBRE_CHANNEL = 'FibreChannel' """Boot this device by using the embedded Fibre Channel support and configuration. Only applicable if the NetDevFuncType is `FibreChannel`.""" FIBRE_CHANNEL_OVER_ETHERNET = 'FibreChannelOverEthernet' """Boot this device by using the embedded Fibre Channel over Ethernet (FCoE) boot support and configuration. Only applicable if the NetDevFuncType is `FibreChannelOverEthernet`.""" class IPAddressType(enum.Enum): IPV4 = 'IPv4' """IPv4 addressing is used for all IP-fields in this object.""" IPV6 = 'IPv6' """IPv6 addressing is used for all IP-fields in this object.""" class FlowControl(enum.Enum): NONE = 'None' """No IEEE 802.3x flow control is enabled on this port.""" TX = 'TX' """This station can initiate IEEE 802.3x flow control.""" RX = 'RX' """The link partner can initiate IEEE 802.3x flow control.""" TX_RX = 'TX_RX' """This station or the link partner can initiate IEEE 802.3x flow control.""" class NetworkDeviceTechnology(enum.Enum): DISABLED = 'Disabled' """Neither enumerated nor visible to the operating system.""" ETHERNET = 'Ethernet' """Appears to the operating system as an Ethernet device.""" FIBRE_CHANNEL = 'FibreChannel' """Appears to the operating system as a Fibre Channel device.""" iSCSI = 'iSCSI' """Appears to the operating system as an iSCSI device.""" FIBRE_CHANNEL_OVER_ETHERNET = 'FibreChannelOverEthernet' """Appears to the operating system as an FCoE device.""" INFINI_BAND = 'InfiniBand' """Appears to the operating system as an InfiniBand device.""" class LinkStatus(enum.Enum): DOWN = 'Down' """The port is enabled but link is down.""" UP = 'Up' """The port is enabled and link is good (up).""" STARTING = 'Starting' """This link on this interface is starting. A physical link has been established, but the port is not able to transfer data.""" TRAINING = 'Training' """This physical link on this interface is training.""" class PortLinkStatus(enum.Enum): LINKUP = 'LinkUp' """The port is enabled and link is good (up).""" LINKDOWN = 'LinkDown' """The port is enabled but link is down.""" NOLINK = 'NoLink' """The port is enabled but have no connectivity.""" STARTING = 'Starting' """This link on this interface is starting. A physical link has been established, but the port is not able to transfer data.""" TRAINING = 'Training' """This physical link on this interface is training.""" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/system/network/device_function.py0000664000175000017500000001547300000000000024651 0ustar00zuulzuul00000000000000# Copyright (c) 2021 Anexia Internetdienstleistungs GmbH # 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 is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/v1/NetworkDeviceFunction.v1_3_3.json from sushy.resources import base from sushy.resources import common from sushy.resources.system.network import constants from sushy.resources.system.network import port from sushy import utils class BootTargetsField(base.ListField): lun_id = base.Field("LUNID") """The logical unit number (LUN) ID from which to boot on the device""" priority = base.Field("BootPriority", adapter=utils.int_or_none) """The relative priority for this entry in the boot targets array.""" wwpn = base.Field("WWPN") """The World Wide Port Name (WWPN) from which to boot.""" class VLANField(base.CompositeField): vlan_enabled = base.Field("VLANEnable", adapter=utils.bool_or_none) vlan_id = base.Field("VLANId", adapter=utils.int_or_none) class ISCSIBootField(base.CompositeField): authentication_method = base.MappedField( 'AuthenticationMethod', constants.NetworkAuthenticationMethod) """The configured capability of this network device function.""" initiator_default_gateway = base.Field('InitiatorDefaultGateway') """The IPv6 or IPv4 iSCSI boot default gateway.""" initiator_ip_address = base.Field('InitiatorIPAddress') """The IPv6 or IPv4 address of the iSCSI initiator.""" initiator_netmask = base.Field('InitiatorNetmask') """The IPv6 or IPv4 netmask of the iSCSI boot initiator.""" ip_address_type = base.MappedField( 'IPAddressType', constants.IPAddressType) """The type of IP address being populated IP address fields.""" primary_dns = base.Field('PrimaryDNS') """The IPv6 or IPv4 address of the primary DNS server.""" primary_lun = base.Field('PrimaryLUN', adapter=utils.int_or_none) """The logical unit number (LUN) for the primary iSCSI boot target.""" primary_target_ip_address = base.Field('PrimaryTargetIPAddress') """The IPv4 or IPv6 address for the primary iSCSI boot target.""" primary_target_tcp_port = base.Field('PrimaryTargetTCPPort') """The TCP port for the primary iSCSI boot target.""" primary_vlan_enabled = base.Field( 'PrimaryVLANEnable', adapter=utils.bool_or_none ) """An indication of whether the primary VLAN is enabled.""" primary_vlan_id = base.Field("PrimaryVLANId", adapter=utils.int_or_none) """The 802.1q VLAN ID to use for iSCSI boot from the primary target.""" secondary_dns = base.Field('SecondaryDNS') """The IPv6 or IPv4 address of the secondary DNS server.""" secondary_lun = base.Field('SecondaryLUN', adapter=utils.int_or_none) """The logical unit number (LUN) for the secondary iSCSI boot target.""" secondary_target_ip_address = base.Field('SecondaryTargetIPAddress') """The IPv4 or IPv6 address for the secondary iSCSI boot target.""" secondary_target_tcp_port = base.Field('SecondaryTargetTCPPort') """The TCP port for the secondary iSCSI boot target.""" secondary_vlan_enabled = base.Field( 'SecondaryVLANEnable', adapter=utils.bool_or_none) """An indication of whether the secondary VLAN is enabled.""" secondary_vlan_id = base.Field( "SecondaryVLANId", adapter=utils.int_or_none ) """The 802.1q VLAN ID to use for iSCSI boot from the secondary target.""" class EthernetField(base.CompositeField): mac_address = base.Field("MACAddress") """The currently configured MAC address of the resource""" mtu_size = base.Field("MTUSize", adapter=utils.int_or_none) """The Maximum Transmission Unit (MTU) configured for this resource""" permanent_mac_address = base.Field("PermanentMACAddress") """The permanent MAC address assigned to this resource""" vlan = VLANField("VLAN") """The VLAN for this interface""" class FibreChannelField(base.CompositeField): boot_targets = BootTargetsField("BootTargets") """An array of Fibre Channel boot targets configured for this resource.""" class NetworkDeviceFunction(base.ResourceBase): capabilities = base.MappedListField( 'NetDevFuncCapabilities', constants.NetworkDeviceTechnology) """An array of capabilities for this network device function.""" type = base.MappedField( 'NetDevFuncType', constants.NetworkDeviceTechnology) """The configured capability of this network device function.""" description = base.Field('Description') """The network device function description""" ethernet = EthernetField("Ethernet") """The Ethernet capabilities, status, and configuration values.""" fibre_channel = FibreChannelField("FibreChannel") """The Fibre Channel capabilities, status, and configuration values.""" identity = base.Field('Id', required=True) """Identifier for the network device function""" iscsi_boot = ISCSIBootField('iSCSIBoot') """ The iSCSI boot capabilities, status, and configuration for a network device function. """ max_virtual_functions = base.Field( 'MaxVirtualFunctions', adapter=utils.int_or_none ) """ The number of virtual functions that are available for this network device function. """ name = base.Field('Name', required=True) """The network device function name""" status = common.StatusField('Status') """The status of the resource""" @property @utils.cache_it def assignable_physical_ports(self): """An array of physical ports to which this resource may be assigned. Network ports to which this network device function may be assigned. :raises: MissingAttributeError if '@odata.id' field is missing. :returns: A list of `NetworkPort` instances """ paths = utils.get_sub_resource_path_by( self, "AssignablePhysicalPorts", is_collection=True) return [port.NetworkPort(self._conn, path, redfish_version=self.redfish_version, registries=self.registries, root=self.root ) for path in paths] class NetworkDeviceFunctionCollection(base.ResourceCollectionBase): @property def _resource_type(self): return NetworkDeviceFunction ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/system/network/port.py0000664000175000017500000000447200000000000022466 0ustar00zuulzuul00000000000000# Copyright (c) 2021 Anexia Internetdienstleistungs GmbH # 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 is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/v1/NetworkPort.v1_2_1.json from sushy.resources import base from sushy.resources import common from sushy.resources.system.network import constants class NetworkPortCollection(base.ResourceCollectionBase): @property def _resource_type(self): return NetworkPort class NetworkPort(base.ResourceBase): associated_network_addresses = base.Field( "AssociatedNetworkAddresses", adapter=list ) """The array of configured network addresses that are associated.""" current_link_speed_mbps = base.Field( "CurrentLinkSpeedMbps", adapter=int ) """The network port current link speed.""" description = base.Field('Description') """The network port description""" flow_control_configuration = base.MappedField('FlowControlConfiguration', constants.FlowControl) """The locally configured 802.3x flow control setting.""" flow_control_status = base.MappedField('FlowControlStatus', constants.FlowControl) """The 802.3x flow control behavior negotiated with the link partner""" identity = base.Field('Id', required=True) """The network port identity""" link_status = base.MappedField('LinkStatus', constants.LinkStatus) """The link status of the network port.""" name = base.Field( "Name", required=True ) """The network port name""" physical_port_number = base.Field("PhysicalPortNumber", adapter=int) """The physical port number label for this port.""" status = common.StatusField('Status') """The network port status""" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/system/port.py0000664000175000017500000000507300000000000020773 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/v1/Port.v1_8_0.json from sushy.resources import base from sushy.resources import common from sushy.resources.system.network import constants class LLDPReceiveField(base.CompositeField): chassis_id = base.Field("ChassisId") """chassis ID received from the remote partner across this link.""" port_id = base.Field("PortId") """A colon delimited string of hexadecimal octets identifying a port.""" class EthernetField(base.CompositeField): associated_mac_addresses = base.Field( "AssociatedMACAddresses", adapter=list ) """The array of configured MAC addresses that are associated.""" flow_control_configuration = base.MappedField( "FlowControlConfiguration", constants.FlowControl ) """The locally configured 802.3x flow control setting.""" flow_control_status = base.MappedField( "FlowControlStatus", constants.FlowControl ) """The 802.3x flow control behavior negotiated with the link partner""" lldp_receive = LLDPReceiveField("LLDPReceive") """LLDP data being received on this link.""" class Port(base.ResourceBase): """This class adds the Port resource""" identity = base.Field("Id", required=True) """The Port identity string""" name = base.Field("Name", required=True) """The port name""" current_speed_gbps = base.Field("CurrentSpeedGbps", adapter=int) """The network port current link speed.""" description = base.Field("Description") """The port description""" ethernet = EthernetField("Ethernet") """The Ethernet-specific properties of the port.""" link_status = base.MappedField("LinkStatus", constants.PortLinkStatus) """The link status of the port.""" port_id = base.Field("PortId") """The physical port id label for this port.""" status = common.StatusField("Status") """The port status""" class PortCollection(base.ResourceCollectionBase): @property def _resource_type(self): return Port ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/system/processor.py0000664000175000017500000001465400000000000022033 0ustar00zuulzuul00000000000000# 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. # This is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/Processor.v1_3_0.json import collections import logging from sushy import exceptions from sushy.resources import base from sushy.resources import common from sushy.resources.system import constants as sys_cons from sushy import utils # Representation of Summary of Processor information ProcessorSummary = collections.namedtuple('ProcessorSummary', ['count', 'architecture']) LOG = logging.getLogger(__name__) class ProcessorIdField(base.CompositeField): effective_family = base.Field('EffectiveFamily') """The processor effective family""" effective_model = base.Field('EffectiveModel') """The processor effective model""" identification_registers = base.Field('IdentificationRegisters') """The processor identification registers""" microcode_info = base.Field('MicrocodeInfo') """The processor microcode info""" step = base.Field('Step') """The processor stepping""" vendor_id = base.Field('VendorID') """The processor vendor id""" class Processor(base.ResourceBase): identity = base.Field('Id', required=True) """The processor identity string""" socket = base.Field('Socket') """The socket or location of the processor""" processor_type = base.MappedField('ProcessorType', sys_cons.ProcessorType) """The type of processor""" processor_architecture = base.MappedField('ProcessorArchitecture', sys_cons.ProcessorArchitecture) """The architecture of the processor""" instruction_set = base.MappedField('InstructionSet', sys_cons.InstructionSet) """The instruction set of the processor""" manufacturer = base.Field('Manufacturer') """The processor manufacturer""" model = base.Field('Model') """The product model number of this device""" max_speed_mhz = base.Field('MaxSpeedMHz', adapter=utils.int_or_none) """The maximum clock speed of the processor in MHz.""" processor_id = ProcessorIdField('ProcessorId') """The processor id""" status = common.StatusField('Status') """The processor status""" total_cores = base.Field('TotalCores', adapter=utils.int_or_none) """The total number of cores contained in this processor""" total_threads = base.Field('TotalThreads', adapter=utils.int_or_none) """The total number of execution threads supported by this processor""" def __init__(self, connector, identity, redfish_version=None, registries=None, root=None): """A class representing a Processor :param connector: A Connector instance :param identity: The identity of the processor :param redfish_version: The version of RedFish. Used to construct the object according to schema of the given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, identity, redfish_version=redfish_version, registries=registries, root=root) def _get_subprocessor_collection_path(self): """Helper function to find the SubProcessors path""" subproc_col = self.json.get('SubProcessors') if not subproc_col: raise exceptions.MissingAttributeError( attribute='SubProcessors', resource=self._path) return subproc_col.get('@odata.id') @property @utils.cache_it def sub_processors(self): """A reference to the collection of Sub-Processors""" return ProcessorCollection( self._conn, self._get_subprocessor_collection_path(), redfish_version=self.redfish_version, root=self.root) class ProcessorCollection(base.ResourceCollectionBase): @property def _resource_type(self): return Processor @property @utils.cache_it def summary(self): """Property to provide ProcessorSummary info It is calculated once when the first time it is queried. On refresh, this property gets reset. :returns: A namedtuple containing the ``count`` of processors in regards to logical CPUs, and their ``architecture``. """ count, architecture = 0, None for proc in self.get_members(): # Note(deray): It attempts to detect the number of CPU cores. # It returns the number of logical CPUs. if proc.total_threads is not None: count += proc.total_threads # Note(deray): Bail out of checking the architecture info # if you have already got hold of any one of the processors' # architecture information. if (architecture is None and proc.processor_architecture is not None): architecture = proc.processor_architecture return ProcessorSummary(count=count, architecture=architecture) def __init__(self, connector, path, redfish_version=None, registries=None, root=None): """A class representing a ProcessorCollection :param connector: A Connector instance :param path: The canonical path to the Processor collection resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of the given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, path, redfish_version=redfish_version, registries=registries, root=root) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/system/secure_boot.py0000664000175000017500000001265400000000000022323 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is referred from Redfish standard schema. # http://redfish.dmtf.org/schemas/v1/SecureBoot.v1_1_0.json import logging from sushy import exceptions from sushy.resources import base from sushy.resources import common from sushy.resources.system import constants from sushy.resources.system import secure_boot_database from sushy import utils LOG = logging.getLogger(__name__) class ResetKeysActionField(common.ActionField): allowed_values = base.Field('ResetKeysType@Redfish.AllowableValues', adapter=list) class ActionsField(base.CompositeField): reset_keys = ResetKeysActionField('#SecureBoot.ResetKeys') """Action that resets the UEFI Secure Boot keys.""" class SecureBoot(base.ResourceBase): identity = base.Field('Id', required=True) """The Bios resource identity string""" name = base.Field('Name') """The name of the resource""" description = base.Field('Description') """Human-readable description of the BIOS resource""" current_boot = base.MappedField('SecureBootCurrentBoot', constants.SecureBootCurrentBoot) """The UEFI Secure Boot state during the current boot cycle.""" enabled = base.Field('SecureBootEnable') """Whether the UEFI Secure Boot takes effect on next boot. This property can be enabled in UEFI boot mode only. """ mode = base.MappedField('SecureBootMode', constants.SecureBootMode) """The current UEFI Secure Boot Mode.""" # TODO(dtantsur): SecureBootDatabases _actions = ActionsField('Actions') def __init__(self, connector, path, redfish_version=None, registries=None, root=None): """A class representing secure boot settings. :param connector: A Connector instance :param path: Sub-URI path to the SecureBoot resource :param registries: Dict of message registries to be used when parsing messages of attribute update status :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__(connector, path, redfish_version=redfish_version, registries=registries, root=root) @property @utils.cache_it def databases(self): """A collection of secure boot databases. It is set once when the first time it is queried. On refresh, this property is marked as stale (greedy-refresh not done). Here the actual refresh of the sub-resource happens, if stale. :raises: MissingAttributeError if 'SecureBootDatabases/@odata.id' field is missing. :returns: `SimpleStorageCollection` instance """ return secure_boot_database.SecureBootDatabaseCollection( self._conn, utils.get_sub_resource_path_by( self, "SecureBootDatabases"), redfish_version=self.redfish_version, registries=self.registries, root=self.root) def _get_reset_action_element(self): reset_action = self._actions.reset_keys if not reset_action: raise exceptions.MissingActionError(action='#SecureBoot.ResetKeys', resource=self._path) return reset_action def get_allowed_reset_keys_values(self): """Get the allowed values for resetting the keys. :returns: A set with the allowed values. """ reset_action = self._get_reset_action_element() if not reset_action.allowed_values: LOG.warning('Could not figure out the allowed values for the ' 'reset keys action for %s', self.identity) return set(constants.SecureBootResetKeysType) return {v for v in constants.SecureBootResetKeysType if v.value in reset_action.allowed_values} def reset_keys(self, reset_type): """Reset secure boot keys. :param reset_type: Reset type, one of `SECURE_BOOT_RESET_KEYS_*` constants. """ valid_resets = self.get_allowed_reset_keys_values() if reset_type not in valid_resets: raise exceptions.InvalidParameterValueError( parameter='reset_type', value=reset_type, valid_values=valid_resets) reset_type = constants.SecureBootResetKeysType(reset_type).value target_uri = self._get_reset_action_element().target_uri self._conn.post(target_uri, data={'ResetKeysType': reset_type}) def set_enabled(self, enabled): """Enable/disable secure boot. :param enabled: True, if secure boot is enabled for next boot. """ if not isinstance(enabled, bool): raise exceptions.InvalidParameterValueError( f"Expected a boolean for 'enabled', got {enabled}") etag = self._get_etag() self._conn.patch(self.path, data={'SecureBootEnable': enabled}, etag=etag) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/system/secure_boot_database.py0000664000175000017500000001004600000000000024140 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging from sushy import exceptions from sushy.resources import base from sushy.resources import common from sushy.resources.system import constants LOG = logging.getLogger(__name__) class ResetKeysActionField(common.ActionField): allowed_values = base.Field('ResetKeysType@Redfish.AllowableValues', adapter=list) class ActionsField(base.CompositeField): reset_keys = ResetKeysActionField('#SecureBootDatabase.ResetKeys') """Action that resets the UEFI Secure Boot keys.""" class SecureBootDatabase(base.ResourceBase): # TODO(dtantsur): certificates database_id = base.MappedField('DatabaseId', constants.SecureBootDatabaseId) """Standard UEFI database type.""" description = base.Field('Description') """The system description""" identity = base.Field('Id', required=True) """The secure boot database identity string""" name = base.Field('Name') """The secure boot database name""" # TODO(dtantsur): signatures _actions = ActionsField('Actions') def _get_reset_action_element(self): reset_action = self._actions.reset_keys if not reset_action: raise exceptions.MissingActionError( action='#SecureBootDatabase.ResetKeys', resource=self._path) return reset_action def get_allowed_reset_keys_values(self): """Get the allowed values for resetting the keys. :returns: A set with the allowed values. """ reset_action = self._get_reset_action_element() if not reset_action.allowed_values: LOG.warning('Could not figure out the allowed values for the ' 'reset keys action for %s', self.identity) return constants._SECURE_BOOT_DATABASE_RESET_KEYS return {v for v in constants._SECURE_BOOT_DATABASE_RESET_KEYS if v.value in reset_action.allowed_values} def reset_keys(self, reset_type): """Reset secure boot keys. :param reset_type: Reset type, one of `SECURE_BOOT_RESET_KEYS_*` constants. """ valid_resets = self.get_allowed_reset_keys_values() if reset_type not in valid_resets: raise exceptions.InvalidParameterValueError( parameter='reset_type', value=reset_type, valid_values=valid_resets) reset_type = constants.SecureBootResetKeysType(reset_type).value target_uri = self._get_reset_action_element().target_uri self._conn.post(target_uri, data={'ResetKeysType': reset_type}) class SecureBootDatabaseCollection(base.ResourceCollectionBase): @property def _resource_type(self): return SecureBootDatabase def __init__(self, connector, path, redfish_version=None, registries=None, root=None): """A class representing a ComputerSystemCollection :param connector: A Connector instance :param path: The canonical path to the System collection resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of the given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, path, redfish_version=redfish_version, registries=registries, root=root) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/system/simple_storage.py0000664000175000017500000000546400000000000023030 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is referred from Redfish standard schema. # http://redfish.dmtf.org/schemas/v1/SimpleStorage.v1_2_0.json import logging from sushy.resources import base from sushy.resources import common from sushy.resources import constants as res_cons from sushy import utils LOG = logging.getLogger(__name__) class DeviceListField(base.ListField): """The storage device/s associated with SimpleStorage.""" name = base.Field('Name', required=True) """The name of the storage device""" capacity_bytes = base.Field('CapacityBytes', adapter=utils.int_or_none) """The size of the storage device.""" status = common.StatusField('Status') """Describes the status and health of a storage device.""" class SimpleStorage(base.ResourceBase): """This class represents a simple storage. It represents the properties of a storage controller and its directly-attached devices. A storage device can be a disk drive or optical media device. """ identity = base.Field('Id', required=True) """The SimpleStorage identity string""" name = base.Field('Name') """The name of the resource""" devices = DeviceListField('Devices', default=[]) """The storage devices associated with this resource.""" class SimpleStorageCollection(base.ResourceCollectionBase): """Represents a collection of simple storage associated with system.""" @property def _resource_type(self): return SimpleStorage @property @utils.cache_it def disks_sizes_bytes(self): """Sizes of each Disk in bytes in SimpleStorage collection resource. Returns the list of cached values until it (or its parent resource) is refreshed. """ return sorted(device.capacity_bytes for simpl_stor in self.get_members() for device in simpl_stor.devices if (device.status.state == res_cons.State.ENABLED and device.capacity_bytes is not None)) @property def max_size_bytes(self): """Max size available (in bytes) among all enabled Disk resources. Returns the cached value until it (or its parent resource) is refreshed. """ return utils.max_safe(self.disks_sizes_bytes) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6555147 sushy-5.5.0/sushy/resources/system/storage/0000775000175000017500000000000000000000000021074 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/system/storage/__init__.py0000664000175000017500000000000000000000000023173 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/system/storage/constants.py0000664000175000017500000001421300000000000023463 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # From http://redfish.dmtf.org/schemas/swordfish/v1/Volume.json import enum # FIXME(dtantsur): this is deprecated in favour of InitializeMethod class VolumeInitializeType(enum.Enum): FAST = 'Fast' """The volume is prepared for use quickly, typically by erasing just the beginning and end of the space so that partitioning can be performed.""" SLOW = 'Slow' """The volume is prepared for use slowly, typically by completely erasing the volume.""" # Backward compatibility VOLUME_INIT_TYPE_FAST = VolumeInitializeType.FAST VOLUME_INIT_TYPE_SLOW = VolumeInitializeType.SLOW class VolumeType(enum.Enum): RAW_DEVICE = 'RawDevice' """The volume is a raw physical device without any RAID or other virtualization applied.""" NON_REDUNDANT = 'NonRedundant' """The volume is a non-redundant storage device.""" MIRRORED = 'Mirrored' """The volume is a mirrored device.""" STRIPED_WITH_PARITY = 'StripedWithParity' """The volume is a device which uses parity to retain redundant information.""" SPANNED_MIRRORS = 'SpannedMirrors' """The volume is a spanned set of mirrored devices.""" SPANNED_STRIPES_WITH_PARITY = 'SpannedStripesWithParity' """The volume is a spanned set of devices which uses parity to retain redundant information.""" # Backward compatibility VOLUME_TYPE_RAW_DEVICE = VolumeType.RAW_DEVICE VOLUME_TYPE_NON_REDUNDANT = VolumeType.NON_REDUNDANT VOLUME_TYPE_MIRRORED = VolumeType.MIRRORED VOLUME_TYPE_STRIPED_WITH_PARITY = VolumeType.STRIPED_WITH_PARITY VOLUME_TYPE_SPANNED_MIRRORS = VolumeType.SPANNED_MIRRORS VOLUME_TYPE_SPANNED_STRIPES_WITH_PARITY = \ VolumeType.SPANNED_STRIPES_WITH_PARITY class RAIDType(enum.Enum): RAID0 = 'RAID0' """A placement policy where consecutive logical blocks of data are uniformly distributed across a set of independent storage devices without offering any form of redundancy.""" RAID1 = 'RAID1' """A placement policy where each logical block of data is stored on more than one independent storage device.""" RAID3 = 'RAID3' """A placement policy using parity-based protection where logical bytes of data are uniformly distributed across a set of independent storage devices and where the parity is stored on a dedicated independent storage device.""" RAID4 = 'RAID4' """A placement policy using parity-based protection where logical blocks of data are uniformly distributed across a set of independent storage devices and where the parity is stored on a dedicated independent storage device.""" RAID5 = 'RAID5' """A placement policy using parity-based protection for storing stripes of 'n' logical blocks of data and one logical block of parity across a set of 'n+1' independent storage devices where the parity and data blocks are interleaved across the storage devices.""" RAID6 = 'RAID6' """A placement policy using parity-based protection for storing stripes of 'n' logical blocks of data and two logical blocks of independent parity across a set of 'n+2' independent storage devices where the parity and data blocks are interleaved across the storage devices.""" RAID10 = 'RAID10' """A placement policy that creates a striped device (RAID 0) over a set of mirrored devices (RAID 1).""" RAID01 = 'RAID01' """A data placement policy that creates a mirrored device (RAID 1) over a set of striped devices (RAID 0).""" RAID6TP = 'RAID6TP' """A placement policy that uses parity-based protection for storing stripes of 'n' logical blocks of data and three logical blocks of independent parity across a set of 'n+3' independent storage devices where the parity and data blocks are interleaved across the storage devices.""" RAID1E = 'RAID1E' """A placement policy that uses a form of mirroring implemented over a set of independent storage devices where logical blocks are duplicated on a pair of independent storage devices so that data is uniformly distributed across the storage devices.""" RAID50 = 'RAID50' """A placement policy that uses a RAID 0 stripe set over two or more RAID 5 sets of independent storage devices.""" RAID60 = 'RAID60' """A placement policy that uses a RAID 0 stripe set over two or more RAID 6 sets of independent storage devices.""" RAID00 = 'RAID00' """A placement policy that creates a RAID 0 stripe set over two or more RAID 0 sets.""" RAID10E = 'RAID10E' """A placement policy that uses a RAID 0 stripe set over two or more RAID 10 sets.""" RAID1_TRIPLE = 'RAID1Triple' """A placement policy where each logical block of data is mirrored three times across a set of three independent storage devices.""" RAID10_TRIPLE = 'RAID10Triple' """A placement policy that uses a striped device (RAID 0) over a set of triple mirrored devices (RAID 1Triple).""" NONE = 'None' """A placement policy with no redundancy at the device level.""" # Backward compatibility RAID_TYPE_RAID0 = RAIDType.RAID0 RAID_TYPE_RAID1 = RAIDType.RAID1 RAID_TYPE_RAID3 = RAIDType.RAID3 RAID_TYPE_RAID4 = RAIDType.RAID4 RAID_TYPE_RAID5 = RAIDType.RAID5 RAID_TYPE_RAID6 = RAIDType.RAID6 RAID_TYPE_RAID10 = RAIDType.RAID10 RAID_TYPE_RAID01 = RAIDType.RAID01 RAID_TYPE_RAID6TP = RAIDType.RAID6TP RAID_TYPE_RAID1E = RAIDType.RAID1E RAID_TYPE_RAID50 = RAIDType.RAID50 RAID_TYPE_RAID60 = RAIDType.RAID60 RAID_TYPE_RAID00 = RAIDType.RAID00 RAID_TYPE_RAID10E = RAIDType.RAID10E RAID_TYPE_RAID1Triple = RAIDType.RAID1_TRIPLE RAID_TYPE_RAID10Triple = RAIDType.RAID10_TRIPLE RAID_TYPE_NONE = RAIDType.NONE ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/system/storage/controller.py0000664000175000017500000001370500000000000023637 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is referred from Redfish standard schema. # http://redfish.dmtf.org/schemas/v1/StorageController.v1_6_0.json import logging from sushy.resources import base from sushy.resources import common from sushy.resources import constants as res_cons from sushy.resources import settings from sushy.resources.system.storage import constants from sushy.taskmonitor import TaskMonitor from sushy import utils LOG = logging.getLogger(__name__) class StorageController(base.ResourceBase): """Storage controller""" identity = base.Field('Id', required=True) """The storage controller identity""" name = base.Field('Name', required=True) """The name of the storage controller""" status = common.StatusField('Status') """Describes the status and health of the resource and its children.""" identifiers = common.IdentifiersListField('Identifiers', default=[]) """The Durable names for the storage controller.""" speed_gbps = base.Field('SpeedGbps') """The maximum speed of the storage controller's device interface.""" controller_protocols = base.MappedListField( 'SupportedControllerProtocols', res_cons.Protocol) """The protocols by which this storage controller can be communicated to""" device_protocols = base.MappedListField('SupportedDeviceProtocols', res_cons.Protocol) """The protocols that can be used to communicate with attached devices""" raid_types = base.MappedListField('SupportedRAIDTypes', constants.RAIDType) """The set of RAID types supported by the storage controller.""" _settings = settings.SettingsField() """Future intended state for settings that can't be updated immediately.""" @property @utils.cache_it def pending_settings(self): """Pending Storage Controller settings resource""" return StorageController( self._conn, self._settings.resource_uri, registries=None, redfish_version=self.redfish_version, root=self.root) @property def supported_apply_times(self): """List of supported BIOS update apply times :returns: List of supported update apply time names """ return self._settings._supported_apply_times def update(self, payload, apply_time=None, maint_window_start_time=None, maint_window_duration=None): """Updates writable properties Supports updating properties that require reboot. :param payload: dictionary with properties to update :param apply_time: When to update the attributes. Optional. A :py:class:`sushy.ApplyTime` value. :param maint_window_start_time: The start time of a maintenance window, datetime. Required when updating during maintenance window and default maintenance window not set by the system. :param maint_window_duration: Duration of maintenance time since maintenance window start time in seconds. Required when updating during maintenance window and default maintenance window not set by the system. :returns: TaskMonitor if async task or None """ payload = utils.process_apply_time_input( payload, apply_time, maint_window_start_time, maint_window_duration) # NOTE(vanou): To retrieve current ETag value of @Redfish.Settings # but not update cached pending_settings, because cached property is # only this one and re-cache this is not required self.refresh(force=False) r = self._settings.commit(self._conn, payload) utils.cache_clear(self, force_refresh=False, only_these=['pending_settings']) if r.status_code == 202: return TaskMonitor.from_response( self._conn, r, self._settings.resource_uri, self.redfish_version, self.registries) class ControllerCollection(base.ResourceCollectionBase): @property def _resource_type(self): return StorageController @property @utils.cache_it def summary(self): """Summary of storage controllers :returns: dictionary of controller id-s and their status in format .. code-block:: python {'RAID.Integrated.1-1': {'Health': sushy.Health.OK, 'State': sushy.State.ENABLED}} """ controllers = {} for controller in self.get_members(): controllers[controller.identity] = { 'Health': controller.status.health, 'State': controller.status.state, } return controllers def __init__(self, connector, path, redfish_version=None, registries=None, root=None): """A class representing a ControllerCollection :param connector: A Connector instance :param path: The canonical path to the Controller collection resource :param redfish_version: The version of Redfish. Used to construct the object according to schema of the given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, path, redfish_version=redfish_version, registries=registries, root=root) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/system/storage/drive.py0000664000175000017500000000742500000000000022567 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is referred from Redfish standard schema. # http://redfish.dmtf.org/schemas/v1/Drive.v1_4_0.json import logging from sushy import exceptions from sushy.resources import base from sushy.resources import common from sushy.resources import constants as res_cons from sushy.resources.system.storage import volume from sushy import utils LOG = logging.getLogger(__name__) class Drive(base.ResourceBase): """This class represents a disk drive or other physical storage medium.""" block_size_bytes = base.Field('BlockSizeBytes', adapter=utils.int_or_none) """The size of the smallest addressable unit of this drive in bytes""" capacity_bytes = base.Field('CapacityBytes', adapter=utils.int_or_none) """The size in bytes of this Drive""" identifiers = common.IdentifiersListField('Identifiers', default=[]) """The Durable names for the drive""" identity = base.Field('Id', required=True) """The Drive identity string""" indicator_led = base.MappedField('IndicatorLED', res_cons.IndicatorLED) """Whether the indicator LED is lit or off""" manufacturer = base.Field('Manufacturer') """This is the manufacturer of this drive""" media_type = base.Field('MediaType') """The type of media contained in this drive""" model = base.Field('Model') """This is the model number for the drive""" name = base.Field('Name') """The name of the resource""" part_number = base.Field('PartNumber') """The part number for this drive""" protocol = base.MappedField('Protocol', res_cons.Protocol) """Protocol this drive is using to communicate to the storage controller""" revision = base.Field("Revision") """The firmware/hardware version of the drive.""" serial_number = base.Field('SerialNumber') """The serial number for this drive""" status = common.StatusField('Status') """This type describes the status and health of the drive""" @property @utils.cache_it def volumes(self): """A list of volumes that this drive is part of. Volumes that this drive either wholly or only partially contains. :raises: MissingAttributeError if '@odata.id' field is missing. :returns: A list of `Volume` instances """ paths = utils.get_sub_resource_path_by( self, ["Links", "Volumes"], is_collection=True) return [volume.Volume(self._conn, path, redfish_version=self.redfish_version, registries=self.registries) for path in paths] def set_indicator_led(self, state): """Set IndicatorLED to the given state. :param state: Desired LED state, an IndicatorLED value. :raises: InvalidParameterValueError, if any information passed is invalid. """ try: state = res_cons.IndicatorLED(state).value except ValueError: raise exceptions.InvalidParameterValueError( parameter='state', value=state, valid_values=' ,'.join(i.value for i in res_cons.IndicatorLED)) etag = self._get_etag() data = {'IndicatorLED': state} self._conn.patch(self.path, data=data, etag=etag) self.invalidate() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/system/storage/storage.py0000664000175000017500000001634300000000000023121 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is referred from Redfish standard schema. # http://redfish.dmtf.org/schemas/v1/Storage.v1_4_0.json import logging from sushy.resources import base from sushy.resources import common from sushy.resources import constants as res_cons from sushy.resources.system.storage import constants from sushy.resources.system.storage import controller from sushy.resources.system.storage import drive from sushy.resources.system.storage import volume from sushy import utils LOG = logging.getLogger(__name__) class StorageControllersListField(base.ListField): """The set of storage controllers represented by this resource.""" member_id = base.Field('MemberId', required=True) """Uniquely identifies the member within the collection.""" name = base.Field('Name') """The name of the storage controller""" status = common.StatusField('Status') """Describes the status and health of the resource and its children.""" identifiers = common.IdentifiersListField('Identifiers', default=[]) """The Durable names for the storage controller.""" speed_gbps = base.Field('SpeedGbps') """The maximum speed of the storage controller's device interface.""" controller_protocols = base.MappedListField( 'SupportedControllerProtocols', res_cons.Protocol) """The protocols by which this storage controller can be communicated to""" device_protocols = base.MappedListField('SupportedDeviceProtocols', res_cons.Protocol) """The protocols which the controller can use tocommunicate with devices""" raid_types = base.MappedListField('SupportedRAIDTypes', constants.RAIDType) """The set of RAID types supported by the storage controller.""" class Storage(base.ResourceBase): """This class represents the storage subsystem resources. A storage subsystem represents a set of storage controllers (physical or virtual) and the resources such as drives and volumes that can be accessed from that subsystem. """ identity = base.Field('Id', required=True) """The Storage identity string""" name = base.Field('Name') """The name of the resource""" drives_identities = base.Field('Drives', adapter=utils.get_members_identities) """A tuple with the drive identities""" status = common.StatusField('Status') """Describes the status and health of the resource and its children.""" def get_drive(self, drive_identity): """Given the drive identity return a ``Drive`` object :param drive_identity: The identity of the ``Drive`` :returns: The ``Drive`` object :raises: ResourceNotFoundError """ return drive.Drive(self._conn, drive_identity, redfish_version=self.redfish_version, registries=self.registries, root=self.root) @property @utils.cache_it def drives(self): """Return a list of `Drive` objects present in the storage resource. It is set once when the first time it is queried. On subsequent invocations, it returns a cached list of `Drives` objects until it is marked stale. :returns: A list of `Drive` objects :raises: ResourceNotFoundError """ return [self.get_drive(id_) for id_ in self.drives_identities] @property @utils.cache_it def drives_sizes_bytes(self): """Sizes of all Drives in bytes in Storage resource. Returns the list of cached values until it (or its parent resource) is refreshed. """ return sorted(drv.capacity_bytes for drv in self.drives) @property def drives_max_size_bytes(self): """Max size available in bytes among all Drives of this collection.""" return utils.max_safe(self.drives_sizes_bytes) @property @utils.cache_it def volumes(self): """Property to reference `VolumeCollection` instance It is set once when the first time it is queried. On refresh, this property is marked as stale (greedy-refresh not done at that point). Here only the actual refresh of the sub-resource happens, if resource is stale. """ return volume.VolumeCollection( self._conn, utils.get_sub_resource_path_by(self, 'Volumes'), redfish_version=self.redfish_version, root=self.root) storage_controllers = StorageControllersListField('StorageControllers', default=[]) """The storage devices associated with this resource. Deprecated since Redfish v1.13 to allow storage controllers be their own resource. Use `controllers` where available. """ @property @utils.cache_it def controllers(self): """The storage controllers allocated to this storage subsystem. Replaces `storage_controllers` since Redfish v1.9 to allow storage controllers be their own resource. """ return controller.ControllerCollection( self._conn, utils.get_sub_resource_path_by(self, "Controllers"), redfish_version=self.redfish_version, registries=self.registries, root=self.root) class StorageCollection(base.ResourceCollectionBase): """This class represents the collection of Storage resources""" @property def _resource_type(self): return Storage @property @utils.cache_it def drives_sizes_bytes(self): """Sizes of each Drive in bytes in Storage collection resource. Returns the list of cached values until it (or its parent resource) is refreshed. """ return sorted(drive_size for storage_ in self.get_members() for drive_size in storage_.drives_sizes_bytes) @property def max_drive_size_bytes(self): """Max size available (in bytes) among all Drive resources. Returns the cached value until it (or its parent resource) is refreshed. """ return utils.max_safe(self.drives_sizes_bytes) @property @utils.cache_it def volumes_sizes_bytes(self): """Sizes of each Volume in bytes in Storage collection resource. Returns the list of cached values until it (or its parent resource) is refreshed. """ return sorted(volume_size for storage_ in self.get_members() for volume_size in storage_.volumes.volumes_sizes_bytes) @property def max_volume_size_bytes(self): """Max size available (in bytes) among all Volume resources. Returns the cached value until it (or its parent resource) is refreshed. """ return utils.max_safe(self.volumes_sizes_bytes) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/system/storage/volume.py0000664000175000017500000002254700000000000022767 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is referred from Redfish standard schema. # http://redfish.dmtf.org/schemas/v1/Volume.v1_0_3.json import logging from sushy import exceptions from sushy.resources import base from sushy.resources import common from sushy.resources import constants as res_cons from sushy.resources.system.storage import constants as store_cons from sushy.taskmonitor import TaskMonitor from sushy import utils LOG = logging.getLogger(__name__) _OAT_PROP = '@Redfish.OperationApplyTime' class ActionsField(base.CompositeField): initialize = common.InitializeActionField('#Volume.Initialize') class Volume(base.ResourceBase): """This class adds the Storage Volume resource""" identity = base.Field('Id', required=True) """The Volume identity string""" name = base.Field('Name') """The name of the resource""" capacity_bytes = base.Field('CapacityBytes', adapter=utils.int_or_none) """The size in bytes of this Volume.""" volume_type = base.MappedField('VolumeType', store_cons.VolumeType) """The type of this volume.""" raid_type = base.MappedField('RAIDType', store_cons.RAIDType) """The RAID type of this volume.""" encrypted = base.Field('Encrypted', adapter=bool) """Is this Volume encrypted.""" identifiers = common.IdentifiersListField('Identifiers', default=[]) """The Durable names for the volume.""" block_size_bytes = base.Field('BlockSizeBytes', adapter=int) """The size of the smallest addressable unit of this volume in bytes.""" operation_apply_time_support = common.OperationApplyTimeSupportField() """Indicates if a client is allowed to request for a specific apply time of a create, delete, or action operation of a given resource""" _actions = ActionsField('Actions') def _get_initialize_action_element(self): initialize_action = self._actions.initialize if not initialize_action: raise exceptions.MissingActionError(action='#Volume.Initialize', resource=self._path) return initialize_action def get_allowed_initialize_volume_values(self): """Get the allowed values for initializing the volume. :returns: A set with the allowed values. """ action = self._get_initialize_action_element() if not action.allowed_values: LOG.warning('Could not figure out the allowed values for the ' 'initialize volume action for Volume %s', self.identity) return set(store_cons.VolumeInitializeType) return {v for v in store_cons.VolumeInitializeType if v.value in action.allowed_values} def _initialize(self, value=store_cons.VolumeInitializeType.FAST, apply_time=None, timeout=500): valid_values = self.get_allowed_initialize_volume_values() value = store_cons.VolumeInitializeType(value) if value not in valid_values: raise exceptions.InvalidParameterValueError( parameter='value', value=value, valid_values=valid_values) payload = {'InitializeType': value.value} blocking = False if apply_time: payload[_OAT_PROP] = res_cons.ApplyTime(apply_time).value if (payload and payload.get(_OAT_PROP) == res_cons.ApplyTime.IMMEDIATE.value): blocking = True target_uri = self._get_initialize_action_element().target_uri r = self._conn.post(target_uri, data=payload, blocking=blocking, timeout=timeout) return r, target_uri def initialize(self, value=store_cons.VolumeInitializeType.FAST, apply_time=None, timeout=500): """Initialize the volume. :param value: The InitializeType value. :param apply_time: When to update the attributes. Optional. An :py:class:`sushy.ApplyTime` value. :param timeout: Max time in seconds to wait for blocking async call. :raises: InvalidParameterValueError, if the target value is not allowed. :raises: ConnectionError :raises: HTTPError :returns: TaskMonitor if async task or None if successful init """ r, target_uri = self._initialize(value, apply_time, timeout) if r.status_code == 202: return TaskMonitor.from_response( self._conn, r, target_uri, self.redfish_version, self.registries) def _delete(self, payload=None, apply_time=None, timeout=500): blocking = False if apply_time: if payload is None: payload = {} payload[_OAT_PROP] = res_cons.ApplyTime(apply_time).value if (payload and payload.get(_OAT_PROP) == res_cons.ApplyTime.IMMEDIATE.value): blocking = True try: r = self._conn.delete(self._path, data=payload, blocking=blocking, timeout=timeout) except exceptions.ServerSideError as exc: if (_OAT_PROP in str(exc.message) and 'SYS029' in str(exc.message)): LOG.debug('Retry volume delete without %(prop)s for %(path)s ' 'because got error: %(err)s', {'prop': _OAT_PROP, 'path': self._path, 'err': exc}) payload.pop(_OAT_PROP) r = self._conn.delete(self._path, data=payload, blocking=blocking, timeout=timeout) else: raise exc return r def delete(self, payload=None, apply_time=None, timeout=500): """Delete the volume. :param payload: May contain @Redfish.OperationApplyTime property :param apply_time: When to update the attributes. Optional. An :py:class:`sushy.ApplyTime` value. :param timeout: Max time in seconds to wait for blocking async call. :raises: ConnectionError :raises: HTTPError :returns: TaskMonitor if async task or None if successful deletion """ r = self._delete(payload, apply_time, timeout) if r.status_code == 202: return TaskMonitor.from_response( self._conn, r, self._path, self.redfish_version, self.registries) class VolumeCollection(base.ResourceCollectionBase): """This class represents the Storage Volume collection""" @property def _resource_type(self): return Volume @property @utils.cache_it def volumes_sizes_bytes(self): """Sizes of all Volumes in bytes in VolumeCollection resource. Returns the list of cached values until it (or its parent resource) is refreshed. """ return sorted(vol.capacity_bytes for vol in self.get_members()) @property def max_volume_size_bytes(self): """Max size available (in bytes) among all Volume resources. Returns the cached value until it (or its parent resource) is refreshed. """ return utils.max_safe(self.volumes_sizes_bytes) # NOTE(etingof): for backward compatibility max_size_bytes = max_volume_size_bytes operation_apply_time_support = common.OperationApplyTimeSupportField() """Indicates if a client is allowed to request for a specific apply time of a create, delete, or action operation of a given resource""" def _create(self, payload, apply_time=None, timeout=500): blocking = False if apply_time: if payload is None: payload = {} payload[_OAT_PROP] = res_cons.ApplyTime(apply_time).value if (payload and payload.get(_OAT_PROP) == res_cons.ApplyTime.IMMEDIATE.value): blocking = True r = self._conn.post(self._path, data=payload, blocking=blocking, timeout=timeout) location = r.headers.get('Location') return r, location def create(self, payload, apply_time=None, timeout=500): """Create a volume. :param payload: The payload representing the new volume to create. :param apply_time: When to update the attributes. Optional. An :py:class:`sushy.ApplyTime` value. :param timeout: Max time in seconds to wait for blocking async call. :raises: ConnectionError :raises: HTTPError :returns: Newly created Volume resource or TaskMonitor if async task """ r, location = self._create(payload, apply_time, timeout) if r.status_code == 201: if location: self.refresh() return self.get_member(location) elif r.status_code == 202: return TaskMonitor.from_response( self._conn, r, self._path, self.redfish_version, self.registries) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/system/system.py0000664000175000017500000005744200000000000021342 0ustar00zuulzuul00000000000000# Copyright 2017 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. # This is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/v1/ComputerSystem.v1_10_0.json import collections import logging from dateutil import parser from sushy import exceptions from sushy.resources import base from sushy.resources.chassis import chassis from sushy.resources import common from sushy.resources import constants as res_cons from sushy.resources.manager import manager from sushy.resources.manager import virtual_media from sushy.resources import settings from sushy.resources.system import bios from sushy.resources.system import constants as sys_cons from sushy.resources.system import ethernet_interface from sushy.resources.system import processor from sushy.resources.system import secure_boot from sushy.resources.system import simple_storage as sys_simple_storage from sushy.resources.system.storage import storage as sys_storage from sushy import utils LOG = logging.getLogger(__name__) class ActionsField(base.CompositeField): reset = common.ResetActionField('#ComputerSystem.Reset') class BootField(base.CompositeField): allowed_values = base.Field( 'BootSourceOverrideTarget@Redfish.AllowableValues', adapter=list) enabled = base.MappedField('BootSourceOverrideEnabled', sys_cons.BootSourceOverrideEnabled) mode = base.MappedField('BootSourceOverrideMode', sys_cons.BootSourceOverrideMode) target = base.MappedField('BootSourceOverrideTarget', sys_cons.BootSource) http_boot_uri = base.Field('HttpBootUri') class BootProgressField(base.CompositeField): last_boot_seconds_count = base.Field('LastBootTimeSeconds', adapter=utils.int_or_none) """The number of seconds the last boot took to reach OSRunning.""" last_state = base.MappedField('LastState', sys_cons.BootProgressStates) """The last recorded boot progress states.""" last_state_updated_at = base.Field('LastStateTime', adapter=parser.parse) """The date-time value when the last state field was updated.""" oem_last_state = base.Field('OemLastState') """The OEM last state time to describe OEM specific state information.""" class MemorySummaryField(base.CompositeField): health = base.Field(['Status', 'HealthRollup']) """The overall health state of memory. This signifies health state of memory along with its dependent resources. """ size_gib = base.Field('TotalSystemMemoryGiB', adapter=utils.int_or_none) """The size of memory of the system in GiB. This signifies the total installed, operating system-accessible memory (RAM), measured in GiB. """ class System(base.ResourceBase): asset_tag = base.Field('AssetTag') """The system asset tag""" bios_version = base.Field('BiosVersion') """The system BIOS version""" boot = BootField('Boot', required=True) """A dictionary containing the current boot device, frequency and mode""" description = base.Field('Description') """The system description""" hostname = base.Field('HostName') """The system hostname""" identity = base.Field('Id', required=True) """The system identity string""" indicator_led = base.MappedField('IndicatorLED', res_cons.IndicatorLED) """Whether the indicator LED is lit or off""" manufacturer = base.Field('Manufacturer') """The system manufacturer""" name = base.Field('Name') """The system name""" part_number = base.Field('PartNumber') """The system part number""" power_state = base.MappedField('PowerState', res_cons.PowerState) """The system power state""" serial_number = base.Field('SerialNumber') """The system serial number""" sku = base.Field('SKU') """The system stock-keeping unit""" status = common.StatusField('Status') """The system status""" system_type = base.MappedField('SystemType', sys_cons.SystemType) """The system type""" uuid = base.Field('UUID') """The system UUID""" memory_summary = MemorySummaryField('MemorySummary') """The summary info of memory of the system in general detail""" maintenance_window = settings.MaintenanceWindowField( '@Redfish.MaintenanceWindow') """Indicates if a given resource has a maintenance window assignment for applying settings or operations""" _settings = settings.SettingsField() """Settings Resource is used to represent the future intended state of a Resource Ref: http://redfish.dmtf.org/schemas/DSP0266_1.7.0.html#settings-resource """ _actions = ActionsField('Actions', required=True) boot_progress = BootProgressField('BootProgress') """The last updated boot progress indicator""" def __init__(self, connector, identity, redfish_version=None, registries=None, root=None): """A class representing a ComputerSystem :param connector: A Connector instance :param identity: The identity of the System resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of the given version. :param registries: Dict of registries to be used in any resource that needs registries to parse messages. :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, identity, redfish_version=redfish_version, registries=registries, root=root) def _get_reset_action_element(self): reset_action = self._actions.reset # TODO(dtantsur): make this check also declarative? if not reset_action: raise exceptions.MissingActionError(action='#ComputerSystem.Reset', resource=self._path) return reset_action def get_allowed_reset_system_values(self): """Get the allowed values for resetting the system. :returns: A set with the allowed values. """ reset_action = self._get_reset_action_element() if not reset_action.allowed_values: LOG.warning('Could not figure out the allowed values for the ' 'reset system action for System %s', self.identity) return set(res_cons.ResetType) return {v for v in res_cons.ResetType if v.value in reset_action.allowed_values} def reset_system(self, value): """Reset the system. :param value: The target value. :raises: InvalidParameterValueError, if the target value is not allowed. """ valid_resets = self.get_allowed_reset_system_values() if value not in valid_resets: raise exceptions.InvalidParameterValueError( parameter='value', value=value, valid_values=valid_resets) value = res_cons.ResetType(value).value target_uri = self._get_reset_action_element().target_uri # TODO(lucasagomes): Check the return code and response body ? # Probably we should call refresh() as well. self._conn.post(target_uri, data={'ResetType': value}) def get_allowed_system_boot_source_values(self): """Get the allowed values for changing the boot source. :returns: A set with the allowed values. """ if not self.boot.allowed_values: LOG.warning('Could not figure out the allowed values for ' 'configuring the boot source for System %s', self.identity) return set(sys_cons.BootSource) return {v for v in sys_cons.BootSource if v.value in self.boot.allowed_values} def set_system_boot_options(self, target=None, enabled=None, mode=None, http_boot_uri=None): """Set boot source and/or boot frequency and/or boot mode. Set the boot source and/or boot frequency and/or boot mode to use on next reboot of the System. :param target: The target boot source, a :py:class:`sushy.BootSource` value. Optional. :param enabled: How long the override is enabled, a :py:class:`sushy.BootSourceOverrideEnabled` value. Optional. :param mode: The boot mode, a :py:class:`sushy.BootSourceOverrideMode` value. Optional. :param http_boot_uri: The requested HTTP Boot URI to transmit to the BMC. Only valid when BootSourceOverrideTarget is set to UefiHTTP, when utilizing the ``target`` parameter. If no value is supplied, and the target is set to UefiHTTP, then an empty value will be sent to the BMC to remove any prior setting, allowing the host to load configuration from DHCP. If not explicitly set, any value will be removed from a BMC when UefiHttp boot is not engaged. :raises: InvalidParameterValueError, if any information passed is invalid. """ data = collections.defaultdict(dict) settings_data = collections.defaultdict(dict) if self._settings and self._settings.resource_uri: settings_resp = self._conn.get(self._settings.resource_uri) settings_boot_section = settings_resp.json().get('Boot', {}) else: settings_resp = None settings_boot_section = {} if target is not None: valid_targets = self.get_allowed_system_boot_source_values() if target not in valid_targets: raise exceptions.InvalidParameterValueError( parameter='target', value=target, valid_values=valid_targets) target = sys_cons.BootSource(target) # NOTE(janders) on SuperMicro X11 and X12 machines, virtual media # is presented as an "USB CD" drive as opposed to a CD drive. Both # are present in the list of boot devices, however only selecting # UsbCd as the boot source results in a successful boot from # vMedia. If "CD" is selected, boot fails even if vMedia is # inserted. This code detects a case where a SuperMicro machine is # about to attempt boot from CD and overrides the boot device to # UsbCd instead which makes boot from vMedia work as expected. if (self.manufacturer and self.manufacturer.lower() == 'supermicro' and target == sys_cons.BootSource.CD and sys_cons.BootSource.USB_CD.value in self.boot.allowed_values): LOG.debug('Boot from vMedia was requested on a SuperMicro' 'machine. Overriding boot device from %s to %s.', target, sys_cons.BootSource.USB_CD) target = sys_cons.BootSource.USB_CD if (settings_resp and "BootSourceOverrideTarget" in settings_boot_section): settings_data['Boot']['BootSourceOverrideTarget'] = \ target.value else: data['Boot']['BootSourceOverrideTarget'] = target.value if enabled is not None: try: fishy_freq = sys_cons.BootSourceOverrideEnabled(enabled).value except ValueError: raise exceptions.InvalidParameterValueError( parameter='enabled', value=enabled, valid_values=list(sys_cons.BootSourceOverrideEnabled)) if (settings_resp and "BootSourceOverrideEnabled" in settings_boot_section): settings_data['Boot']['BootSourceOverrideEnabled'] = fishy_freq else: data['Boot']['BootSourceOverrideEnabled'] = fishy_freq if mode is not None: try: fishy_mode = sys_cons.BootSourceOverrideMode(mode).value except ValueError: raise exceptions.InvalidParameterValueError( parameter='mode', value=mode, valid_values=list(sys_cons.BootSourceOverrideMode)) if (settings_resp and "BootSourceOverrideMode" in settings_boot_section): settings_data['Boot']['BootSourceOverrideMode'] = fishy_mode else: data['Boot']['BootSourceOverrideMode'] = fishy_mode if target == sys_cons.BootSource.UEFI_HTTP: # The http_boot_uri value *can* be set independently of the # target, but the BMC will just ignore it unless the target # is set. So we should only, and explicitly set it when we've # been requested to boot from UefiHTTP. if not http_boot_uri: # This should clear out any old entries, as no URI translates # to the intent of "use whatever the dhcp server says". http_boot_uri = None if (settings_resp and "HttpBootUri" in settings_boot_section): settings_data['Boot']['HttpBootUri'] = http_boot_uri else: data['Boot']['HttpBootUri'] = http_boot_uri elif not http_boot_uri: # We're not doing boot from URL, we should cleanup any setting # which may be from a prior step/call. if settings_boot_section.get('HttpBootUri'): # If the setting is present, and has any value, unset it. data['Boot']['HttpBootUri'] = None # TODO(lucasagomes): Check the return code and response body ? # Probably we should call refresh() as well. if settings_data.get('Boot'): etag = settings_resp.headers.get('ETag') path = self._settings.resource_uri self._conn.patch(path, data=settings_data, etag=etag) if data.get('Boot'): etag = self._get_etag() path = self.path self._conn.patch(path, data=data, etag=etag) # TODO(etingof): we should remove this method, eventually def set_system_boot_source( self, target, enabled=sys_cons.BootSourceOverrideEnabled.ONCE, mode=None): """Set boot source and/or boot frequency and/or boot mode. Set the boot source and/or boot frequency and/or boot mode to use on next reboot of the System. This method is obsoleted by `set_system_boot_options`. :param target: The target boot source, a :py:class:`sushy.BootSource` value. :param enabled: The frequency, whether to set it for the next a :py:class:`sushy.BootSourceOverrideEnabled` value. Default is `ONCE`. :param mode: The boot mode, a :py:class:`sushy.BootSourceOverrideMode` value. :raises: InvalidParameterValueError, if any information passed is invalid. """ self.set_system_boot_options(target, enabled, mode) def set_indicator_led(self, state): """Set IndicatorLED to the given state. :param state: Desired LED state, an IndicatorLED value. :raises: InvalidParameterValueError, if any information passed is invalid. """ try: state = res_cons.IndicatorLED(state).value except ValueError: raise exceptions.InvalidParameterValueError( parameter='state', value=state, valid_values=' ,'.join(i.value for i in res_cons.IndicatorLED)) etag = self._get_etag() data = {'IndicatorLED': state} self._conn.patch(self.path, data=data, etag=etag) self.invalidate() def _get_processor_collection_path(self): """Helper function to find the ProcessorCollection path""" return utils.get_sub_resource_path_by(self, 'Processors') @property @utils.cache_it def processors(self): """Property to reference `ProcessorCollection` instance It is set once when the first time it is queried. On refresh, this property is marked as stale (greedy-refresh not done). Here the actual refresh of the sub-resource happens, if stale. """ return processor.ProcessorCollection( self._conn, self._get_processor_collection_path(), redfish_version=self.redfish_version, registries=self.registries, root=self.root) @property @utils.cache_it def ethernet_interfaces(self): """Property to reference `EthernetInterfaceCollection` instance It is set once when the first time it is queried. On refresh, this property is marked as stale (greedy-refresh not done). Here the actual refresh of the sub-resource happens, if stale. """ return ethernet_interface.EthernetInterfaceCollection( self._conn, utils.get_sub_resource_path_by(self, "EthernetInterfaces"), redfish_version=self.redfish_version, registries=self.registries, root=self.root) @property @utils.cache_it def bios(self): """Property to reference `Bios` instance It is set once when the first time it is queried. On refresh, this property is marked as stale (greedy-refresh not done). Here the actual refresh of the sub-resource happens, if stale. """ return bios.Bios( self._conn, utils.get_sub_resource_path_by(self, 'Bios'), redfish_version=self.redfish_version, registries=self.registries, root=self.root) @property @utils.cache_it def simple_storage(self): """A collection of simple storage associated with system. This returns a reference to `SimpleStorageCollection` instance. SimpleStorage represents the properties of a storage controller and its directly-attached devices. It is set once when the first time it is queried. On refresh, this property is marked as stale (greedy-refresh not done). Here the actual refresh of the sub-resource happens, if stale. :raises: MissingAttributeError if 'SimpleStorage/@odata.id' field is missing. :returns: `SimpleStorageCollection` instance """ return sys_simple_storage.SimpleStorageCollection( self._conn, utils.get_sub_resource_path_by(self, "SimpleStorage"), redfish_version=self.redfish_version, registries=self.registries, root=self.root) @property @utils.cache_it def storage(self): """A collection of storage subsystems associated with system. This returns a reference to `StorageCollection` instance. A storage subsystem represents a set of storage controllers (physical or virtual) and the resources such as drives and volumes that can be accessed from that subsystem. It is set once when the first time it is queried. On refresh, this property is marked as stale (greedy-refresh not done). Here the actual refresh of the sub-resource happens, if stale. :raises: MissingAttributeError if 'Storage/@odata.id' field is missing. :returns: `StorageCollection` instance """ return sys_storage.StorageCollection( self._conn, utils.get_sub_resource_path_by(self, "Storage"), redfish_version=self.redfish_version, registries=self.registries, root=self.root) @property @utils.cache_it def managers(self): """A list of managers for this system. Returns a list of `Manager` objects representing the managers that manage this system. :raises: MissingAttributeError if '@odata.id' field is missing. :returns: A list of `Manager` instances """ try: paths = utils.get_sub_resource_path_by( self, ["Links", "ManagedBy"], is_collection=True) except exceptions.MissingAttributeError as exc_orig: LOG.warning('Unable to find ManagedBy attribute for System %s, ' 'retrying with Managers attribute', self.identity) try: paths = utils.get_sub_resource_path_by( self, ["Links", "Managers"], is_collection=True) except exceptions.MissingAttributeError: LOG.error('Both ManagedBy and Managers attributes missing for ' 'System %s, aborting', self.identity) # NOTE(janders) last_error may record only Managers and not # ManagedBy MissingAttributeError with this approach raise exc_orig return [manager.Manager(self._conn, path, redfish_version=self.redfish_version, registries=self.registries, root=self.root) for path in paths] @property @utils.cache_it def chassis(self): """A list of chassis where this system resides. Returns a list of `Chassis` objects representing the chassis or cabinets where this system is mounted. :raises: MissingAttributeError if '@odata.id' field is missing. :returns: A list of `Chassis` instances """ paths = utils.get_sub_resource_path_by( self, ["Links", "Chassis"], is_collection=True) return [chassis.Chassis(self._conn, path, redfish_version=self.redfish_version, registries=self.registries, root=self.root) for path in paths] @property @utils.cache_it def secure_boot(self): """Property to reference `SecureBoot` instance It is set once when the first time it is queried. On refresh, this property is marked as stale (greedy-refresh not done). Here the actual refresh of the sub-resource happens, if stale. """ return secure_boot.SecureBoot( self._conn, utils.get_sub_resource_path_by(self, 'SecureBoot'), redfish_version=self.redfish_version, registries=self.registries, root=self.root) @property @utils.cache_it def virtual_media(self): """Property to reference `VirtualMedia` instance :returns: A `VirtualMediaCollection` instance. """ return virtual_media.VirtualMediaCollection( self._conn, utils.get_sub_resource_path_by(self, 'VirtualMedia'), redfish_version=self.redfish_version, registries=self.registries, root=self.root) class SystemCollection(base.ResourceCollectionBase): @property def _resource_type(self): return System def __init__(self, connector, path, redfish_version=None, registries=None, root=None): """A class representing a ComputerSystemCollection :param connector: A Connector instance :param path: The canonical path to the System collection resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of the given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, path, redfish_version=redfish_version, registries=registries, root=root) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6595147 sushy-5.5.0/sushy/resources/taskservice/0000775000175000017500000000000000000000000020427 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/taskservice/__init__.py0000664000175000017500000000000000000000000022526 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/taskservice/constants.py0000664000175000017500000000272700000000000023025 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # Values come from the Redfish UpdateService json-schema. # https://redfish.dmtf.org/schemas/v1/TaskService.v1_1_5.json#/definitions/OverWritePolicy import enum class TaskState(enum.Enum): """Task state related constants.""" CANCELLED = 'Cancelled' CANCELLING = 'Cancelling' COMPLETED = 'Completed' EXCEPTION = 'Exception' INTERRUPTED = 'Interrupted' NEW = 'New' PENDING = 'Pending' RUNNING = 'Running' SERVICE = 'Service' STARTING = 'Starting' STOPPING = 'Stopping' SUSPENDED = 'Suspended' # Deprecated in 1.2.0 KILLED = 'Killed' class OverWritePolicy(enum.Enum): """Overwrite Policy constants""" MANUAL = 'Manual' """Completed tasks are not automatically overwritten.""" OLDEST = 'Oldest' """Oldest completed tasks are overwritten.""" # Backward compatibility OVERWRITE_POLICY_MANUAL = OverWritePolicy.MANUAL OVERWRITE_POLICY_OLDEST = OverWritePolicy.OLDEST ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/taskservice/task.py0000664000175000017500000000711000000000000021742 0ustar00zuulzuul00000000000000# Copyright (c) 2020 Dell, Inc. or its subsidiaries # # 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 is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/Task.v1_4_3.json from http import client as http_client import logging from sushy.resources import base from sushy.resources import constants as res_cons from sushy.resources.registry import message_registry from sushy.resources.taskservice import constants as ts_cons from sushy import utils LOG = logging.getLogger(__name__) class Task(base.ResourceBase): identity = base.Field('Id', required=True) """The Task identity""" name = base.Field('Name', required=True) """The Task name""" description = base.Field('Description') """The Task description""" task_monitor = base.Field('TaskMonitor') """An opaque URL that the client can use to monitor an asynchronous operation""" start_time = base.Field('StartTime') """Start time of the Task""" end_time = base.Field('EndTime') """End time of the Task""" percent_complete = base.Field('PercentComplete', adapter=utils.int_or_none) """Percentage complete of the Task""" task_state = base.MappedField('TaskState', ts_cons.TaskState) """The Task state""" task_status = base.MappedField('TaskStatus', res_cons.Health) """The Task status""" messages = base.MessageListField("Messages") """List of :class:`.MessageListField` with messages from the Task""" def __init__(self, connector, identity, redfish_version=None, registries=None, json_doc=None, root=None): """A class representing a Task :param connector: A Connector instance :param identity: The identity of the task :param redfish_version: The version of RedFish. Used to construct the object according to schema of the given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param field_data: the data to use populating the fields :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, identity, redfish_version, registries, json_doc=json_doc, root=root) @property def is_processing(self): """Indicates if the Task is processing""" return self.status_code == http_client.ACCEPTED def parse_messages(self): """Parses the messages""" for m in self.messages: message_registry.parse_message(self._registries, m) class TaskCollection(base.ResourceCollectionBase): @property def _resource_type(self): return Task @property @utils.cache_it def summary(self): """Summary of task ids and corresponding state :returns: dictionary in the format {'jid_123456789': sushy.TaskState.NEW, 'jid_123454321': sushy.TaskState.RUNNING} """ task_dict = {} for task in self.get_members(): task_dict[task.identity] = task.task_state return task_dict ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/taskservice/taskservice.py0000664000175000017500000000557200000000000023335 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # This is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/v1/TaskService.v1_1_5.json import logging from sushy.resources import base from sushy.resources import common from sushy.resources.taskservice import constants as ts_cons from sushy.resources.taskservice import task from sushy import utils LOG = logging.getLogger(__name__) class TaskService(base.ResourceBase): identity = base.Field('Id', required=True) """The task service identity""" name = base.Field('Name', required=True) """The task service name""" service_enabled = base.Field('ServiceEnabled') """The status of whether this service is enabled""" status = common.StatusField('Status') """The status of the task service""" overwrite_policy = base.MappedField( 'CompletedTaskOverWritePolicy', ts_cons.OverWritePolicy) """The overwrite policy for completed tasks""" event_on_task_state_change = base.Field( 'LifeCycleEventOnTaskStateChange', adapter=bool) """Whether a task state change sends an event""" def __init__(self, connector, identity, redfish_version=None, registries=None, root=None): """A class representing a TaskService :param connector: A Connector instance :param identity: The identity of the TaskService resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of given version :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, identity, redfish_version=redfish_version, registries=registries, root=root) @property @utils.cache_it def tasks(self): """Property to reference `TaskCollection` instance It is set once when the first time it is queried. On refresh, this property is marked as stale (greedy-refresh not done). Here the actual refresh of the sub-resource happens, if stale. """ return task.TaskCollection( self._conn, utils.get_sub_resource_path_by(self, 'Tasks'), redfish_version=self.redfish_version, registries=self.registries, root=self.root) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6595147 sushy-5.5.0/sushy/resources/updateservice/0000775000175000017500000000000000000000000020747 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/updateservice/__init__.py0000664000175000017500000000000000000000000023046 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/updateservice/constants.py0000664000175000017500000000362100000000000023337 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # Values come from the Redfish UpdateService json-schema. # https://redfish.dmtf.org/schemas/UpdateService.v1_2_2.json#/definitions/TransferProtocolType import enum class UpdateTransferProtocolType(enum.Enum): """Transfer Protocol Type constants""" CIFS = 'CIFS' """Common Internet File System (CIFS).""" FTP = 'FTP' """File Transfer Protocol (FTP).""" SFTP = 'SFTP' """Secure File Transfer Protocol (SFTP).""" HTTP = 'HTTP' """Hypertext Transfer Protocol (HTTP).""" HTTPS = 'HTTPS' """Hypertext Transfer Protocol Secure (HTTPS).""" SCP = 'SCP' """Secure Copy Protocol (SCP).""" TFTP = 'TFTP' """Trivial File Transfer Protocol (TFTP).""" OEM = 'OEM' """A manufacturer-defined protocol.""" NFS = 'NFS' """Network File System (NFS).""" # Deprecated alias: NSF = 'NFS' """Network File System (NFS).""" UPDATE_PROTOCOL_CIFS = UpdateTransferProtocolType.CIFS UPDATE_PROTOCOL_FTP = UpdateTransferProtocolType.FTP UPDATE_PROTOCOL_SFTP = UpdateTransferProtocolType.SFTP UPDATE_PROTOCOL_HTTP = UpdateTransferProtocolType.HTTP UPDATE_PROTOCOL_HTTPS = UpdateTransferProtocolType.HTTPS UPDATE_PROTOCOL_SCP = UpdateTransferProtocolType.SCP UPDATE_PROTOCOL_TFTP = UpdateTransferProtocolType.TFTP UPDATE_PROTOCOL_OEM = UpdateTransferProtocolType.OEM UPDATE_PROTOCOL_NFS = UpdateTransferProtocolType.NFS ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/updateservice/softwareinventory.py0000664000175000017500000000733700000000000025143 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # This is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/SoftwareInventory.v1_2_0.json import logging from sushy.resources import base from sushy.resources import common LOG = logging.getLogger(__name__) class SoftwareInventory(base.ResourceBase): identity = base.Field('Id', required=True) """The software inventory identity""" lowest_supported_version = base.Field('LowestSupportedVersion') """The lowest supported version of the software""" manufacturer = base.Field('Manufacturer') """The manufacturer of the software""" name = base.Field('Name', required=True) """The software inventory name""" release_date = base.Field('ReleaseDate') """Release date of the software""" related_item = base.Field('RelatedItem') """The ID(s) of the resources associated with the software inventory item""" status = common.StatusField('Status') """The status of the software inventory""" software_id = base.Field('SoftwareId') """The identity of the software""" uefi_device_paths = base.Field('UefiDevicePaths') """Represents the UEFI Device Path(s)""" updateable = base.Field('Updateable') """Indicates whether this software can be updated by the update service""" version = base.Field('Version') """The version of the software""" def __init__(self, connector, identity, redfish_version=None, registries=None, root=None): """A class representing a SoftwareInventory :param connector: A Connector instance :param identity: The identity of the SoftwareInventory resources :param redfish_version: The version of RedFish. Used to construct the object according to schema of given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, identity, redfish_version=redfish_version, registries=registries, root=root) class SoftwareInventoryCollection(base.ResourceCollectionBase): name = base.Field('Name') """The software inventory collection name""" description = base.Field('Description') """The software inventory collection description""" @property def _resource_type(self): return SoftwareInventory def __init__(self, connector, identity, redfish_version=None, registries=None, root=None): """A class representing a SoftwareInventoryCollection :param connector: A Connector instance :param identity: The identity of SoftwareInventory resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, identity, redfish_version=redfish_version, registries=registries, root=root) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/resources/updateservice/updateservice.py0000664000175000017500000001663700000000000024201 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # This is referred from Redfish standard schema. # https://redfish.dmtf.org/schemas/UpdateService.v1_2_2.json import logging from sushy import exceptions from sushy.resources import base from sushy.resources import common from sushy.resources.updateservice import constants as up_cons from sushy.resources.updateservice import softwareinventory from sushy import taskmonitor from sushy import utils LOG = logging.getLogger(__name__) class ActionsField(base.CompositeField): simple_update = common.ActionField('#UpdateService.SimpleUpdate') class UpdateService(base.ResourceBase): identity = base.Field('Id', required=True) """The update service identity""" http_push_uri = base.Field('HttpPushUri') """The URI used to perform an HTTP or HTTPS push update to the Update Service""" http_push_uri_targets = base.Field('HttpPushUriTargets') """The array of URIs indicating the target for applying the""" + \ """update image""" http_push_uri_targets_busy = base.Field('HttpPushUriTargetsBusy') """This represents if the HttpPushUriTargets property is reserved""" + \ """by anyclient""" name = base.Field('Name', required=True) """The update service name""" service_enabled = base.Field('ServiceEnabled') """The status of whether this service is enabled""" status = common.StatusField('Status') """The status of the update service""" _actions = ActionsField('Actions', required=True) _firmware_inventory_path = base.Field(['FirmwareInventory', '@odata.id']) _software_inventory_path = base.Field(['SoftwareInventory', '@odata.id']) def __init__(self, connector, identity, redfish_version=None, registries=None, root=None): """A class representing a UpdateService :param connector: A Connector instance :param identity: The identity of the UpdateService resource :param redfish_version: The version of RedFish. Used to construct the object according to schema of given version :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages :param root: Sushy root object. Empty for Sushy root itself. """ super().__init__( connector, identity, redfish_version=redfish_version, registries=registries, root=root) def _get_simple_update_element(self): simple_update_action = self._actions.simple_update if not simple_update_action: raise exceptions.MissingAttributeError( action='#UpdateService.SimpleUpdate', resource=self._path) return simple_update_action def _get_legacy_transfer_protocols(self): """Get the backward-compatible values for transfer protocol. :returns: A set of allowed values. """ LOG.warning( 'Could not figure out the allowed values for the simple ' 'update action for UpdateService %s', self.identity) return {x.value for x in up_cons.UpdateTransferProtocolType} def get_allowed_transfer_protocols(self): """Get the allowed values for transfer protocol. :returns: A set of allowed values. :raises: MissingAttributeError, if Actions/#UpdateService.SimpleUpdate attribute not present. """ simple_update_action = self._get_simple_update_element() if not getattr(simple_update_action, 'transfer_protocol', None): LOG.debug( 'Server does not constrain allowed transfer protocols for ' 'simple update action of UpdateService %s', self.identity) return set(up_cons.UpdateTransferProtocolType) return {simple_update_action.transfer_protocol} def simple_update(self, image_uri, targets=None, transfer_protocol=up_cons.UPDATE_PROTOCOL_HTTP): """Simple Update is used to update software components. :returns: A task monitor. """ valid_transfer_protocols = self.get_allowed_transfer_protocols() if transfer_protocol in valid_transfer_protocols: transfer_protocol = up_cons.UpdateTransferProtocolType( transfer_protocol).value else: legacy_transfer_protocols = self._get_legacy_transfer_protocols() if transfer_protocol not in legacy_transfer_protocols: raise exceptions.InvalidParameterValueError( parameter='transfer_protocol', value=transfer_protocol, valid_values=valid_transfer_protocols) LOG.warning( 'Legacy transfer protocol constant %s is being used. ' 'Consider migrating to any of: %s', transfer_protocol, ', '.join(x.value for x in up_cons.UpdateTransferProtocolType)) target_uri = self._get_simple_update_element().target_uri LOG.debug( 'Updating software component %s via ' '%s ...', image_uri, target_uri) data = {'ImageURI': image_uri, 'TransferProtocol': transfer_protocol} if targets: data['Targets'] = targets rsp = self._conn.post(target_uri, data=data) return taskmonitor.TaskMonitor.from_response( self._conn, rsp, target_uri, redfish_version=self.redfish_version, registries=self.registries) def get_task_monitor(self, task_monitor): """Used to retrieve a TaskMonitor. Deprecated: Use sushy.Sushy.get_task_monitor :returns: A task monitor. """ return taskmonitor.TaskMonitor( self._conn, task_monitor, redfish_version=self.redfish_version, registries=self.registries, root=self.root) @property @utils.cache_it def software_inventory(self): """Property to reference SoftwareInventory collection instance""" if not self._software_inventory_path: raise exceptions.MissingAttributeError( attribute='SoftwareInventory/@odata.id', resource=self._software_inventory_path) return softwareinventory.SoftwareInventoryCollection( self._conn, self._software_inventory_path, redfish_version=self.redfish_version, registries=self.registries, root=self.root) @property @utils.cache_it def firmware_inventory(self): """Property to reference FirmwareInventory collection instance""" if not self._firmware_inventory_path: raise exceptions.MissingAttributeError( attribute='FirmwareInventory/@odata.id', resource=self._firmware_inventory_path) return softwareinventory.SoftwareInventoryCollection( self._conn, self._firmware_inventory_path, redfish_version=self.redfish_version, registries=self.registries, root=self.root) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6595147 sushy-5.5.0/sushy/standard_registries/0000775000175000017500000000000000000000000020132 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/standard_registries/Base.1.0.0.json0000664000175000017500000006360600000000000022345 0ustar00zuulzuul00000000000000{ "@Redfish.Copyright": "Copyright © 2014-2015 Distributed Management Task Force, Inc. (DMTF). All rights reserved.", "@Redfish.License": "Creative Commons Attribution 4.0 License. For full text see link: https://creativecommons.org/licenses/by/4.0/", "@odata.type": "#MessageRegistry.1.0.0.MessageRegistry", "Id": "Base.1.0.0", "Name": "Base Message Registry", "Language": "en", "Description": "This registry defines the base messages for Redfish", "RegistryPrefix": "Base", "RegistryVersion": "1.0.0", "OwningEntity": "DMTF", "Messages": { "Success": { "Description": "Indicates that all conditions of a successful operation have been met.", "Message": "Successfully Completed Request", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "None" }, "GeneralError": { "Description": "Indicates that a general error has occurred.", "Message": "A general error has occurred. See ExtendedInfo for more information.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "See ExtendedInfo for more information." }, "Created": { "Description": "Indicates that all conditions of a successful creation operation have been met.", "Message": "The resource has been created successfully", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "None" }, "PropertyDuplicate": { "Description": "Indicates that a duplicate property was included in the request body.", "Message": "The property %1 was duplicated in the request.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Remove the duplicate property from the request body and resubmit the request if the operation failed." }, "PropertyUnknown": { "Description": "Indicates that an unknown property was included in the request body.", "Message": "The property %1 is not in the list of valid properties for the resource.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Remove the unknown property from the request body and resubmit the request if the operation failed." }, "PropertyValueTypeError": { "Description": "Indicates that a property was given the wrong value type, such as when a number is supplied for a property that requires a string.", "Message": "The value %1 for the property %2 is of a different type than the property can accept.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the property in the request body and resubmit the request if the operation failed." }, "PropertyValueFormatError": { "Description": "Indicates that a property was given the correct value type but the value of that property was not supported. This includes value size/length exceeded.", "Message": "The value %1 for the property %2 is of a different format than the property can accept.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the property in the request body and resubmit the request if the operation failed." }, "PropertyValueNotInList": { "Description": "Indicates that a property was given the correct value type but the value of that property was not supported. This values not in an enumeration", "Message": "The value %1 for the property %2 is not in the list of acceptable values.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Choose a value from the enumeration list that the implementation can support and resubmit the request if the operation failed." }, "PropertyNotWritable": { "Description": "Indicates that a property was given a value in the request body, but the property is a readonly property.", "Message": "The property %1 is a read only property and cannot be assigned a value.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Remove the property from the request body and resubmit the request if the operation failed." }, "PropertyMissing": { "Description": "Indicates that a required property was not supplied as part of the request.", "Message": "The property %1 is a required property and must be included in the request.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Ensure that the property is in the request body and has a valid value and resubmit the request if the operation failed." }, "MalformedJSON": { "Description": "Indicates that the request body was malformed JSON. Could be duplicate, syntax error,etc.", "Message": "The request body submitted was malformed JSON and could not be parsed by the receiving service.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Ensure that the request body is valid JSON and resubmit the request." }, "ActionNotSupported": { "Description": "Indicates that the action supplied with the POST operation is not supported by the resource.", "Message": "The action %1 is not supported by the resource.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "The action supplied cannot be resubmitted to the implementation. Perhaps the action was invalid, the wrong resource was the target or the implementation documentation may be of assistance." }, "ActionParameterMissing": { "Description": "Indicates that the action requested was missing a parameter that is required to process the action.", "Message": "The action %1 requires the parameter %2 to be present in the request body.", "Severity": "Critical", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Supply the action with the required parameter in the request body when the request is resubmitted." }, "ActionParameterDuplicate": { "Description": "Indicates that the action was supplied with a duplicated parameter in the request body.", "Message": "The action %1 was submitted with more than one value for the parameter %2.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Resubmit the action with only one instance of the parameter in the request body if the operation failed." }, "ActionParameterUnknown": { "Description": "Indicates that an action was submitted but a parameter supplied did not match any of the known parameters.", "Message": "The action %1 was submitted with with the invalid parameter %2.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the invalid parameter and resubmit the request if the operation failed." }, "ActionParameterValueTypeError": { "Description": "Indicates that a parameter was given the wrong value type, such as when a number is supplied for a parameter that requires a string.", "Message": "The value %1 for the parameter %2 in the action %3 is of a different type than the parameter can accept.", "Severity": "Warning", "NumberOfArgs": 3, "ParamTypes": [ "string", "string", "string" ], "Resolution": "Correct the value for the parameter in the request body and resubmit the request if the operation failed." }, "ActionParameterValueFormatError": { "Description": "Indicates that a parameter was given the correct value type but the value of that parameter was not supported. This includes value size/length exceeded.", "Message": "The value %1 for the parameter %2 in the action %3 is of a different format than the parameter can accept.", "Severity": "Warning", "NumberOfArgs": 3, "ParamTypes": [ "string", "string", "string" ], "Resolution": "Correct the value for the parameter in the request body and resubmit the request if the operation failed." }, "ActionParameterNotSupported": { "Description": "Indicates that the parameter supplied for the action is not supported on the resource.", "Message": "The parameter %1 for the action %2 is not supported on the target resource.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Remove the parameter supplied and resubmit the request if the operation failed." }, "QueryParameterValueTypeError": { "Description": "Indicates that a query parameter was given the wrong value type, such as when a number is supplied for a query parameter that requires a string.", "Message": "The value %1 for the query parameter %2 is of a different type than the parameter can accept.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the query parameter in the request and resubmit the request if the operation failed." }, "QueryParameterValueFormatError": { "Description": "Indicates that a query parameter was given the correct value type but the value of that parameter was not supported. This includes value size/length exceeded.", "Message": "The value %1 for the parameter %2 is of a different format than the parameter can accept.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the query parameter in the request and resubmit the request if the operation failed." }, "QueryParameterOutOfRange": { "Description": "Indicates that a query parameter was supplied that is out of range for the given resource. This can happen with values that are too low or beyond that possible for the supplied resource, such as when a page is requested that is beyond the last page.", "Message": "The value %1 for the query parameter %2 is out of range %3.", "Severity": "Warning", "NumberOfArgs": 3, "ParamTypes": [ "string", "string", "string" ], "Resolution": "Reduce the value for the query parameter to a value that is within range, such as a start or count value that is within bounds of the number of resources in a collection or a page that is within the range of valid pages." }, "QueryNotSupportedOnResource": { "Description": "Indicates that query is not supported on the given resource, such as when a start/count query is attempted on a resource that is not a collection.", "Message": "Querying is not supported on the requested resource.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Remove the query parameters and resubmit the request if the operation failed." }, "QueryNotSupported": { "Description": "Indicates that query is not supported on the implementation.", "Message": "Querying is not supported by the implementation.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Remove the query parameters and resubmit the request if the operation failed." }, "SessionLimitExceeded": { "Description": "Indicates that a session establishment has been requested but the operation failed due to the number of simultaneous sessions exceeding the limit of the implementation.", "Message": "The session establishment failed due to the number of simultaneous sessions exceeding the limit of the implementation.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Reduce the number of other sessions before trying to establish the session or increase the limit of simultaneous sessions (if supported)." }, "EventSubscriptionLimitExceeded": { "Description": "Indicates that a event subscription establishment has been requested but the operation failed due to the number of simultaneous connection exceeding the limit of the implementation.", "Message": "The event subscription failed due to the number of simultaneous subscriptions exceeding the limit of the implementation.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Reduce the number of other subscriptions before trying to establish the event subscription or increase the limit of simultaneous subscriptions (if supported)." }, "ResourceCannotBeDeleted": { "Description": "Indicates that a delete operation was attempted on a resource that cannot be deleted.", "Message": "The delete request failed because the resource requested cannot be deleted.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Do not attempt to delete a non-deletable resource." }, "ResourceInUse": { "Description": "Indicates that a change was requested to a resource but the change was rejected due to the resource being in use or transition.", "Message": "The change to the requested resource failed because the resource is in use or in transition.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Remove the condition and resubmit the request if the operation failed." }, "ResourceAlreadyExists": { "Description": "Indicates that a resource change or creation was attempted but that the operation cannot proceed because the resource already exists.", "Message": "The requested resource already exists.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Do not repeat the create operation as the resource has already been created." }, "CreateFailedMissingReqProperties": { "Description": "Indicates that a create was attempted on a resource but that properties that are required for the create operation were missing from the request.", "Message": "The create operation failed because the required property %1 was missing from the request.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Correct the body to include the required property with a valid value and resubmit the request if the operation failed." }, "CreateLimitReachedForResource": { "Description": "Indicates that no more resources can be created on the resource as it has reached its create limit.", "Message": "The create operation failed because the resource has reached the limit of possible resources.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Either delete resources and resubmit the request if the operation failed or do not resubmit the request." }, "ServiceShuttingDown": { "Description": "Indicates that the operation failed as the service is shutting down, such as when the service reboots.", "Message": "The operation failed because the service is shutting down and can no longer take incoming requests.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "When the service becomes available, resubmit the request if the operation failed." }, "ServiceInUnknownState": { "Description": "Indicates that the operation failed because the service is in an unknown state and cannot accept additional requests.", "Message": "The operation failed because the service is in an unknown state and can no longer take incoming requests.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Restart the service and resubmit the request if the operation failed." }, "NoValidSession": { "Description": "Indicates that the operation failed because a valid session is required in order to access any resources.", "Message": "There is no valid session established with the implementation.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Establish as session before attempting any operations." }, "InsufficientPrivilege": { "Description": "Indicates that the credentials associated with the established session do not have sufficient privileges for the requested operation", "Message": "There are insufficient privileges for the account or credentials associated with the current session to perform the requested operation.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Either abandon the operation or change the associated access rights and resubmit the request if the operation failed." }, "AccountModified": { "Description": "Indicates that the account was successfully modified.", "Message": "The account was successfully modified.", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "No resolution is required." }, "AccountNotModified": { "Description": "Indicates that the modification requested for the account was not successful.", "Message": "The account modification request failed.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "The modification may have failed due to permission issues or issues with the request body." }, "AccountRemoved": { "Description": "Indicates that the account was successfully removed.", "Message": "The account was successfully removed.", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "No resolution is required." }, "AccountForSessionNoLongerExists": { "Description": "Indicates that the account for the session has been removed, thus the session has been removed as well.", "Message": "The account for the current session has been removed, thus the current session has been removed as well.", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "Attempt to connect with a valid account." }, "InvalidObject": { "Description": "Indicates that the object in question is invalid according to the implementation. Examples include a firmware update malformed URI.", "Message": "The object at %1 is invalid.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Either the object is malformed or the URI is not correct. Correct the condition and resubmit the request if it failed." }, "InternalError": { "Description": "Indicates that the request failed for an unknown internal error but that the service is still operational.", "Message": "The request failed due to an internal service error. The service is still operational.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Resubmit the request. If the problem persists, consider resetting the service." }, "UnrecognizedRequestBody": { "Description": "Indicates that the service encountered an unrecognizable request body that could not even be interpreted as malformed JSON.", "Message": "The service detected a malformed request body that it was unable to interpret.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Correct the request body and resubmit the request if it failed." }, "ResourceMissingAtURI": { "Description": "Indicates that the operation expected an image or other resource at the provided URI but none was found. Examples of this are in requests that require URIs like Firmware Update.", "Message": "The resource at the URI %1 was not found.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Place a valid resource at thr URI or correct the URI and resubmit the request." }, "ResourceAtUriInUnknownFormat": { "Description": "Indicates that the URI was valid but the resource or image at that URI was in a format not supported by the service.", "Message": "The resource at %1 is in a format not recognized by the service.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Place an image or resource or file that is recognized by the service at the URI." }, "ResourceAtUriUnauthorized": { "Description": "Indicates that the attempt to access the resource/file/image at the URI was unauthorized.", "Message": "While accessing the resource at %1, the service received an authorization error %2.", "Severity": "Critical", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Ensure that the appropriate access is provided for the service in order for it to access the URI." }, "CouldNotEstablishConnection": { "Description": "Indicates that the attempt to access the resource/file/image at the URI was unsuccessful because a session could not be established.", "Message": "The service failed to establish a connection with the URI %1.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Ensure that the URI contains a valid and reachable node name, protocol information and other URI components." }, "SourceDoesNotSupportProtocol": { "Description": "Indicates that while attempting to access, connect to or transfer a resource/file/image from another location that the other end of the connection did not support the protocol", "Message": "The other end of the connection at %1 does not support the specified protocol %2.", "Severity": "Critical", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Change protocols or URIs. " }, "AccessDenied": { "Description": "Indicates that while attempting to access, connect to or transfer to/from another resource, the service was denied access.", "Message": "While attempting to establish a connection to %1, the service was denied access.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Attempt to ensure that the URI is correct and that the service has the appropriate credentials." }, "ServiceTemporarilyUnavailable": { "Description": "Indicates the service is temporarily unavailable.", "Message": "The service is temporarily unavailable. Retry in %1 seconds.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Wait for the indicated retry duration and retry the operation." }, "InvalidIndex": { "Description": "The Index is not valid.", "Message": "The Index %1 is not a valid offset into the array.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "number" ], "Resolution": "Verify the index value provided is within the bounds of the array." }, "PropertyValueModified": { "Description": "Indicates that a property was given the correct value type but the value of that property was modified. Examples are truncated or rounded values.", "Message": "The property %1 was assigned the value %2 due to modification by the service.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "No resolution is required." } } } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/standard_registries/Base.1.2.0.json0000664000175000017500000007100600000000000022340 0ustar00zuulzuul00000000000000{ "@Redfish.Copyright": "Copyright 2014-2015, 2017 Distributed Management Task Force, Inc. (DMTF). All rights reserved.", "@Redfish.License": "Creative Commons Attribution 4.0 License. For full text see link: https://creativecommons.org/licenses/by/4.0/", "@odata.type": "#MessageRegistry.v1_0_0.MessageRegistry", "Id": "Base.1.2.0", "Name": "Base Message Registry", "Language": "en", "Description": "This registry defines the base messages for Redfish", "RegistryPrefix": "Base", "RegistryVersion": "1.2.0", "OwningEntity": "DMTF", "Messages": { "Success": { "Description": "Indicates that all conditions of a successful operation have been met.", "Message": "Successfully Completed Request", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "None" }, "GeneralError": { "Description": "Indicates that a general error has occurred.", "Message": "A general error has occurred. See ExtendedInfo for more information.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "See ExtendedInfo for more information." }, "Created": { "Description": "Indicates that all conditions of a successful creation operation have been met.", "Message": "The resource has been created successfully", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "None" }, "PropertyDuplicate": { "Description": "Indicates that a duplicate property was included in the request body.", "Message": "The property %1 was duplicated in the request.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Remove the duplicate property from the request body and resubmit the request if the operation failed." }, "PropertyUnknown": { "Description": "Indicates that an unknown property was included in the request body.", "Message": "The property %1 is not in the list of valid properties for the resource.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Remove the unknown property from the request body and resubmit the request if the operation failed." }, "PropertyValueTypeError": { "Description": "Indicates that a property was given the wrong value type, such as when a number is supplied for a property that requires a string.", "Message": "The value %1 for the property %2 is of a different type than the property can accept.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the property in the request body and resubmit the request if the operation failed." }, "PropertyValueFormatError": { "Description": "Indicates that a property was given the correct value type but the value of that property was not supported.", "Message": "The value %1 for the property %2 is of a different format than the property can accept.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the property in the request body and resubmit the request if the operation failed." }, "PropertyValueNotInList": { "Description": "Indicates that a property was given the correct value type but the value of that property was not supported. This values not in an enumeration", "Message": "The value %1 for the property %2 is not in the list of acceptable values.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Choose a value from the enumeration list that the implementation can support and resubmit the request if the operation failed." }, "PropertyNotWritable": { "Description": "Indicates that a property was given a value in the request body, but the property is a readonly property.", "Message": "The property %1 is a read only property and cannot be assigned a value.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Remove the property from the request body and resubmit the request if the operation failed." }, "PropertyMissing": { "Description": "Indicates that a required property was not supplied as part of the request.", "Message": "The property %1 is a required property and must be included in the request.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Ensure that the property is in the request body and has a valid value and resubmit the request if the operation failed." }, "MalformedJSON": { "Description": "Indicates that the request body was malformed JSON. Could be duplicate, syntax error,etc.", "Message": "The request body submitted was malformed JSON and could not be parsed by the receiving service.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Ensure that the request body is valid JSON and resubmit the request." }, "EmptyJSON": { "Description": "Indicates that the request body contained an empty JSON object when one or more properties are expected in the body.", "Message": "The request body submitted contained an empty JSON object and the service is unable to process it.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Add properties in the JSON object and resubmit the request." }, "ActionNotSupported": { "Description": "Indicates that the action supplied with the POST operation is not supported by the resource.", "Message": "The action %1 is not supported by the resource.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "The action supplied cannot be resubmitted to the implementation. Perhaps the action was invalid, the wrong resource was the target or the implementation documentation may be of assistance." }, "ActionParameterMissing": { "Description": "Indicates that the action requested was missing a parameter that is required to process the action.", "Message": "The action %1 requires the parameter %2 to be present in the request body.", "Severity": "Critical", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Supply the action with the required parameter in the request body when the request is resubmitted." }, "ActionParameterDuplicate": { "Description": "Indicates that the action was supplied with a duplicated parameter in the request body.", "Message": "The action %1 was submitted with more than one value for the parameter %2.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Resubmit the action with only one instance of the parameter in the request body if the operation failed." }, "ActionParameterUnknown": { "Description": "Indicates that an action was submitted but a parameter supplied did not match any of the known parameters.", "Message": "The action %1 was submitted with the invalid parameter %2.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the invalid parameter and resubmit the request if the operation failed." }, "ActionParameterValueTypeError": { "Description": "Indicates that a parameter was given the wrong value type, such as when a number is supplied for a parameter that requires a string.", "Message": "The value %1 for the parameter %2 in the action %3 is of a different type than the parameter can accept.", "Severity": "Warning", "NumberOfArgs": 3, "ParamTypes": [ "string", "string", "string" ], "Resolution": "Correct the value for the parameter in the request body and resubmit the request if the operation failed." }, "ActionParameterValueFormatError": { "Description": "Indicates that a parameter was given the correct value type but the value of that parameter was not supported. This includes value size/length exceeded.", "Message": "The value %1 for the parameter %2 in the action %3 is of a different format than the parameter can accept.", "Severity": "Warning", "NumberOfArgs": 3, "ParamTypes": [ "string", "string", "string" ], "Resolution": "Correct the value for the parameter in the request body and resubmit the request if the operation failed." }, "ActionParameterNotSupported": { "Description": "Indicates that the parameter supplied for the action is not supported on the resource.", "Message": "The parameter %1 for the action %2 is not supported on the target resource.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Remove the parameter supplied and resubmit the request if the operation failed." }, "QueryParameterValueTypeError": { "Description": "Indicates that a query parameter was given the wrong value type, such as when a number is supplied for a query parameter that requires a string.", "Message": "The value %1 for the query parameter %2 is of a different type than the parameter can accept.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the query parameter in the request and resubmit the request if the operation failed." }, "QueryParameterValueFormatError": { "Description": "Indicates that a query parameter was given the correct value type but the value of that parameter was not supported. This includes value size/length exceeded.", "Message": "The value %1 for the parameter %2 is of a different format than the parameter can accept.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the query parameter in the request and resubmit the request if the operation failed." }, "QueryParameterOutOfRange": { "Description": "Indicates that a query parameter was supplied that is out of range for the given resource. This can happen with values that are too low or beyond that possible for the supplied resource, such as when a page is requested that is beyond the last page.", "Message": "The value %1 for the query parameter %2 is out of range %3.", "Severity": "Warning", "NumberOfArgs": 3, "ParamTypes": [ "string", "string", "string" ], "Resolution": "Reduce the value for the query parameter to a value that is within range, such as a start or count value that is within bounds of the number of resources in a collection or a page that is within the range of valid pages." }, "QueryNotSupportedOnResource": { "Description": "Indicates that query is not supported on the given resource, such as when a start/count query is attempted on a resource that is not a collection.", "Message": "Querying is not supported on the requested resource.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Remove the query parameters and resubmit the request if the operation failed." }, "QueryNotSupported": { "Description": "Indicates that query is not supported on the implementation.", "Message": "Querying is not supported by the implementation.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Remove the query parameters and resubmit the request if the operation failed." }, "SessionLimitExceeded": { "Description": "Indicates that a session establishment has been requested but the operation failed due to the number of simultaneous sessions exceeding the limit of the implementation.", "Message": "The session establishment failed due to the number of simultaneous sessions exceeding the limit of the implementation.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Reduce the number of other sessions before trying to establish the session or increase the limit of simultaneous sessions (if supported)." }, "EventSubscriptionLimitExceeded": { "Description": "Indicates that a event subscription establishment has been requested but the operation failed due to the number of simultaneous connection exceeding the limit of the implementation.", "Message": "The event subscription failed due to the number of simultaneous subscriptions exceeding the limit of the implementation.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Reduce the number of other subscriptions before trying to establish the event subscription or increase the limit of simultaneous subscriptions (if supported)." }, "ResourceCannotBeDeleted": { "Description": "Indicates that a delete operation was attempted on a resource that cannot be deleted.", "Message": "The delete request failed because the resource requested cannot be deleted.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Do not attempt to delete a non-deletable resource." }, "ResourceInUse": { "Description": "Indicates that a change was requested to a resource but the change was rejected due to the resource being in use or transition.", "Message": "The change to the requested resource failed because the resource is in use or in transition.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Remove the condition and resubmit the request if the operation failed." }, "ResourceAlreadyExists": { "Description": "Indicates that a resource change or creation was attempted but that the operation cannot proceed because the resource already exists.", "Message": "The requested resource of type %1 with the property %2 with the value %3 already exists.", "Severity": "Critical", "NumberOfArgs": 3, "ParamTypes": [ "string", "string", "string" ], "Resolution": "Do not repeat the create operation as the resource has already been created." }, "ResourceNotFound": { "Description": "Indicates that the operation expected a resource identifier that corresponds to an existing resource but one was not found.", "Message": "The requested resource of type %1 named %2 was not found.", "Severity": "Critical", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Provide a valid resource identifier and resubmit the request." }, "CreateFailedMissingReqProperties": { "Description": "Indicates that a create was attempted on a resource but that properties that are required for the create operation were missing from the request.", "Message": "The create operation failed because the required property %1 was missing from the request.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Correct the body to include the required property with a valid value and resubmit the request if the operation failed." }, "CreateLimitReachedForResource": { "Description": "Indicates that no more resources can be created on the resource as it has reached its create limit.", "Message": "The create operation failed because the resource has reached the limit of possible resources.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Either delete resources and resubmit the request if the operation failed or do not resubmit the request." }, "ServiceShuttingDown": { "Description": "Indicates that the operation failed as the service is shutting down, such as when the service reboots.", "Message": "The operation failed because the service is shutting down and can no longer take incoming requests.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "When the service becomes available, resubmit the request if the operation failed." }, "ServiceInUnknownState": { "Description": "Indicates that the operation failed because the service is in an unknown state and cannot accept additional requests.", "Message": "The operation failed because the service is in an unknown state and can no longer take incoming requests.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Restart the service and resubmit the request if the operation failed." }, "NoValidSession": { "Description": "Indicates that the operation failed because a valid session is required in order to access any resources.", "Message": "There is no valid session established with the implementation.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Establish as session before attempting any operations." }, "InsufficientPrivilege": { "Description": "Indicates that the credentials associated with the established session do not have sufficient privileges for the requested operation", "Message": "There are insufficient privileges for the account or credentials associated with the current session to perform the requested operation.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Either abandon the operation or change the associated access rights and resubmit the request if the operation failed." }, "AccountModified": { "Description": "Indicates that the account was successfully modified.", "Message": "The account was successfully modified.", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "No resolution is required." }, "AccountNotModified": { "Description": "Indicates that the modification requested for the account was not successful.", "Message": "The account modification request failed.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "The modification may have failed due to permission issues or issues with the request body." }, "AccountRemoved": { "Description": "Indicates that the account was successfully removed.", "Message": "The account was successfully removed.", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "No resolution is required." }, "AccountForSessionNoLongerExists": { "Description": "Indicates that the account for the session has been removed, thus the session has been removed as well.", "Message": "The account for the current session has been removed, thus the current session has been removed as well.", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "Attempt to connect with a valid account." }, "InvalidObject": { "Description": "Indicates that the object in question is invalid according to the implementation. Examples include a firmware update malformed URI.", "Message": "The object at %1 is invalid.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Either the object is malformed or the URI is not correct. Correct the condition and resubmit the request if it failed." }, "InternalError": { "Description": "Indicates that the request failed for an unknown internal error but that the service is still operational.", "Message": "The request failed due to an internal service error. The service is still operational.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Resubmit the request. If the problem persists, consider resetting the service." }, "UnrecognizedRequestBody": { "Description": "Indicates that the service encountered an unrecognizable request body that could not even be interpreted as malformed JSON.", "Message": "The service detected a malformed request body that it was unable to interpret.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Correct the request body and resubmit the request if it failed." }, "ResourceMissingAtURI": { "Description": "Indicates that the operation expected an image or other resource at the provided URI but none was found. Examples of this are in requests that require URIs like Firmware Update.", "Message": "The resource at the URI %1 was not found.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Place a valid resource at the URI or correct the URI and resubmit the request." }, "ResourceAtUriInUnknownFormat": { "Description": "Indicates that the URI was valid but the resource or image at that URI was in a format not supported by the service.", "Message": "The resource at %1 is in a format not recognized by the service.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Place an image or resource or file that is recognized by the service at the URI." }, "ResourceAtUriUnauthorized": { "Description": "Indicates that the attempt to access the resource/file/image at the URI was unauthorized.", "Message": "While accessing the resource at %1, the service received an authorization error %2.", "Severity": "Critical", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Ensure that the appropriate access is provided for the service in order for it to access the URI." }, "CouldNotEstablishConnection": { "Description": "Indicates that the attempt to access the resource/file/image at the URI was unsuccessful because a session could not be established.", "Message": "The service failed to establish a connection with the URI %1.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Ensure that the URI contains a valid and reachable node name, protocol information and other URI components." }, "SourceDoesNotSupportProtocol": { "Description": "Indicates that while attempting to access, connect to or transfer a resource/file/image from another location that the other end of the connection did not support the protocol", "Message": "The other end of the connection at %1 does not support the specified protocol %2.", "Severity": "Critical", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Change protocols or URIs. " }, "AccessDenied": { "Description": "Indicates that while attempting to access, connect to or transfer to/from another resource, the service denied access.", "Message": "While attempting to establish a connection to %1, the service denied access.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Attempt to ensure that the URI is correct and that the service has the appropriate credentials." }, "ServiceTemporarilyUnavailable": { "Description": "Indicates the service is temporarily unavailable.", "Message": "The service is temporarily unavailable. Retry in %1 seconds.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Wait for the indicated retry duration and retry the operation." }, "InvalidIndex": { "Description": "The Index is not valid.", "Message": "The Index %1 is not a valid offset into the array.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "number" ], "Resolution": "Verify the index value provided is within the bounds of the array." }, "PropertyValueModified": { "Description": "Indicates that a property was given the correct value type but the value of that property was modified. Examples are truncated or rounded values.", "Message": "The property %1 was assigned the value %2 due to modification by the service.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "No resolution is required." }, "ResourceInStandby": { "Description": "Indicates that the request could not be performed because the resource is in standby.", "Message": "The request could not be performed because the resource is in standby.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Ensure that the resource is in the correct power state and resubmit the request." }, "ResourceExhaustion": { "Description": "Indicates that a resource could not satisfy the request due to some unavailability of resources. An example is that available capacity has been allocated.", "Message": "The resource %1 was unable to satisfy the request due to unavailability of resources.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Ensure that the resources are available and resubmit the request." }, "StringValueTooLong": { "Description": "Indicates that a string value passed to the given resource exceeded its length limit. An example is when a shorter limit is imposed by an implementation than that allowed by the specification.", "Message": "The string %1 exceeds the length limit %2.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "number" ], "Resolution": "Resubmit the request with an appropriate string length." } } } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/standard_registries/Base.1.3.0.json0000664000175000017500000007265100000000000022350 0ustar00zuulzuul00000000000000{ "@Redfish.Copyright": "Copyright 2014-2015, 2017-2018 DMTF. All rights reserved.", "@Redfish.License": "Creative Commons Attribution 4.0 License. For full text see link: https://creativecommons.org/licenses/by/4.0/", "@odata.type": "#MessageRegistry.v1_0_0.MessageRegistry", "Id": "Base.1.3.0", "Name": "Base Message Registry", "Language": "en", "Description": "This registry defines the base messages for Redfish", "RegistryPrefix": "Base", "RegistryVersion": "1.3.0", "OwningEntity": "DMTF", "Messages": { "Success": { "Description": "Indicates that all conditions of a successful operation have been met.", "Message": "Successfully Completed Request", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "None" }, "GeneralError": { "Description": "Indicates that a general error has occurred.", "Message": "A general error has occurred. See ExtendedInfo for more information.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "See ExtendedInfo for more information." }, "Created": { "Description": "Indicates that all conditions of a successful creation operation have been met.", "Message": "The resource has been created successfully", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "None" }, "PropertyDuplicate": { "Description": "Indicates that a duplicate property was included in the request body.", "Message": "The property %1 was duplicated in the request.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Remove the duplicate property from the request body and resubmit the request if the operation failed." }, "PropertyUnknown": { "Description": "Indicates that an unknown property was included in the request body.", "Message": "The property %1 is not in the list of valid properties for the resource.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Remove the unknown property from the request body and resubmit the request if the operation failed." }, "PropertyValueTypeError": { "Description": "Indicates that a property was given the wrong value type, such as when a number is supplied for a property that requires a string.", "Message": "The value %1 for the property %2 is of a different type than the property can accept.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the property in the request body and resubmit the request if the operation failed." }, "PropertyValueFormatError": { "Description": "Indicates that a property was given the correct value type but the value of that property was not supported.", "Message": "The value %1 for the property %2 is of a different format than the property can accept.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the property in the request body and resubmit the request if the operation failed." }, "PropertyValueNotInList": { "Description": "Indicates that a property was given the correct value type but the value of that property was not supported. This values not in an enumeration", "Message": "The value %1 for the property %2 is not in the list of acceptable values.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Choose a value from the enumeration list that the implementation can support and resubmit the request if the operation failed." }, "PropertyValueOutOfRange": { "Description": "Indicates that a property was given the correct value type but the value of that property is outside the supported range.", "Message": "The value %1 for the property %2 is not in the supported range of acceptable values.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the property in the request body and resubmit the request if the operation failed." }, "PropertyNotWritable": { "Description": "Indicates that a property was given a value in the request body, but the property is a readonly property.", "Message": "The property %1 is a read only property and cannot be assigned a value.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Remove the property from the request body and resubmit the request if the operation failed." }, "PropertyMissing": { "Description": "Indicates that a required property was not supplied as part of the request.", "Message": "The property %1 is a required property and must be included in the request.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Ensure that the property is in the request body and has a valid value and resubmit the request if the operation failed." }, "MalformedJSON": { "Description": "Indicates that the request body was malformed JSON. Could be duplicate, syntax error,etc.", "Message": "The request body submitted was malformed JSON and could not be parsed by the receiving service.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Ensure that the request body is valid JSON and resubmit the request." }, "EmptyJSON": { "Description": "Indicates that the request body contained an empty JSON object when one or more properties are expected in the body.", "Message": "The request body submitted contained an empty JSON object and the service is unable to process it.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Add properties in the JSON object and resubmit the request." }, "ActionNotSupported": { "Description": "Indicates that the action supplied with the POST operation is not supported by the resource.", "Message": "The action %1 is not supported by the resource.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "The action supplied cannot be resubmitted to the implementation. Perhaps the action was invalid, the wrong resource was the target or the implementation documentation may be of assistance." }, "ActionParameterMissing": { "Description": "Indicates that the action requested was missing a parameter that is required to process the action.", "Message": "The action %1 requires the parameter %2 to be present in the request body.", "Severity": "Critical", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Supply the action with the required parameter in the request body when the request is resubmitted." }, "ActionParameterDuplicate": { "Description": "Indicates that the action was supplied with a duplicated parameter in the request body.", "Message": "The action %1 was submitted with more than one value for the parameter %2.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Resubmit the action with only one instance of the parameter in the request body if the operation failed." }, "ActionParameterUnknown": { "Description": "Indicates that an action was submitted but a parameter supplied did not match any of the known parameters.", "Message": "The action %1 was submitted with the invalid parameter %2.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the invalid parameter and resubmit the request if the operation failed." }, "ActionParameterValueTypeError": { "Description": "Indicates that a parameter was given the wrong value type, such as when a number is supplied for a parameter that requires a string.", "Message": "The value %1 for the parameter %2 in the action %3 is of a different type than the parameter can accept.", "Severity": "Warning", "NumberOfArgs": 3, "ParamTypes": [ "string", "string", "string" ], "Resolution": "Correct the value for the parameter in the request body and resubmit the request if the operation failed." }, "ActionParameterValueFormatError": { "Description": "Indicates that a parameter was given the correct value type but the value of that parameter was not supported. This includes value size/length exceeded.", "Message": "The value %1 for the parameter %2 in the action %3 is of a different format than the parameter can accept.", "Severity": "Warning", "NumberOfArgs": 3, "ParamTypes": [ "string", "string", "string" ], "Resolution": "Correct the value for the parameter in the request body and resubmit the request if the operation failed." }, "ActionParameterNotSupported": { "Description": "Indicates that the parameter supplied for the action is not supported on the resource.", "Message": "The parameter %1 for the action %2 is not supported on the target resource.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Remove the parameter supplied and resubmit the request if the operation failed." }, "QueryParameterValueTypeError": { "Description": "Indicates that a query parameter was given the wrong value type, such as when a number is supplied for a query parameter that requires a string.", "Message": "The value %1 for the query parameter %2 is of a different type than the parameter can accept.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the query parameter in the request and resubmit the request if the operation failed." }, "QueryParameterValueFormatError": { "Description": "Indicates that a query parameter was given the correct value type but the value of that parameter was not supported. This includes value size/length exceeded.", "Message": "The value %1 for the parameter %2 is of a different format than the parameter can accept.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the query parameter in the request and resubmit the request if the operation failed." }, "QueryParameterOutOfRange": { "Description": "Indicates that a query parameter was supplied that is out of range for the given resource. This can happen with values that are too low or beyond that possible for the supplied resource, such as when a page is requested that is beyond the last page.", "Message": "The value %1 for the query parameter %2 is out of range %3.", "Severity": "Warning", "NumberOfArgs": 3, "ParamTypes": [ "string", "string", "string" ], "Resolution": "Reduce the value for the query parameter to a value that is within range, such as a start or count value that is within bounds of the number of resources in a collection or a page that is within the range of valid pages." }, "QueryNotSupportedOnResource": { "Description": "Indicates that query is not supported on the given resource, such as when a start/count query is attempted on a resource that is not a collection.", "Message": "Querying is not supported on the requested resource.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Remove the query parameters and resubmit the request if the operation failed." }, "QueryNotSupported": { "Description": "Indicates that query is not supported on the implementation.", "Message": "Querying is not supported by the implementation.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Remove the query parameters and resubmit the request if the operation failed." }, "SessionLimitExceeded": { "Description": "Indicates that a session establishment has been requested but the operation failed due to the number of simultaneous sessions exceeding the limit of the implementation.", "Message": "The session establishment failed due to the number of simultaneous sessions exceeding the limit of the implementation.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Reduce the number of other sessions before trying to establish the session or increase the limit of simultaneous sessions (if supported)." }, "EventSubscriptionLimitExceeded": { "Description": "Indicates that a event subscription establishment has been requested but the operation failed due to the number of simultaneous connection exceeding the limit of the implementation.", "Message": "The event subscription failed due to the number of simultaneous subscriptions exceeding the limit of the implementation.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Reduce the number of other subscriptions before trying to establish the event subscription or increase the limit of simultaneous subscriptions (if supported)." }, "ResourceCannotBeDeleted": { "Description": "Indicates that a delete operation was attempted on a resource that cannot be deleted.", "Message": "The delete request failed because the resource requested cannot be deleted.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Do not attempt to delete a non-deletable resource." }, "ResourceInUse": { "Description": "Indicates that a change was requested to a resource but the change was rejected due to the resource being in use or transition.", "Message": "The change to the requested resource failed because the resource is in use or in transition.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Remove the condition and resubmit the request if the operation failed." }, "ResourceAlreadyExists": { "Description": "Indicates that a resource change or creation was attempted but that the operation cannot proceed because the resource already exists.", "Message": "The requested resource of type %1 with the property %2 with the value %3 already exists.", "Severity": "Critical", "NumberOfArgs": 3, "ParamTypes": [ "string", "string", "string" ], "Resolution": "Do not repeat the create operation as the resource has already been created." }, "ResourceNotFound": { "Description": "Indicates that the operation expected a resource identifier that corresponds to an existing resource but one was not found.", "Message": "The requested resource of type %1 named %2 was not found.", "Severity": "Critical", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Provide a valid resource identifier and resubmit the request." }, "CreateFailedMissingReqProperties": { "Description": "Indicates that a create was attempted on a resource but that properties that are required for the create operation were missing from the request.", "Message": "The create operation failed because the required property %1 was missing from the request.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Correct the body to include the required property with a valid value and resubmit the request if the operation failed." }, "CreateLimitReachedForResource": { "Description": "Indicates that no more resources can be created on the resource as it has reached its create limit.", "Message": "The create operation failed because the resource has reached the limit of possible resources.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Either delete resources and resubmit the request if the operation failed or do not resubmit the request." }, "ServiceShuttingDown": { "Description": "Indicates that the operation failed as the service is shutting down, such as when the service reboots.", "Message": "The operation failed because the service is shutting down and can no longer take incoming requests.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "When the service becomes available, resubmit the request if the operation failed." }, "ServiceInUnknownState": { "Description": "Indicates that the operation failed because the service is in an unknown state and cannot accept additional requests.", "Message": "The operation failed because the service is in an unknown state and can no longer take incoming requests.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Restart the service and resubmit the request if the operation failed." }, "NoValidSession": { "Description": "Indicates that the operation failed because a valid session is required in order to access any resources.", "Message": "There is no valid session established with the implementation.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Establish as session before attempting any operations." }, "InsufficientPrivilege": { "Description": "Indicates that the credentials associated with the established session do not have sufficient privileges for the requested operation", "Message": "There are insufficient privileges for the account or credentials associated with the current session to perform the requested operation.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Either abandon the operation or change the associated access rights and resubmit the request if the operation failed." }, "AccountModified": { "Description": "Indicates that the account was successfully modified.", "Message": "The account was successfully modified.", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "No resolution is required." }, "AccountNotModified": { "Description": "Indicates that the modification requested for the account was not successful.", "Message": "The account modification request failed.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "The modification may have failed due to permission issues or issues with the request body." }, "AccountRemoved": { "Description": "Indicates that the account was successfully removed.", "Message": "The account was successfully removed.", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "No resolution is required." }, "AccountForSessionNoLongerExists": { "Description": "Indicates that the account for the session has been removed, thus the session has been removed as well.", "Message": "The account for the current session has been removed, thus the current session has been removed as well.", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "Attempt to connect with a valid account." }, "InvalidObject": { "Description": "Indicates that the object in question is invalid according to the implementation. Examples include a firmware update malformed URI.", "Message": "The object at %1 is invalid.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Either the object is malformed or the URI is not correct. Correct the condition and resubmit the request if it failed." }, "InternalError": { "Description": "Indicates that the request failed for an unknown internal error but that the service is still operational.", "Message": "The request failed due to an internal service error. The service is still operational.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Resubmit the request. If the problem persists, consider resetting the service." }, "UnrecognizedRequestBody": { "Description": "Indicates that the service encountered an unrecognizable request body that could not even be interpreted as malformed JSON.", "Message": "The service detected a malformed request body that it was unable to interpret.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Correct the request body and resubmit the request if it failed." }, "ResourceMissingAtURI": { "Description": "Indicates that the operation expected an image or other resource at the provided URI but none was found. Examples of this are in requests that require URIs like Firmware Update.", "Message": "The resource at the URI %1 was not found.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Place a valid resource at the URI or correct the URI and resubmit the request." }, "ResourceAtUriInUnknownFormat": { "Description": "Indicates that the URI was valid but the resource or image at that URI was in a format not supported by the service.", "Message": "The resource at %1 is in a format not recognized by the service.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Place an image or resource or file that is recognized by the service at the URI." }, "ResourceAtUriUnauthorized": { "Description": "Indicates that the attempt to access the resource/file/image at the URI was unauthorized.", "Message": "While accessing the resource at %1, the service received an authorization error %2.", "Severity": "Critical", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Ensure that the appropriate access is provided for the service in order for it to access the URI." }, "CouldNotEstablishConnection": { "Description": "Indicates that the attempt to access the resource/file/image at the URI was unsuccessful because a session could not be established.", "Message": "The service failed to establish a connection with the URI %1.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Ensure that the URI contains a valid and reachable node name, protocol information and other URI components." }, "SourceDoesNotSupportProtocol": { "Description": "Indicates that while attempting to access, connect to or transfer a resource/file/image from another location that the other end of the connection did not support the protocol", "Message": "The other end of the connection at %1 does not support the specified protocol %2.", "Severity": "Critical", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Change protocols or URIs. " }, "AccessDenied": { "Description": "Indicates that while attempting to access, connect to or transfer to/from another resource, the service denied access.", "Message": "While attempting to establish a connection to %1, the service denied access.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Attempt to ensure that the URI is correct and that the service has the appropriate credentials." }, "ServiceTemporarilyUnavailable": { "Description": "Indicates the service is temporarily unavailable.", "Message": "The service is temporarily unavailable. Retry in %1 seconds.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Wait for the indicated retry duration and retry the operation." }, "InvalidIndex": { "Description": "The Index is not valid.", "Message": "The Index %1 is not a valid offset into the array.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "number" ], "Resolution": "Verify the index value provided is within the bounds of the array." }, "PropertyValueModified": { "Description": "Indicates that a property was given the correct value type but the value of that property was modified. Examples are truncated or rounded values.", "Message": "The property %1 was assigned the value %2 due to modification by the service.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "No resolution is required." }, "ResourceInStandby": { "Description": "Indicates that the request could not be performed because the resource is in standby.", "Message": "The request could not be performed because the resource is in standby.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Ensure that the resource is in the correct power state and resubmit the request." }, "ResourceExhaustion": { "Description": "Indicates that a resource could not satisfy the request due to some unavailability of resources. An example is that available capacity has been allocated.", "Message": "The resource %1 was unable to satisfy the request due to unavailability of resources.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Ensure that the resources are available and resubmit the request." }, "StringValueTooLong": { "Description": "Indicates that a string value passed to the given resource exceeded its length limit. An example is when a shorter limit is imposed by an implementation than that allowed by the specification.", "Message": "The string %1 exceeds the length limit %2.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "number" ], "Resolution": "Resubmit the request with an appropriate string length." }, "SessionTerminated": { "Description": "Indicates that the DELETE operation on the Session resource resulted in the successful termination of the session.", "Message": "The session was successfully terminated.", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "No resolution is required." } } } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/standard_registries/Base.1.3.1.json0000664000175000017500000007311100000000000022341 0ustar00zuulzuul00000000000000{ "@Redfish.Copyright": "Copyright 2014-2018 DMTF. All rights reserved.", "@Redfish.License": "Creative Commons Attribution 4.0 License. For full text see link: https://creativecommons.org/licenses/by/4.0/", "@odata.type": "#MessageRegistry.v1_0_0.MessageRegistry", "Id": "Base.1.3.1", "Name": "Base Message Registry", "Language": "en", "Description": "This registry defines the base messages for Redfish", "RegistryPrefix": "Base", "RegistryVersion": "1.3.1", "OwningEntity": "DMTF", "Messages": { "Success": { "Description": "Indicates that all conditions of a successful operation have been met.", "Message": "Successfully Completed Request", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "None" }, "GeneralError": { "Description": "Indicates that a general error has occurred. Use in ExtendedInfo is discouraged. When used in ExtendedInfo, implementations are expected to include a Resolution property with this error to indicate how to resolve the problem.", "Message": "A general error has occurred. See Resolution for information on how to resolve the error.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "None." }, "Created": { "Description": "Indicates that all conditions of a successful creation operation have been met.", "Message": "The resource has been created successfully", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "None" }, "PropertyDuplicate": { "Description": "Indicates that a duplicate property was included in the request body.", "Message": "The property %1 was duplicated in the request.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Remove the duplicate property from the request body and resubmit the request if the operation failed." }, "PropertyUnknown": { "Description": "Indicates that an unknown property was included in the request body.", "Message": "The property %1 is not in the list of valid properties for the resource.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Remove the unknown property from the request body and resubmit the request if the operation failed." }, "PropertyValueTypeError": { "Description": "Indicates that a property was given the wrong value type, such as when a number is supplied for a property that requires a string.", "Message": "The value %1 for the property %2 is of a different type than the property can accept.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the property in the request body and resubmit the request if the operation failed." }, "PropertyValueFormatError": { "Description": "Indicates that a property was given the correct value type but the value of that property was not supported.", "Message": "The value %1 for the property %2 is of a different format than the property can accept.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the property in the request body and resubmit the request if the operation failed." }, "PropertyValueNotInList": { "Description": "Indicates that a property was given the correct value type but the value of that property was not supported. This values not in an enumeration", "Message": "The value %1 for the property %2 is not in the list of acceptable values.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Choose a value from the enumeration list that the implementation can support and resubmit the request if the operation failed." }, "PropertyValueOutOfRange": { "Description": "Indicates that a property was given the correct value type but the value of that property is outside the supported range.", "Message": "The value %1 for the property %2 is not in the supported range of acceptable values.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the property in the request body and resubmit the request if the operation failed." }, "PropertyNotWritable": { "Description": "Indicates that a property was given a value in the request body, but the property is a readonly property.", "Message": "The property %1 is a read only property and cannot be assigned a value.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Remove the property from the request body and resubmit the request if the operation failed." }, "PropertyMissing": { "Description": "Indicates that a required property was not supplied as part of the request.", "Message": "The property %1 is a required property and must be included in the request.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Ensure that the property is in the request body and has a valid value and resubmit the request if the operation failed." }, "MalformedJSON": { "Description": "Indicates that the request body was malformed JSON. Could be duplicate, syntax error,etc.", "Message": "The request body submitted was malformed JSON and could not be parsed by the receiving service.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Ensure that the request body is valid JSON and resubmit the request." }, "EmptyJSON": { "Description": "Indicates that the request body contained an empty JSON object when one or more properties are expected in the body.", "Message": "The request body submitted contained an empty JSON object and the service is unable to process it.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Add properties in the JSON object and resubmit the request." }, "ActionNotSupported": { "Description": "Indicates that the action supplied with the POST operation is not supported by the resource.", "Message": "The action %1 is not supported by the resource.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "The action supplied cannot be resubmitted to the implementation. Perhaps the action was invalid, the wrong resource was the target or the implementation documentation may be of assistance." }, "ActionParameterMissing": { "Description": "Indicates that the action requested was missing a parameter that is required to process the action.", "Message": "The action %1 requires the parameter %2 to be present in the request body.", "Severity": "Critical", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Supply the action with the required parameter in the request body when the request is resubmitted." }, "ActionParameterDuplicate": { "Description": "Indicates that the action was supplied with a duplicated parameter in the request body.", "Message": "The action %1 was submitted with more than one value for the parameter %2.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Resubmit the action with only one instance of the parameter in the request body if the operation failed." }, "ActionParameterUnknown": { "Description": "Indicates that an action was submitted but a parameter supplied did not match any of the known parameters.", "Message": "The action %1 was submitted with the invalid parameter %2.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the invalid parameter and resubmit the request if the operation failed." }, "ActionParameterValueTypeError": { "Description": "Indicates that a parameter was given the wrong value type, such as when a number is supplied for a parameter that requires a string.", "Message": "The value %1 for the parameter %2 in the action %3 is of a different type than the parameter can accept.", "Severity": "Warning", "NumberOfArgs": 3, "ParamTypes": [ "string", "string", "string" ], "Resolution": "Correct the value for the parameter in the request body and resubmit the request if the operation failed." }, "ActionParameterValueFormatError": { "Description": "Indicates that a parameter was given the correct value type but the value of that parameter was not supported. This includes value size/length exceeded.", "Message": "The value %1 for the parameter %2 in the action %3 is of a different format than the parameter can accept.", "Severity": "Warning", "NumberOfArgs": 3, "ParamTypes": [ "string", "string", "string" ], "Resolution": "Correct the value for the parameter in the request body and resubmit the request if the operation failed." }, "ActionParameterNotSupported": { "Description": "Indicates that the parameter supplied for the action is not supported on the resource.", "Message": "The parameter %1 for the action %2 is not supported on the target resource.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Remove the parameter supplied and resubmit the request if the operation failed." }, "QueryParameterValueTypeError": { "Description": "Indicates that a query parameter was given the wrong value type, such as when a number is supplied for a query parameter that requires a string.", "Message": "The value %1 for the query parameter %2 is of a different type than the parameter can accept.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the query parameter in the request and resubmit the request if the operation failed." }, "QueryParameterValueFormatError": { "Description": "Indicates that a query parameter was given the correct value type but the value of that parameter was not supported. This includes value size/length exceeded.", "Message": "The value %1 for the parameter %2 is of a different format than the parameter can accept.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the query parameter in the request and resubmit the request if the operation failed." }, "QueryParameterOutOfRange": { "Description": "Indicates that a query parameter was supplied that is out of range for the given resource. This can happen with values that are too low or beyond that possible for the supplied resource, such as when a page is requested that is beyond the last page.", "Message": "The value %1 for the query parameter %2 is out of range %3.", "Severity": "Warning", "NumberOfArgs": 3, "ParamTypes": [ "string", "string", "string" ], "Resolution": "Reduce the value for the query parameter to a value that is within range, such as a start or count value that is within bounds of the number of resources in a collection or a page that is within the range of valid pages." }, "QueryNotSupportedOnResource": { "Description": "Indicates that query is not supported on the given resource, such as when a start/count query is attempted on a resource that is not a collection.", "Message": "Querying is not supported on the requested resource.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Remove the query parameters and resubmit the request if the operation failed." }, "QueryNotSupported": { "Description": "Indicates that query is not supported on the implementation.", "Message": "Querying is not supported by the implementation.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Remove the query parameters and resubmit the request if the operation failed." }, "SessionLimitExceeded": { "Description": "Indicates that a session establishment has been requested but the operation failed due to the number of simultaneous sessions exceeding the limit of the implementation.", "Message": "The session establishment failed due to the number of simultaneous sessions exceeding the limit of the implementation.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Reduce the number of other sessions before trying to establish the session or increase the limit of simultaneous sessions (if supported)." }, "EventSubscriptionLimitExceeded": { "Description": "Indicates that a event subscription establishment has been requested but the operation failed due to the number of simultaneous connection exceeding the limit of the implementation.", "Message": "The event subscription failed due to the number of simultaneous subscriptions exceeding the limit of the implementation.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Reduce the number of other subscriptions before trying to establish the event subscription or increase the limit of simultaneous subscriptions (if supported)." }, "ResourceCannotBeDeleted": { "Description": "Indicates that a delete operation was attempted on a resource that cannot be deleted.", "Message": "The delete request failed because the resource requested cannot be deleted.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Do not attempt to delete a non-deletable resource." }, "ResourceInUse": { "Description": "Indicates that a change was requested to a resource but the change was rejected due to the resource being in use or transition.", "Message": "The change to the requested resource failed because the resource is in use or in transition.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Remove the condition and resubmit the request if the operation failed." }, "ResourceAlreadyExists": { "Description": "Indicates that a resource change or creation was attempted but that the operation cannot proceed because the resource already exists.", "Message": "The requested resource of type %1 with the property %2 with the value %3 already exists.", "Severity": "Critical", "NumberOfArgs": 3, "ParamTypes": [ "string", "string", "string" ], "Resolution": "Do not repeat the create operation as the resource has already been created." }, "ResourceNotFound": { "Description": "Indicates that the operation expected a resource identifier that corresponds to an existing resource but one was not found.", "Message": "The requested resource of type %1 named %2 was not found.", "Severity": "Critical", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Provide a valid resource identifier and resubmit the request." }, "CreateFailedMissingReqProperties": { "Description": "Indicates that a create was attempted on a resource but that properties that are required for the create operation were missing from the request.", "Message": "The create operation failed because the required property %1 was missing from the request.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Correct the body to include the required property with a valid value and resubmit the request if the operation failed." }, "CreateLimitReachedForResource": { "Description": "Indicates that no more resources can be created on the resource as it has reached its create limit.", "Message": "The create operation failed because the resource has reached the limit of possible resources.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Either delete resources and resubmit the request if the operation failed or do not resubmit the request." }, "ServiceShuttingDown": { "Description": "Indicates that the operation failed as the service is shutting down, such as when the service reboots.", "Message": "The operation failed because the service is shutting down and can no longer take incoming requests.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "When the service becomes available, resubmit the request if the operation failed." }, "ServiceInUnknownState": { "Description": "Indicates that the operation failed because the service is in an unknown state and cannot accept additional requests.", "Message": "The operation failed because the service is in an unknown state and can no longer take incoming requests.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Restart the service and resubmit the request if the operation failed." }, "NoValidSession": { "Description": "Indicates that the operation failed because a valid session is required in order to access any resources.", "Message": "There is no valid session established with the implementation.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Establish as session before attempting any operations." }, "InsufficientPrivilege": { "Description": "Indicates that the credentials associated with the established session do not have sufficient privileges for the requested operation", "Message": "There are insufficient privileges for the account or credentials associated with the current session to perform the requested operation.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Either abandon the operation or change the associated access rights and resubmit the request if the operation failed." }, "AccountModified": { "Description": "Indicates that the account was successfully modified.", "Message": "The account was successfully modified.", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "No resolution is required." }, "AccountNotModified": { "Description": "Indicates that the modification requested for the account was not successful.", "Message": "The account modification request failed.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "The modification may have failed due to permission issues or issues with the request body." }, "AccountRemoved": { "Description": "Indicates that the account was successfully removed.", "Message": "The account was successfully removed.", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "No resolution is required." }, "AccountForSessionNoLongerExists": { "Description": "Indicates that the account for the session has been removed, thus the session has been removed as well.", "Message": "The account for the current session has been removed, thus the current session has been removed as well.", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "Attempt to connect with a valid account." }, "InvalidObject": { "Description": "Indicates that the object in question is invalid according to the implementation. Examples include a firmware update malformed URI.", "Message": "The object at %1 is invalid.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Either the object is malformed or the URI is not correct. Correct the condition and resubmit the request if it failed." }, "InternalError": { "Description": "Indicates that the request failed for an unknown internal error but that the service is still operational.", "Message": "The request failed due to an internal service error. The service is still operational.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Resubmit the request. If the problem persists, consider resetting the service." }, "UnrecognizedRequestBody": { "Description": "Indicates that the service encountered an unrecognizable request body that could not even be interpreted as malformed JSON.", "Message": "The service detected a malformed request body that it was unable to interpret.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Correct the request body and resubmit the request if it failed." }, "ResourceMissingAtURI": { "Description": "Indicates that the operation expected an image or other resource at the provided URI but none was found. Examples of this are in requests that require URIs like Firmware Update.", "Message": "The resource at the URI %1 was not found.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Place a valid resource at the URI or correct the URI and resubmit the request." }, "ResourceAtUriInUnknownFormat": { "Description": "Indicates that the URI was valid but the resource or image at that URI was in a format not supported by the service.", "Message": "The resource at %1 is in a format not recognized by the service.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Place an image or resource or file that is recognized by the service at the URI." }, "ResourceAtUriUnauthorized": { "Description": "Indicates that the attempt to access the resource/file/image at the URI was unauthorized.", "Message": "While accessing the resource at %1, the service received an authorization error %2.", "Severity": "Critical", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Ensure that the appropriate access is provided for the service in order for it to access the URI." }, "CouldNotEstablishConnection": { "Description": "Indicates that the attempt to access the resource/file/image at the URI was unsuccessful because a session could not be established.", "Message": "The service failed to establish a connection with the URI %1.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Ensure that the URI contains a valid and reachable node name, protocol information and other URI components." }, "SourceDoesNotSupportProtocol": { "Description": "Indicates that while attempting to access, connect to or transfer a resource/file/image from another location that the other end of the connection did not support the protocol", "Message": "The other end of the connection at %1 does not support the specified protocol %2.", "Severity": "Critical", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Change protocols or URIs. " }, "AccessDenied": { "Description": "Indicates that while attempting to access, connect to or transfer to/from another resource, the service denied access.", "Message": "While attempting to establish a connection to %1, the service denied access.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Attempt to ensure that the URI is correct and that the service has the appropriate credentials." }, "ServiceTemporarilyUnavailable": { "Description": "Indicates the service is temporarily unavailable.", "Message": "The service is temporarily unavailable. Retry in %1 seconds.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Wait for the indicated retry duration and retry the operation." }, "InvalidIndex": { "Description": "The Index is not valid.", "Message": "The Index %1 is not a valid offset into the array.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "number" ], "Resolution": "Verify the index value provided is within the bounds of the array." }, "PropertyValueModified": { "Description": "Indicates that a property was given the correct value type but the value of that property was modified. Examples are truncated or rounded values.", "Message": "The property %1 was assigned the value %2 due to modification by the service.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "No resolution is required." }, "ResourceInStandby": { "Description": "Indicates that the request could not be performed because the resource is in standby.", "Message": "The request could not be performed because the resource is in standby.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Ensure that the resource is in the correct power state and resubmit the request." }, "ResourceExhaustion": { "Description": "Indicates that a resource could not satisfy the request due to some unavailability of resources. An example is that available capacity has been allocated.", "Message": "The resource %1 was unable to satisfy the request due to unavailability of resources.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Ensure that the resources are available and resubmit the request." }, "StringValueTooLong": { "Description": "Indicates that a string value passed to the given resource exceeded its length limit. An example is when a shorter limit is imposed by an implementation than that allowed by the specification.", "Message": "The string %1 exceeds the length limit %2.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "number" ], "Resolution": "Resubmit the request with an appropriate string length." }, "SessionTerminated": { "Description": "Indicates that the DELETE operation on the Session resource resulted in the successful termination of the session.", "Message": "The session was successfully terminated.", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "No resolution is required." } } } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/standard_registries/Base.1.4.0.json0000664000175000017500000007423200000000000022346 0ustar00zuulzuul00000000000000{ "@Redfish.Copyright": "Copyright 2014-2018 DMTF. All rights reserved.", "@Redfish.License": "Creative Commons Attribution 4.0 License. For full text see link: https://creativecommons.org/licenses/by/4.0/", "@odata.type": "#MessageRegistry.v1_0_0.MessageRegistry", "Id": "Base.1.4.0", "Name": "Base Message Registry", "Language": "en", "Description": "This registry defines the base messages for Redfish", "RegistryPrefix": "Base", "RegistryVersion": "1.4.0", "OwningEntity": "DMTF", "Messages": { "Success": { "Description": "Indicates that all conditions of a successful operation have been met.", "Message": "Successfully Completed Request", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "None" }, "GeneralError": { "Description": "Indicates that a general error has occurred. Use in ExtendedInfo is discouraged. When used in ExtendedInfo, implementations are expected to include a Resolution property with this error to indicate how to resolve the problem.", "Message": "A general error has occurred. See Resolution for information on how to resolve the error.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "None." }, "Created": { "Description": "Indicates that all conditions of a successful creation operation have been met.", "Message": "The resource has been created successfully", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "None" }, "NoOperation": { "Description": "Indicates that the requested operation will not perform any changes on the service.", "Message": "The request body submitted contain no data to act upon and no changes to the resource took place.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Add properties in the JSON object and resubmit the request." }, "PropertyDuplicate": { "Description": "Indicates that a duplicate property was included in the request body.", "Message": "The property %1 was duplicated in the request.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Remove the duplicate property from the request body and resubmit the request if the operation failed." }, "PropertyUnknown": { "Description": "Indicates that an unknown property was included in the request body.", "Message": "The property %1 is not in the list of valid properties for the resource.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Remove the unknown property from the request body and resubmit the request if the operation failed." }, "PropertyValueTypeError": { "Description": "Indicates that a property was given the wrong value type, such as when a number is supplied for a property that requires a string.", "Message": "The value %1 for the property %2 is of a different type than the property can accept.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the property in the request body and resubmit the request if the operation failed." }, "PropertyValueFormatError": { "Description": "Indicates that a property was given the correct value type but the value of that property was not supported.", "Message": "The value %1 for the property %2 is of a different format than the property can accept.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the property in the request body and resubmit the request if the operation failed." }, "PropertyValueNotInList": { "Description": "Indicates that a property was given the correct value type but the value of that property was not supported. This values not in an enumeration", "Message": "The value %1 for the property %2 is not in the list of acceptable values.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Choose a value from the enumeration list that the implementation can support and resubmit the request if the operation failed." }, "PropertyNotWritable": { "Description": "Indicates that a property was given a value in the request body, but the property is a readonly property.", "Message": "The property %1 is a read only property and cannot be assigned a value.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Remove the property from the request body and resubmit the request if the operation failed." }, "PropertyMissing": { "Description": "Indicates that a required property was not supplied as part of the request.", "Message": "The property %1 is a required property and must be included in the request.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Ensure that the property is in the request body and has a valid value and resubmit the request if the operation failed." }, "MalformedJSON": { "Description": "Indicates that the request body was malformed JSON. Could be duplicate, syntax error,etc.", "Message": "The request body submitted was malformed JSON and could not be parsed by the receiving service.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Ensure that the request body is valid JSON and resubmit the request." }, "EmptyJSON": { "Description": "Indicates that the request body contained an empty JSON object when one or more properties are expected in the body.", "Message": "The request body submitted contained an empty JSON object and the service is unable to process it.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Add properties in the JSON object and resubmit the request." }, "ActionNotSupported": { "Description": "Indicates that the action supplied with the POST operation is not supported by the resource.", "Message": "The action %1 is not supported by the resource.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "The action supplied cannot be resubmitted to the implementation. Perhaps the action was invalid, the wrong resource was the target or the implementation documentation may be of assistance." }, "ActionParameterMissing": { "Description": "Indicates that the action requested was missing a parameter that is required to process the action.", "Message": "The action %1 requires the parameter %2 to be present in the request body.", "Severity": "Critical", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Supply the action with the required parameter in the request body when the request is resubmitted." }, "ActionParameterDuplicate": { "Description": "Indicates that the action was supplied with a duplicated parameter in the request body.", "Message": "The action %1 was submitted with more than one value for the parameter %2.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Resubmit the action with only one instance of the parameter in the request body if the operation failed." }, "ActionParameterUnknown": { "Description": "Indicates that an action was submitted but a parameter supplied did not match any of the known parameters.", "Message": "The action %1 was submitted with the invalid parameter %2.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the invalid parameter and resubmit the request if the operation failed." }, "ActionParameterValueTypeError": { "Description": "Indicates that a parameter was given the wrong value type, such as when a number is supplied for a parameter that requires a string.", "Message": "The value %1 for the parameter %2 in the action %3 is of a different type than the parameter can accept.", "Severity": "Warning", "NumberOfArgs": 3, "ParamTypes": [ "string", "string", "string" ], "Resolution": "Correct the value for the parameter in the request body and resubmit the request if the operation failed." }, "ActionParameterValueFormatError": { "Description": "Indicates that a parameter was given the correct value type but the value of that parameter was not supported. This includes value size/length exceeded.", "Message": "The value %1 for the parameter %2 in the action %3 is of a different format than the parameter can accept.", "Severity": "Warning", "NumberOfArgs": 3, "ParamTypes": [ "string", "string", "string" ], "Resolution": "Correct the value for the parameter in the request body and resubmit the request if the operation failed." }, "ActionParameterNotSupported": { "Description": "Indicates that the parameter supplied for the action is not supported on the resource.", "Message": "The parameter %1 for the action %2 is not supported on the target resource.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Remove the parameter supplied and resubmit the request if the operation failed." }, "QueryParameterValueTypeError": { "Description": "Indicates that a query parameter was given the wrong value type, such as when a number is supplied for a query parameter that requires a string.", "Message": "The value %1 for the query parameter %2 is of a different type than the parameter can accept.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the query parameter in the request and resubmit the request if the operation failed." }, "QueryParameterValueFormatError": { "Description": "Indicates that a query parameter was given the correct value type but the value of that parameter was not supported. This includes value size/length exceeded.", "Message": "The value %1 for the parameter %2 is of a different format than the parameter can accept.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Correct the value for the query parameter in the request and resubmit the request if the operation failed." }, "QueryParameterOutOfRange": { "Description": "Indicates that a query parameter was supplied that is out of range for the given resource. This can happen with values that are too low or beyond that possible for the supplied resource, such as when a page is requested that is beyond the last page.", "Message": "The value %1 for the query parameter %2 is out of range %3.", "Severity": "Warning", "NumberOfArgs": 3, "ParamTypes": [ "string", "string", "string" ], "Resolution": "Reduce the value for the query parameter to a value that is within range, such as a start or count value that is within bounds of the number of resources in a collection or a page that is within the range of valid pages." }, "QueryNotSupportedOnResource": { "Description": "Indicates that query is not supported on the given resource, such as when a start/count query is attempted on a resource that is not a collection.", "Message": "Querying is not supported on the requested resource.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Remove the query parameters and resubmit the request if the operation failed." }, "QueryNotSupported": { "Description": "Indicates that query is not supported on the implementation.", "Message": "Querying is not supported by the implementation.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Remove the query parameters and resubmit the request if the operation failed." }, "SessionLimitExceeded": { "Description": "Indicates that a session establishment has been requested but the operation failed due to the number of simultaneous sessions exceeding the limit of the implementation.", "Message": "The session establishment failed due to the number of simultaneous sessions exceeding the limit of the implementation.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Reduce the number of other sessions before trying to establish the session or increase the limit of simultaneous sessions (if supported)." }, "EventSubscriptionLimitExceeded": { "Description": "Indicates that a event subscription establishment has been requested but the operation failed due to the number of simultaneous connection exceeding the limit of the implementation.", "Message": "The event subscription failed due to the number of simultaneous subscriptions exceeding the limit of the implementation.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Reduce the number of other subscriptions before trying to establish the event subscription or increase the limit of simultaneous subscriptions (if supported)." }, "ResourceCannotBeDeleted": { "Description": "Indicates that a delete operation was attempted on a resource that cannot be deleted.", "Message": "The delete request failed because the resource requested cannot be deleted.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Do not attempt to delete a non-deletable resource." }, "ResourceInUse": { "Description": "Indicates that a change was requested to a resource but the change was rejected due to the resource being in use or transition.", "Message": "The change to the requested resource failed because the resource is in use or in transition.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Remove the condition and resubmit the request if the operation failed." }, "ResourceAlreadyExists": { "Description": "Indicates that a resource change or creation was attempted but that the operation cannot proceed because the resource already exists.", "Message": "The requested resource of type %1 with the property %2 with the value %3 already exists.", "Severity": "Critical", "NumberOfArgs": 3, "ParamTypes": [ "string", "string", "string" ], "Resolution": "Do not repeat the create operation as the resource has already been created." }, "ResourceNotFound": { "Description": "Indicates that the operation expected a resource identifier that corresponds to an existing resource but one was not found.", "Message": "The requested resource of type %1 named %2 was not found.", "Severity": "Critical", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Provide a valid resource identifier and resubmit the request." }, "CreateFailedMissingReqProperties": { "Description": "Indicates that a create was attempted on a resource but that properties that are required for the create operation were missing from the request.", "Message": "The create operation failed because the required property %1 was missing from the request.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Correct the body to include the required property with a valid value and resubmit the request if the operation failed." }, "CreateLimitReachedForResource": { "Description": "Indicates that no more resources can be created on the resource as it has reached its create limit.", "Message": "The create operation failed because the resource has reached the limit of possible resources.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Either delete resources and resubmit the request if the operation failed or do not resubmit the request." }, "ServiceShuttingDown": { "Description": "Indicates that the operation failed as the service is shutting down, such as when the service reboots.", "Message": "The operation failed because the service is shutting down and can no longer take incoming requests.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "When the service becomes available, resubmit the request if the operation failed." }, "ServiceInUnknownState": { "Description": "Indicates that the operation failed because the service is in an unknown state and cannot accept additional requests.", "Message": "The operation failed because the service is in an unknown state and can no longer take incoming requests.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Restart the service and resubmit the request if the operation failed." }, "NoValidSession": { "Description": "Indicates that the operation failed because a valid session is required in order to access any resources.", "Message": "There is no valid session established with the implementation.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Establish as session before attempting any operations." }, "InsufficientPrivilege": { "Description": "Indicates that the credentials associated with the established session do not have sufficient privileges for the requested operation", "Message": "There are insufficient privileges for the account or credentials associated with the current session to perform the requested operation.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Either abandon the operation or change the associated access rights and resubmit the request if the operation failed." }, "AccountModified": { "Description": "Indicates that the account was successfully modified.", "Message": "The account was successfully modified.", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "No resolution is required." }, "AccountNotModified": { "Description": "Indicates that the modification requested for the account was not successful.", "Message": "The account modification request failed.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "The modification may have failed due to permission issues or issues with the request body." }, "AccountRemoved": { "Description": "Indicates that the account was successfully removed.", "Message": "The account was successfully removed.", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "No resolution is required." }, "AccountForSessionNoLongerExists": { "Description": "Indicates that the account for the session has been removed, thus the session has been removed as well.", "Message": "The account for the current session has been removed, thus the current session has been removed as well.", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "Attempt to connect with a valid account." }, "InvalidObject": { "Description": "Indicates that the object in question is invalid according to the implementation. Examples include a firmware update malformed URI.", "Message": "The object at %1 is invalid.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Either the object is malformed or the URI is not correct. Correct the condition and resubmit the request if it failed." }, "InternalError": { "Description": "Indicates that the request failed for an unknown internal error but that the service is still operational.", "Message": "The request failed due to an internal service error. The service is still operational.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Resubmit the request. If the problem persists, consider resetting the service." }, "UnrecognizedRequestBody": { "Description": "Indicates that the service encountered an unrecognizable request body that could not even be interpreted as malformed JSON.", "Message": "The service detected a malformed request body that it was unable to interpret.", "Severity": "Warning", "NumberOfArgs": 0, "Resolution": "Correct the request body and resubmit the request if it failed." }, "ResourceMissingAtURI": { "Description": "Indicates that the operation expected an image or other resource at the provided URI but none was found. Examples of this are in requests that require URIs like Firmware Update.", "Message": "The resource at the URI %1 was not found.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Place a valid resource at the URI or correct the URI and resubmit the request." }, "ResourceAtUriInUnknownFormat": { "Description": "Indicates that the URI was valid but the resource or image at that URI was in a format not supported by the service.", "Message": "The resource at %1 is in a format not recognized by the service.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Place an image or resource or file that is recognized by the service at the URI." }, "ResourceAtUriUnauthorized": { "Description": "Indicates that the attempt to access the resource/file/image at the URI was unauthorized.", "Message": "While accessing the resource at %1, the service received an authorization error %2.", "Severity": "Critical", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Ensure that the appropriate access is provided for the service in order for it to access the URI." }, "CouldNotEstablishConnection": { "Description": "Indicates that the attempt to access the resource/file/image at the URI was unsuccessful because a session could not be established.", "Message": "The service failed to establish a connection with the URI %1.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Ensure that the URI contains a valid and reachable node name, protocol information and other URI components." }, "SourceDoesNotSupportProtocol": { "Description": "Indicates that while attempting to access, connect to or transfer a resource/file/image from another location that the other end of the connection did not support the protocol", "Message": "The other end of the connection at %1 does not support the specified protocol %2.", "Severity": "Critical", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Change protocols or URIs. " }, "AccessDenied": { "Description": "Indicates that while attempting to access, connect to or transfer to/from another resource, the service denied access.", "Message": "While attempting to establish a connection to %1, the service denied access.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Attempt to ensure that the URI is correct and that the service has the appropriate credentials." }, "ServiceTemporarilyUnavailable": { "Description": "Indicates the service is temporarily unavailable.", "Message": "The service is temporarily unavailable. Retry in %1 seconds.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Wait for the indicated retry duration and retry the operation." }, "InvalidIndex": { "Description": "The Index is not valid.", "Message": "The Index %1 is not a valid offset into the array.", "Severity": "Warning", "NumberOfArgs": 1, "ParamTypes": [ "number" ], "Resolution": "Verify the index value provided is within the bounds of the array." }, "PropertyValueModified": { "Description": "Indicates that a property was given the correct value type but the value of that property was modified. Examples are truncated or rounded values.", "Message": "The property %1 was assigned the value %2 due to modification by the service.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "No resolution is required." }, "ResourceInStandby": { "Description": "Indicates that the request could not be performed because the resource is in standby.", "Message": "The request could not be performed because the resource is in standby.", "Severity": "Critical", "NumberOfArgs": 0, "Resolution": "Ensure that the resource is in the correct power state and resubmit the request." }, "ResourceExhaustion": { "Description": "Indicates that a resource could not satisfy the request due to some unavailability of resources. An example is that available capacity has been allocated.", "Message": "The resource %1 was unable to satisfy the request due to unavailability of resources.", "Severity": "Critical", "NumberOfArgs": 1, "ParamTypes": [ "string" ], "Resolution": "Ensure that the resources are available and resubmit the request." }, "StringValueTooLong": { "Description": "Indicates that a string value passed to the given resource exceeded its length limit. An example is when a shorter limit is imposed by an implementation than that allowed by the specification.", "Message": "The string %1 exceeds the length limit %2.", "Severity": "Warning", "NumberOfArgs": 2, "ParamTypes": [ "string", "number" ], "Resolution": "Resubmit the request with an appropriate string length." }, "SessionTerminated": { "Description": "Indicates that the DELETE operation on the Session resource resulted in the successful termination of the session.", "Message": "The session was successfully terminated.", "Severity": "OK", "NumberOfArgs": 0, "Resolution": "No resolution is required." }, "ResourceTypeIncompatible": { "Description": "Indicates that the resource type of the operation does not match that for the operation destination. Examples of when this can happen include during a POST to a collection using the wrong resource type, an update where the @odata.types do not match or on a major version incompatibility.", "Message": "The @odata.type of the request body %1 is incompatible with the @odata.type of the resource which is %2.", "Severity": "Critical", "NumberOfArgs": 2, "ParamTypes": [ "string", "string" ], "Resolution": "Resubmit the request with a payload compatible with the resource's schema." } } } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/taskmonitor.py0000664000175000017500000001712400000000000017023 0ustar00zuulzuul00000000000000# Copyright (c) 2021 Dell, Inc. or its subsidiaries # # 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 datetime import datetime from http import client as http_client import logging import time from urllib.parse import urljoin from dateutil import parser from sushy import exceptions from sushy.resources.taskservice import task LOG = logging.getLogger(__name__) class TaskMonitor: def __init__(self, connector, task_monitor_uri, redfish_version=None, registries=None, response=None): """A class representing a task monitor :param connector: A Connector instance :param task_monitor_uri: The task monitor URI :param redfish_version: The version of Redfish. Used to construct the object according to schema of the given version. :param registries: Dict of Redfish Message Registry objects to be used in any resource that needs registries to parse messages. :param response: Raw response """ self._connector = connector self._task_monitor_uri = task_monitor_uri self._redfish_version = redfish_version self._registries = registries self._task = None self._response = response if (self._response and self._response.content and self._response.status_code == http_client.ACCEPTED): self._task = task.Task(self._connector, self._task_monitor_uri, redfish_version=self._redfish_version, registries=self._registries, json_doc=self._response.json()) else: self.refresh() def refresh(self): """Refresh the Task Freshly retrieves/fetches the Task. :raises: ResourceNotFoundError :raises: ConnectionError :raises: HTTPError """ self._response = self._connector.get(path=self.task_monitor_uri) if self._response.status_code == http_client.ACCEPTED: # A Task should have been returned, but wasn't if not self._response.content: self._task = None return # Assume that the body contains a Task since we got a 202 if not self._task: self._task = task.Task(self._connector, self._task_monitor_uri, redfish_version=self._redfish_version, registries=self._registries, json_doc=self._response.json()) else: self._task.refresh(json_doc=self._response.json()) else: self._task = None @property def task_monitor_uri(self): """The TaskMonitor URI :returns: The TaskMonitor URI. """ return self._task_monitor_uri @property def is_processing(self): """Indicates if the task is still processing :returns: A boolean indicating if the task is still processing. """ return self._response.status_code == http_client.ACCEPTED @property def check_is_processing(self): """Refreshes task and check if it is still processing :returns: A boolean indicating if the task is still processing. """ if not self.is_processing: return False self.refresh() return self.is_processing @property def sleep_for(self): """Seconds the client should wait before querying the operation status Defaults to 1 second if Retry-After not specified in response. :returns: The number of seconds to wait """ retry_after = self._response.headers.get('Retry-After') if retry_after is None: return 1 if isinstance(retry_after, int) or retry_after.isdigit(): return retry_after return max(0, (parser.parse(retry_after) - datetime.now().astimezone()).total_seconds()) @property def cancellable(self): """The amount of time to sleep before retrying :returns: A Boolean indicating if the Task is cancellable. """ allow = self._response.headers.get('Allow') cancellable = False if allow and allow.upper() == 'DELETE': cancellable = True return cancellable @property def task(self): """The executing task :returns: The Task being executed. """ return self._task @property def response(self): """Unprocessed response. Intended to be used internally. :returns: Unprocessed response. """ return self._response def get_task(self): """Construct Task instance from task monitor URI. :returns: Task instance. """ return task.Task(self._connector, self._task_monitor_uri, redfish_version=self._redfish_version, registries=self._registries) def wait(self, timeout_sec): """Waits until task is completed or it times out. :param timeout_sec: Timeout to wait :raises: ConnectionError when times out """ timeout_at = time.time() + timeout_sec while self.check_is_processing: LOG.debug('Waiting for task monitor %(url)s; sleeping for ' '%(sleep)s seconds', {'url': self.task_monitor_uri, 'sleep': self.sleep_for}) time.sleep(self.sleep_for) if time.time() >= timeout_at and self.check_is_processing: m = (f'Timeout waiting for task monitor ' f'{self.task_monitor_uri} (timeout = {timeout_sec})') raise exceptions.ConnectionError(url=self.task_monitor_uri, error=m) @staticmethod def from_response(conn, response, target_uri, redfish_version=None, registries=None): """Construct TaskMonitor instance from received response. :response: Unprocessed response :target_uri: URI used to initiate async operation :redfish_version: Redfish version. Optional when used internally. :registries: Redfish registries. Optional when used internally. :returns: TaskMonitor instance :raises: MissingHeaderError if Location is missing in response """ json_data = response.json() if response.content else {} header = 'Location' task_monitor_uri = response.headers.get(header) task_uri_data = json_data.get('@odata.id') if task_uri_data: task_monitor_uri = urljoin(task_monitor_uri, task_uri_data) if not task_monitor_uri: raise exceptions.MissingHeaderError(target_uri=target_uri, header=header) return TaskMonitor(conn, task_monitor_uri, redfish_version=redfish_version, registries=registries, response=response) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6595147 sushy-5.5.0/sushy/tests/0000775000175000017500000000000000000000000015234 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/tests/__init__.py0000664000175000017500000000000000000000000017333 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6635146 sushy-5.5.0/sushy/tests/unit/0000775000175000017500000000000000000000000016213 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/tests/unit/__init__.py0000664000175000017500000000000000000000000020312 0ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/tests/unit/base.py0000664000175000017500000000140000000000000017472 0ustar00zuulzuul00000000000000 # Copyright 2010-2011 OpenStack Foundation # 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. from oslotest import base class TestCase(base.BaseTestCase): """Test case base class for all unit tests""" ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6835146 sushy-5.5.0/sushy/tests/unit/json_samples/0000775000175000017500000000000000000000000020710 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/tests/unit/json_samples/TestRegistry.zip0000664000175000017500000000115600000000000024107 0ustar00zuulzuul00000000000000PK}M Test.2.0.jsonUT Bh[Bh[Bh[ux Sn0T* {JU[@CU%YFC!΋GAmQ33-s#C4tI5852ڨ]uske`?'mܮ^U*J./(XbDU} (4\\!TɄ\E!a.DG); VTÆR]脔.70V' 7iFgT`iH[`W8  O9H&0x.%t/\qycǪd1#5*i'~};6lv#³F~DTrIʤLOn4yo?x main # and ultimately we want to make sure we get an error all # the way back. self.conn._auth.authenticate.side_effect = \ exceptions.AccessError('GET', '/', mock_response) self.request.return_value.status_code = http_client.FORBIDDEN self.request.return_value.json.side_effect = value_error with self.assertRaisesRegex(exceptions.AccessError, 'unknown error') as cm: self.conn._op('GET', 'http://foo.bar') exc = cm.exception self.assertEqual(http_client.FORBIDDEN, exc.status_code) self.conn._auth.authenticate.assert_called_once() def test_access_error_without_auth(self): self.conn._auth = None self.request.return_value.status_code = http_client.FORBIDDEN self.request.return_value.json.side_effect = ValueError('no json') with self.assertRaisesRegex(exceptions.AccessError, 'unknown error') as cm: self.conn._op('GET', 'http://foo.bar') exc = cm.exception self.assertEqual(http_client.FORBIDDEN, exc.status_code) @mock.patch.object(connector.LOG, 'debug', autospec=True) def test_access_error_service_session_reauth(self, mock_log): self.conn._auth.can_refresh_session.return_value = False self.request.return_value.status_code = http_client.FORBIDDEN mock_response = mock.Mock() mock_response.json.side_effect = ValueError('no json') mock_response.status_code = http_client.FORBIDDEN self.session.auth = None self.conn._session = self.session self.conn._auth.authenticate.side_effect = \ exceptions.AccessError('POST', 'fake/path', mock_response) self.request.return_value.json.side_effect = ValueError('no json') with self.assertRaisesRegex(exceptions.AccessError, 'unknown error') as cm: self.conn._op('GET', 'http://redfish/v1/SessionService') exc = cm.exception self.assertEqual(http_client.FORBIDDEN, exc.status_code) self.conn._auth.authenticate.assert_called_once() @mock.patch.object(connector.LOG, 'debug', autospec=True) def test_access_error_service_session_no_auth(self, mock_log): self.conn._auth = None self.request.return_value.status_code = http_client.FORBIDDEN self.request.return_value.json.side_effect = ValueError('no json') with self.assertRaisesRegex(exceptions.AccessError, 'unknown error') as cm: self.conn._op('GET', 'http://redfish/v1/SessionService') exc = cm.exception mock_log.assert_called_with( 'HTTP GET of SessionService failed %s, ' 'this is expected prior to authentication', 'HTTP GET ' 'http://redfish/v1/SessionService returned code ' f'{http_client.FORBIDDEN!s}. unknown error Extended ' 'information: None') self.assertEqual(http_client.FORBIDDEN, exc.status_code) def test_blocking_no_location_header(self): self.request.return_value.status_code = http_client.ACCEPTED self.request.return_value.headers = {'retry-after': 5} with self.assertRaisesRegex(exceptions.ConnectionError, 'status 202, but no Location header'): self.conn._op('POST', 'http://foo.bar', blocking=True) @mock.patch('sushy.connector.time.sleep', autospec=True) def test_blocking_task_fails(self, mock_sleep): response1 = mock.MagicMock(spec=requests.Response) response1.status_code = http_client.ACCEPTED response1.headers = { 'Retry-After': 5, 'Location': '/redfish/v1/taskmon/1', 'Content-Length': 10 } response1.json.return_value = {'Id': 3, 'Name': 'Test'} response2 = mock.MagicMock(spec=requests.Response) response2.status_code = http_client.BAD_REQUEST message = 'Unable to create Volume with given parameters' response2.json.return_value = { 'error': { 'message': message } } self.request.side_effect = [response1, response1, response2] with self.assertRaisesRegex(exceptions.BadRequestError, message): self.conn._op('POST', 'http://foo.bar', blocking=True) @mock.patch.object(connector.LOG, 'warning', autospec=True) def test_access_error_basic_auth(self, mock_log): self.conn._auth.can_refresh_session.return_value = False self.conn._session.auth = ('foo', 'bar') self.request.return_value.status_code = 403 mock_response = mock.Mock() mock_response.json.side_effect = ValueError('no json') mock_response.status_code = 403 self.request.return_value.json.side_effect = ValueError('no json') with self.assertRaisesRegex(exceptions.AccessError, 'unknown error') as cm: self.conn._op('GET', 'http://redfish/v1/SessionService') exc = cm.exception self.assertEqual(http_client.FORBIDDEN, exc.status_code) self.conn._auth.authenticate.assert_not_called() error_msg = (f'HTTP GET http://redfish/v1/SessionService ' f'returned code {exc.status_code}. unknown error ' f'Extended information: None') mock_log.assert_called_once_with( "We have encountered an AccessError when using 'basic' " "authentication. %(err)s", {'err': error_msg} ) def test__op_raises_connection_error(self): exception_list = [ requests.exceptions.ChunkedEncodingError, requests.exceptions.ContentDecodingError, requests.exceptions.ReadTimeout, requests.exceptions.ConnectTimeout, requests.exceptions.SSLError, requests.exceptions.ConnectionError, requests.exceptions.RequestException, ] for exc in exception_list: self.request.side_effect = exc self.assertRaises(exceptions.ConnectionError, self.conn._op, 'POST', 'http://foo.bar', blocking=True) @mock.patch.object(connector.Connector, '_op', autospec=True) def test_etag_handler_strong_etag_ok(self, mock__op): self.headers['If-Match'] = '"3d7b8a7360bf2941d"' response = mock.MagicMock(spec=requests.Response) response.status_code = http_client.OK data = {'Boot': {'BootSourceOverrideTarget': 'Cd', 'BootSourceOverrideEnabled': 'Once'}} mock__op.return_value = response self.conn._etag_handler(path='/redfish/v1/Systems/1', headers=self.headers, data=data, etag=self.headers['If-Match'], blocking=False, timeout=60) mock__op.assert_called_once_with( self.conn, "PATCH", '/redfish/v1/Systems/1', data={'Boot': {'BootSourceOverrideTarget': 'Cd', 'BootSourceOverrideEnabled': 'Once'}}, headers=dict(self.headers, **{'If-Match': '"3d7b8a7360bf2941d"'}), blocking=False, timeout=60) @mock.patch.object(connector.Connector, '_op', autospec=True) def test_etag_handler_weak_etag_ok(self, mock__op): self.headers['If-Match'] = 'W/"3d7b8a7360bf2941d"' response = mock.MagicMock(spec=requests.Response) response.status_code = http_client.OK data = {'Boot': {'BootSourceOverrideTarget': 'Cd', 'BootSourceOverrideEnabled': 'Once'}} mock__op.return_value = response self.conn._etag_handler(path='/redfish/v1/Systems/1', headers=self.headers, data=data, etag=self.headers['If-Match'], blocking=False, timeout=60) mock__op.assert_called_once_with( self.conn, "PATCH", '/redfish/v1/Systems/1', data={'Boot': {'BootSourceOverrideTarget': 'Cd', 'BootSourceOverrideEnabled': 'Once'}}, headers=dict(self.headers, **{'If-Match': 'W/"3d7b8a7360bf2941d"'}), blocking=False, timeout=60) @mock.patch.object(connector.Connector, '_op', autospec=True) def test_etag_handler_no_etag_ok(self, mock__op): response = mock.MagicMock(spec=requests.Response) response.status_code = http_client.OK data = {'Boot': {'BootSourceOverrideTarget': 'Cd', 'BootSourceOverrideEnabled': 'Once'}} mock__op.return_value = response self.conn._etag_handler(path='/redfish/v1/Systems/1', headers=self.headers, data=data, blocking=False, timeout=60) mock__op.assert_called_once_with( self.conn, "PATCH", '/redfish/v1/Systems/1', data={'Boot': {'BootSourceOverrideTarget': 'Cd', 'BootSourceOverrideEnabled': 'Once'}}, headers=self.headers, blocking=False, timeout=60) @mock.patch.object(connector.Connector, '_op', autospec=True) def test_etag_handler_weak_etag_fallback_to_strong(self, mock__op): self.headers['If-Match'] = 'W/"3d7b8a7360bf2941d"' target_uri = '/redfish/v1/Systems/1' response_412 = mock.MagicMock(spec=requests.Response) response_412.status_code = http_client.PRECONDITION_FAILED response_200 = mock.MagicMock(spec=requests.Response) response_200.status_code = http_client.OK mock__op.side_effect = [ exceptions.HTTPError(method='PATCH', url=target_uri, response=response_412), response_200] data = {'Boot': {'BootSourceOverrideTarget': 'Cd', 'BootSourceOverrideEnabled': 'Once'}} self.conn._etag_handler(path=target_uri, headers=self.headers, data=data, etag=self.headers['If-Match'], blocking=False, timeout=60) mock__op.assert_called_with( self.conn, "PATCH", '/redfish/v1/Systems/1', data={'Boot': {'BootSourceOverrideTarget': 'Cd', 'BootSourceOverrideEnabled': 'Once'}}, headers=dict(self.headers, **{'If-Match': '"3d7b8a7360bf2941d"'}), blocking=False, timeout=60) @mock.patch.object(connector.Connector, '_op', autospec=True) def test_etag_handler_weak_etag_fallback_to_no_etag(self, mock__op): self.headers['If-Match'] = 'W/"3d7b8a7360bf2941d"' target_uri = '/redfish/v1/Systems/1' response_412 = mock.MagicMock(spec=requests.Response) response_412.status_code = http_client.PRECONDITION_FAILED response_200 = mock.MagicMock(spec=requests.Response) response_200.status_code = http_client.OK mock__op.side_effect = [ exceptions.HTTPError(method='PATCH', url=target_uri, response=response_412), exceptions.HTTPError(method='PATCH', url=target_uri, response=response_412), response_200] data = {'Boot': {'BootSourceOverrideTarget': 'Cd', 'BootSourceOverrideEnabled': 'Once'}} self.conn._etag_handler(path=target_uri, headers=self.headers, data=data, etag=self.headers['If-Match'], blocking=False, timeout=60) mock__op.assert_called_with( self.conn, "PATCH", '/redfish/v1/Systems/1', data={'Boot': {'BootSourceOverrideTarget': 'Cd', 'BootSourceOverrideEnabled': 'Once'}}, headers=self.headers, blocking=False, timeout=60) @mock.patch.object(connector.Connector, '_op', autospec=True) def test_etag_handler_strong_etag_fallback_to_no_etag(self, mock__op): self.headers['If-Match'] = '"3d7b8a7360bf2941d"' target_uri = '/redfish/v1/Systems/1' response_412 = mock.MagicMock(spec=requests.Response) response_412.status_code = http_client.PRECONDITION_FAILED response_200 = mock.MagicMock(spec=requests.Response) response_200.status_code = http_client.OK mock__op.side_effect = [ exceptions.HTTPError(method='PATCH', url=target_uri, response=response_412), response_200] data = {'Boot': {'BootSourceOverrideTarget': 'Cd', 'BootSourceOverrideEnabled': 'Once'}} self.conn._etag_handler(path=target_uri, headers=self.headers, data=data, etag=self.headers['If-Match'], blocking=False, timeout=60) mock__op.assert_called_with( self.conn, "PATCH", '/redfish/v1/Systems/1', data={'Boot': {'BootSourceOverrideTarget': 'Cd', 'BootSourceOverrideEnabled': 'Once'}}, headers=self.headers, blocking=False, timeout=60) @mock.patch.object(connector.Connector, '_op', autospec=True) def test_etag_handler_fail_other_exception(self, mock__op): self.headers['If-Match'] = 'W/"3d7b8a7360bf2941d"' target_uri = '/redfish/v1/Systems/1' data = {'Boot': {'BootSourceOverrideTarget': 'Cd', 'BootSourceOverrideEnabled': 'Once'}} response = mock.MagicMock(spec=requests.Response) response.status_code = http_client.FORBIDDEN response.message = "boom" mock__op.return_value = response mock__op.side_effect = \ exceptions.HTTPError(method='PATCH', url=target_uri, response=response) self.assertRaises(exceptions.HTTPError, self.conn._etag_handler, 'PATCH', target_uri, data, self.headers, blocking=False, timeout=60) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/tests/unit/test_main.py0000664000175000017500000006751300000000000020564 0ustar00zuulzuul00000000000000# Copyright 2017 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 json from unittest import mock from sushy import auth from sushy import connector from sushy import exceptions from sushy import main from sushy.resources.chassis import chassis from sushy.resources.compositionservice import compositionservice from sushy.resources.eventservice import eventservice from sushy.resources.fabric import fabric from sushy.resources.manager import manager from sushy.resources.registry import message_registry_file from sushy.resources.sessionservice import session from sushy.resources.sessionservice import sessionservice from sushy.resources.system import system from sushy.resources.updateservice import updateservice from sushy import taskmonitor from sushy.tests.unit import base class MainTestCase(base.TestCase): @mock.patch.object(auth, 'SessionOrBasicAuth', autospec=True) @mock.patch.object(connector, 'Connector', autospec=True) @mock.patch.object(sessionservice, 'SessionService', autospec=True) def setUp(self, mock_session_service, mock_connector, mock_auth): super().setUp() self.conn = mock.Mock() self.sess_serv = mock.Mock() self.sess_serv.create_session.return_value = (None, None) mock_session_service.return_value = self.sess_serv mock_connector.return_value = self.conn with open('sushy/tests/unit/json_samples/root.json') as f: self.json_doc = json.load(f) self.conn.get.return_value.json.return_value = self.json_doc self.root = main.Sushy('http://foo.bar:1234', verify=True, auth=mock_auth) mock_connector.assert_called_once_with( 'http://foo.bar:1234', verify=True, server_side_retries=10, server_side_retries_delay=3) def test__parse_attributes(self): self.root._parse_attributes(self.json_doc) self.assertEqual('RootService', self.root.identity) self.assertEqual('Root Service', self.root.name) self.assertEqual('1.0.2', self.root.redfish_version) self.assertEqual('92384634-2938-2342-8820-489239905423', self.root.uuid) self.assertEqual('Product', self.root.product) self.assertTrue(self.root.protocol_features_supported.excerpt_query) self.assertFalse(self.root.protocol_features_supported.expand_query) self.assertTrue(self.root.protocol_features_supported.filter_query) self.assertTrue( self.root.protocol_features_supported.only_member_query) self.assertFalse(self.root.protocol_features_supported.select_query) self.assertEqual('/redfish/v1/Systems', self.root._systems_path) self.assertEqual('/redfish/v1/Managers', self.root._managers_path) self.assertEqual('/redfish/v1/Chassis', self.root._chassis_path) self.assertEqual('/redfish/v1/Fabrics', self.root._fabrics_path) self.assertEqual('/redfish/v1/EventService', self.root._event_service_path) self.assertEqual('/redfish/v1/SessionService', self.root._session_service_path) self.assertEqual('/redfish/v1/CompositionService', self.root._composition_service_path) @mock.patch.object(connector, 'Connector', autospec=True) def test__init_throws_exception(self, mock_Connector): self.assertRaises( ValueError, main.Sushy, 'http://foo.bar:1234', 'foo', 'bar', auth=mock.MagicMock()) @mock.patch.object(connector, 'Connector', autospec=True) def test_custom_connector(self, mock_Sushy_Connector): connector_mock = mock.MagicMock() with open('sushy/tests/unit/json_samples/root.json') as f: connector_mock.get.return_value.json.return_value = ( json.load(f)) main.Sushy('http://foo.bar:1234', 'foo', 'bar', connector=connector_mock) self.assertTrue(connector_mock.post.called) self.assertTrue(connector_mock.get.called) self.assertFalse(mock_Sushy_Connector.called) @mock.patch.object(system, 'SystemCollection', autospec=True) @mock.patch('sushy.Sushy.lazy_registries', autospec=True) def test_get_system_collection( self, mock_lazy_registries, mock_system_collection): self.root._standard_message_registries_path = None self.root.get_system_collection() mock_system_collection.assert_called_once_with( self.root._conn, '/redfish/v1/Systems', redfish_version=self.root.redfish_version, registries=mock_lazy_registries, root=self.root ) @mock.patch.object(system, 'System', autospec=True) @mock.patch('sushy.Sushy.lazy_registries', autospec=True) def test_get_system(self, mock_lazy_registries, mock_system): self.root._standard_message_registries_path = None self.root.get_system('fake-system-id') mock_system.assert_called_once_with( self.root._conn, 'fake-system-id', redfish_version=self.root.redfish_version, registries=mock_lazy_registries, root=self.root) @mock.patch.object(system, 'SystemCollection', autospec=True) @mock.patch.object(system, 'System', autospec=True) @mock.patch('sushy.Sushy.lazy_registries', autospec=True) def test_get_system_default_ok( self, mock_lazy_registries, mock_system, mock_system_collection): self.root._standard_message_registries_path = None mock_system.path = 'fake-system-id' mock_members = mock_system_collection.return_value.get_members mock_members.return_value = [mock_system] self.root.get_system() mock_system_collection.assert_called_once_with( self.root._conn, '/redfish/v1/Systems', redfish_version=self.root.redfish_version, registries=mock_lazy_registries, root=self.root ) mock_system.assert_called_once_with( self.root._conn, 'fake-system-id', redfish_version=self.root.redfish_version, registries=mock_lazy_registries, root=self.root) @mock.patch.object(system, 'SystemCollection', autospec=True) @mock.patch.object(system, 'System', autospec=True) @mock.patch('sushy.Sushy.lazy_registries', autospec=True) def test_get_system_default_failure( self, mock_lazy_registries, mock_system, mock_system_collection): self.root._standard_message_registries_path = None mock_members = mock_system_collection.return_value.get_members mock_members.return_value = [] self.assertRaises(exceptions.UnknownDefaultError, self.root.get_system) mock_system_collection.assert_called_once_with( self.root._conn, '/redfish/v1/Systems', redfish_version=self.root.redfish_version, registries=mock_lazy_registries, root=self.root ) @mock.patch.object(chassis, 'Chassis', autospec=True) def test_get_chassis(self, mock_chassis): self.root.get_chassis('fake-chassis-id') mock_chassis.assert_called_once_with( self.root._conn, 'fake-chassis-id', self.root.redfish_version, self.root.lazy_registries, self.root) @mock.patch.object(chassis, 'ChassisCollection', autospec=True) @mock.patch.object(chassis, 'Chassis', autospec=True) @mock.patch('sushy.Sushy.lazy_registries', autospec=True) def test_get_chassis_default_ok( self, mock_lazy_registries, mock_chassis, mock_chassis_collection): self.root._standard_message_registries_path = None mock_chassis.path = 'fake-chassis-id' mock_members = mock_chassis_collection.return_value.get_members mock_members.return_value = [mock_chassis] self.root.get_chassis() mock_chassis_collection.assert_called_once_with( self.root._conn, '/redfish/v1/Chassis', redfish_version=self.root.redfish_version, registries=mock_lazy_registries, root=self.root ) mock_chassis.assert_called_once_with( self.root._conn, 'fake-chassis-id', redfish_version=self.root.redfish_version, registries=mock_lazy_registries, root=self.root ) @mock.patch.object(chassis, 'ChassisCollection', autospec=True) @mock.patch.object(chassis, 'Chassis', autospec=True) @mock.patch('sushy.Sushy.lazy_registries', autospec=True) def test_get_chassis_default_failure( self, mock_lazy_registries, mock_chassis, mock_chassis_collection): self.root._standard_message_registries_path = None mock_members = mock_chassis_collection.return_value.get_members mock_members.return_value = [] self.assertRaises( exceptions.UnknownDefaultError, self.root.get_chassis) mock_chassis_collection.assert_called_once_with( self.root._conn, '/redfish/v1/Chassis', redfish_version=self.root.redfish_version, registries=mock_lazy_registries, root=self.root ) @mock.patch.object(chassis, 'ChassisCollection', autospec=True) def test_get_chassis_collection(self, chassis_collection_mock): self.root.get_chassis_collection() chassis_collection_mock.assert_called_once_with( self.root._conn, '/redfish/v1/Chassis', self.root.redfish_version, self.root.lazy_registries, self.root) @mock.patch.object(fabric, 'Fabric', autospec=True) def test_get_fabric(self, mock_fabric): self.root.get_fabric('fake-fabric-id') mock_fabric.assert_called_once_with( self.root._conn, 'fake-fabric-id', self.root.redfish_version, self.root.lazy_registries, self.root) @mock.patch.object(fabric, 'FabricCollection', autospec=True) def test_get_fabric_collection(self, fabric_collection_mock): self.root.get_fabric_collection() fabric_collection_mock.assert_called_once_with( self.root._conn, '/redfish/v1/Fabrics', self.root.redfish_version, self.root.lazy_registries, self.root) @mock.patch.object(manager, 'ManagerCollection', autospec=True) def test_get_manager_collection(self, ManagerCollection_mock): self.root.get_manager_collection() ManagerCollection_mock.assert_called_once_with( self.root._conn, '/redfish/v1/Managers', self.root.redfish_version, self.root.lazy_registries, self.root) @mock.patch.object(manager, 'Manager', autospec=True) def test_get_manager(self, Manager_mock): self.root.get_manager('fake-manager-id') Manager_mock.assert_called_once_with( self.root._conn, 'fake-manager-id', self.root.redfish_version, self.root.lazy_registries, self.root) @mock.patch.object(manager, 'ManagerCollection', autospec=True) @mock.patch.object(manager, 'Manager', autospec=True) @mock.patch('sushy.Sushy.lazy_registries', autospec=True) def test_get_manager_default_ok( self, mock_lazy_registries, mock_manager, mock_manager_collection): self.root._standard_message_registries_path = None mock_manager.path = 'fake-manager-id' mock_members = mock_manager_collection.return_value.get_members mock_members.return_value = [mock_manager] self.root.get_manager() mock_manager_collection.assert_called_once_with( self.root._conn, '/redfish/v1/Managers', redfish_version=self.root.redfish_version, registries=mock_lazy_registries, root=self.root ) mock_manager.assert_called_once_with( self.root._conn, 'fake-manager-id', redfish_version=self.root.redfish_version, registries=mock_lazy_registries, root=self.root) @mock.patch.object(manager, 'ManagerCollection', autospec=True) @mock.patch.object(manager, 'Manager', autospec=True) @mock.patch('sushy.Sushy.lazy_registries', autospec=True) def test_get_manager_default_failure( self, mock_lazy_registries, mock_manager, mock_system_collection): self.root._standard_message_registries_path = None mock_members = mock_system_collection.return_value.get_members mock_members.return_value = [] self.assertRaises( exceptions.UnknownDefaultError, self.root.get_manager) mock_system_collection.assert_called_once_with( self.root._conn, '/redfish/v1/Managers', redfish_version=self.root.redfish_version, registries=mock_lazy_registries, root=self.root ) @mock.patch.object(sessionservice, 'SessionService', autospec=True) def test_get_sessionservice(self, mock_sess_serv): self.root.get_session_service() mock_sess_serv.assert_called_once_with( self.root._conn, '/redfish/v1/SessionService', redfish_version=self.root.redfish_version, root=self.root) @mock.patch.object(session, 'Session', autospec=True) def test_get_session(self, mock_sess): self.root.get_session('asdf') mock_sess.assert_called_once_with( self.root._conn, 'asdf', self.root.redfish_version, self.root.lazy_registries, self.root) def test_create_session(self): self.root._conn._session.headers = [] self.root._conn._sessions_uri = None with open('sushy/tests/unit/json_samples/' 'session_creation_headers.json') as f: self.conn.post.return_value.headers = json.load(f) session_key, session_uri = ( self.root.create_session('foo', 'secret')) self.assertEqual('adc530e2016a0ea98c76c087f0e4b76f', session_key) self.assertEqual( '/redfish/v1/SessionService/Sessions/151edd65d41c0b89', session_uri) self.assertEqual('/redfish/v1/SessionService/Sessions', self.root._conn._sessions_uri) def test_create_session_removes_auth_data(self): self.root._conn._session.headers = {'X-Auth-Token': 'meow'} self.root._conn._session.auth = 'meow' with open('sushy/tests/unit/json_samples/' 'session_creation_headers.json') as f: self.conn.post.return_value.headers = json.load(f) session_key, session_uri = ( self.root.create_session('foo', 'secret')) self.assertEqual('adc530e2016a0ea98c76c087f0e4b76f', session_key) self.assertEqual( '/redfish/v1/SessionService/Sessions/151edd65d41c0b89', session_uri) self.assertIsNone(self.root._conn._session.auth) self.assertNotIn('X-Auth-Token', self.root._conn._session.headers) def test_create_session_no_session_path(self): self.root._conn._session.headers = [] mock_get_session_path = mock.Mock() mock_get_session_path.side_effect = exceptions.MissingAttributeError() self.root.get_sessions_path = mock_get_session_path with open('sushy/tests/unit/json_samples/' 'session_creation_headers.json') as f: self.conn.post.return_value.headers = json.load(f) session_key, session_uri = ( self.root.create_session('foo', 'secret')) self.assertEqual('adc530e2016a0ea98c76c087f0e4b76f', session_key) self.assertEqual( '/redfish/v1/SessionService/Sessions/151edd65d41c0b89', session_uri) self.conn.post.assert_called_once_with( '/redfish/v1/SessionService/Sessions', data={'UserName': 'foo', 'Password': 'secret'}) @mock.patch.object(main, 'LOG', autospec=True) def test_create_session_no_session_path_access_error(self, mock_log): self.root._conn._session.headers = [] mock_res = mock.Mock() mock_res.status_code = 403 mock_res.json.side_effect = ValueError('no json') self.conn.post.return_value = mock_res mock_get_session_path = mock.Mock() mock_get_session_path.side_effect = exceptions.AccessError( 'GET', 'redfish/v1', mock_res) self.root.get_sessions_path = mock_get_session_path with open('sushy/tests/unit/json_samples/' 'session_creation_headers_no_location.json') as f: mock_res.headers = json.load(f) session_key, session_uri = ( self.root.create_session('foo', 'secret')) self.assertEqual('adc530e2016a0ea98c76c087f0e4b76f', session_key) self.assertIsNone(session_uri) self.conn.post.assert_called_once_with( '/redfish/v1/SessionService/Sessions', data={'UserName': 'foo', 'Password': 'secret'}) self.assertTrue(mock_log.warning.called) def test_create_session_recover_session_uri_from_body(self): self.root._conn._session.headers = [] mock_res = mock.Mock() mock_res.status_code = 200 mock_json = mock_res.json.return_value mock_session_uri = mock_json.get.return_value self.conn.post.return_value = mock_res mock_get_session_path = mock.Mock() mock_get_session_path.side_effect = exceptions.AccessError( 'GET', 'redfish/v1', mock_res) self.root.get_sessions_path = mock_get_session_path with open('sushy/tests/unit/json_samples/' 'session_creation_headers_no_location.json') as f: mock_res.headers = json.load(f) session_key, session_uri = ( self.root.create_session('foo', 'secret')) self.assertEqual('adc530e2016a0ea98c76c087f0e4b76f', session_key) self.assertEqual(session_uri, mock_session_uri) self.conn.post.assert_called_once_with( '/redfish/v1/SessionService/Sessions', data={'UserName': 'foo', 'Password': 'secret'}) mock_json.get.assert_called_with('@odata.id') def test_create_session_path_discovery(self): self.root._conn._session.headers = [] with open('sushy/tests/unit/json_samples/root.json') as f: self.conn.get.json.return_value = json.load(f) with open('sushy/tests/unit/json_samples/' 'session_creation_headers.json') as f: self.conn.post.return_value.headers = json.load(f) session_key, session_uri = ( self.root.create_session('foo', 'secret')) self.assertEqual('adc530e2016a0ea98c76c087f0e4b76f', session_key) self.assertEqual( '/redfish/v1/SessionService/Sessions/151edd65d41c0b89', session_uri) self.conn.get.assert_called_once_with(path='/redfish/v1/') self.conn.post.assert_called_once_with( '/redfish/v1/SessionService/Sessions', data={'UserName': 'foo', 'Password': 'secret'}) def test_create_session_missing_x_auth_token(self): self.root._conn._session.headers = [] with open('sushy/tests/unit/json_samples/' 'session_creation_headers.json') as f: self.conn.post.return_value.headers = json.load(f) self.conn.post.return_value.headers.pop('X-Auth-Token') self.assertRaisesRegex( exceptions.MissingXAuthToken, 'No X-Auth-Token returned', self.root.create_session, 'foo', 'bar') @mock.patch.object(updateservice, 'UpdateService', autospec=True) def test_get_update_service(self, mock_upd_serv): self.root.get_update_service() mock_upd_serv.assert_called_once_with( self.root._conn, '/redfish/v1/UpdateService', self.root.redfish_version, self.root.lazy_registries, self.root) @mock.patch.object(message_registry_file, 'MessageRegistryFileCollection', autospec=True) def test__get_registry_collection( self, MessageRegistryFileCollection_mock): self.root._get_registry_collection() MessageRegistryFileCollection_mock.assert_called_once_with( self.root._conn, '/redfish/v1/Registries', redfish_version=self.root.redfish_version, root=self.root) @mock.patch.object( compositionservice, 'CompositionService', autospec=True) def test_get_composition_service(self, mock_comp_ser): self.root.get_composition_service() mock_comp_ser.assert_called_once_with( self.root._conn, '/redfish/v1/CompositionService', self.root.redfish_version, self.root.lazy_registries, self.root) @mock.patch.object(eventservice, 'EventService', autospec=True) def test_get_event_service(self, mock_event_service): self.root.get_event_service() mock_event_service.assert_called_once_with( self.root._conn, '/redfish/v1/EventService', self.root.redfish_version, self.root.lazy_registries, self.root) def test__get_standard_message_registry_collection(self): registries = self.root._get_standard_message_registry_collection() self.assertEqual(5, len(registries)) self.assertIn('Base.1.3.0', {r.identity for r in registries}) @mock.patch('sushy.Sushy._get_standard_message_registry_collection', autospec=True) @mock.patch('sushy.Sushy._get_registry_collection', autospec=True) def test__get_message_registries(self, mock_col, mock_st_col): mock_msg_reg1 = mock.Mock() mock_msg_reg1.registry_prefix = 'RegistryA' mock_msg_reg1.registry_version = '2.0.0' mock_msg_reg1.language = 'en' mock_st_col.return_value = [mock_msg_reg1] mock_msg_reg2 = mock.Mock() mock_msg_reg2.registry_prefix = 'RegistryB' mock_msg_reg2.registry_version = '1.0.0' mock_msg_reg_file = mock.Mock() mock_msg_reg_file.identity = 'Messages' mock_msg_reg_file.registry = 'RegistryB.1.0' mock_msg_reg_file.get_message_registry.return_value = mock_msg_reg2 mock_col.return_value.get_members.return_value = [mock_msg_reg_file] registries = self.root.registries self.assertEqual({'RegistryA.2.0': mock_msg_reg1, 'RegistryB.1.0': mock_msg_reg2, 'Messages': mock_msg_reg2}, registries) @mock.patch('sushy.Sushy._get_standard_message_registry_collection', autospec=True) @mock.patch('sushy.Sushy._get_registry_collection', autospec=True) def test__get_message_registries_caching(self, mock_col, mock_st_col): mock_msg_reg1 = mock.Mock() mock_msg_reg1.registry_prefix = 'RegistryA' mock_msg_reg1.registry_version = '2.0.0' mock_msg_reg1.language = 'en' mock_st_col.return_value = [mock_msg_reg1] mock_msg_reg2 = mock.Mock() mock_msg_reg2.registry_prefix = 'RegistryB' mock_msg_reg2.registry_version = '1.0.0' mock_msg_reg_file = mock.Mock() mock_msg_reg_file.identity = 'Messages' mock_msg_reg_file.registry = 'RegistryB.1.0' mock_msg_reg_file.get_message_registry.return_value = mock_msg_reg2 mock_col.return_value.get_members.return_value = [mock_msg_reg_file] registries = self.root.registries self.assertEqual(1, mock_col.call_count) self.assertEqual(1, mock_st_col.call_count) cached_registries = self.root.registries self.assertEqual(1, mock_col.call_count) self.assertEqual(1, mock_st_col.call_count) expected = { 'RegistryA.2.0': mock_msg_reg1, 'RegistryB.1.0': mock_msg_reg2, 'Messages': mock_msg_reg2 } self.assertEqual(expected, registries) self.assertEqual(cached_registries, registries) @mock.patch('sushy.Sushy._get_standard_message_registry_collection', autospec=True) @mock.patch('sushy.Sushy._get_registry_collection', autospec=True) def test_registries_provided_empty(self, mock_col, mock_st_col): mock_msg_reg1 = mock.Mock() mock_msg_reg1.registry_prefix = 'RegistryA' mock_msg_reg1.registry_version = '2.0.0' mock_msg_reg1.language = 'en' mock_st_col.return_value = [mock_msg_reg1] mock_col.return_value = None registries = self.root.registries self.assertEqual({'RegistryA.2.0': mock_msg_reg1}, registries) @mock.patch('sushy.Sushy.registries', autospec=True) def test_lazy_registries(self, mock_registries): registries = self.root.lazy_registries self.assertEqual(0, mock_registries.__getitem__.call_count) registries[1] self.assertEqual(1, mock_registries.__getitem__.call_count) def test_get_sessions_path(self): self.root._conn._sessions_uri = None expected = '/redfish/v1/SessionService/Sessions' self.assertEqual(expected, self.root.get_sessions_path()) self.assertEqual(expected, self.root._conn._sessions_uri) @mock.patch.object(taskmonitor, 'TaskMonitor', autospec=True) def test_get_task_monitor(self, mock_task_mon): self.root.get_task_monitor('/TaskService/Task/123') mock_task_mon.assert_called_once_with( self.root._conn, '/TaskService/Task/123', self.root.redfish_version, self.root.lazy_registries) class BareMinimumMainTestCase(base.TestCase): def setUp(self): super().setUp() self.conn = mock.MagicMock() with open('sushy/tests/unit/json_samples/' 'bare_minimum_root.json') as f: self.conn.get.return_value.json.return_value = json.load(f) self.root = main.Sushy('http://foo.bar:1234', verify=True, auth=mock.MagicMock(), connector=self.conn) def test_get_system_collection_when_systems_attr_absent(self): self.assertRaisesRegex( exceptions.MissingAttributeError, 'Systems/@odata.id', self.root.get_system_collection) def test_get_manager_collection_when_managers_attr_absent(self): self.assertRaisesRegex( exceptions.MissingAttributeError, 'Managers/@odata.id', self.root.get_manager_collection) def test_get_chassis_collection_when_chassis_attr_absent(self): self.assertRaisesRegex( exceptions.MissingAttributeError, 'Chassis/@odata.id', self.root.get_chassis_collection) def test_get_fabric_collection_when_fabrics_attr_absent(self): self.assertRaisesRegex( exceptions.MissingAttributeError, 'Fabrics/@odata.id', self.root.get_fabric_collection) def test_get_session_service_when_sessionservice_attr_absent(self): self.assertRaisesRegex( exceptions.MissingAttributeError, 'SessionService/@odata.id', self.root.get_session_service) def test_get_update_service_when_updateservice_attr_absent(self): self.assertRaisesRegex( exceptions.MissingAttributeError, 'UpdateService/@odata.id', self.root.get_update_service) def test_get_composition_service_when_compositionservice_attr_absent(self): self.assertRaisesRegex( exceptions.MissingAttributeError, 'CompositionService/@odata.id', self.root.get_composition_service) def test__get_registry_collection_when_registries_attr_absent(self): self.assertIsNone(self.root._get_registry_collection()) def test_get_sessions_path_fail(self): self.assertRaisesRegex( exceptions.MissingAttributeError, 'Links/Sessions/@data.id', self.root.get_sessions_path) def test_get_event_service_when_eventservice_attr_absent(self): self.assertRaisesRegex( exceptions.MissingAttributeError, 'EventService/@odata.id', self.root.get_event_service ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/tests/unit/test_taskmonitor.py0000664000175000017500000002636400000000000022211 0ustar00zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from http import client as http_client import json from unittest import mock import requests from sushy import exceptions from sushy.resources import base as resource_base from sushy.resources.taskservice import task from sushy import taskmonitor from sushy.tests.unit import base class TaskMonitorTestCase(base.TestCase): def setUp(self): super().setUp() self.conn = mock.Mock() with open('sushy/tests/unit/json_samples/task.json') as f: self.json_doc = json.load(f) self.conn.get.return_value.json.return_value = self.json_doc self.field_data = resource_base.FieldData( http_client.ACCEPTED, {'Content-Length': 42, 'Location': '/Task/545', 'Retry-After': 20, 'Allow': 'DELETE'}, self.json_doc) self.response = mock.Mock() self.response.status_code = http_client.ACCEPTED self.response.headers = {'Content-Length': 42, 'Location': '/Task/545', 'Retry-After': 20, 'Allow': 'DELETE'} self.response.content = json.dumps(self.json_doc).encode('utf-8') self.response.json.return_value = self.json_doc self.task_monitor = taskmonitor.TaskMonitor( self.conn, '/Task/545', response=self.response ) def test_init_accepted_no_content(self): response = mock.Mock() response.status_code = http_client.ACCEPTED response.headers = {'Location': '/Task/545', 'Retry-After': 20, 'Allow': 'DELETE'} response.content = None task_monitor = taskmonitor.TaskMonitor( self.conn, '/Task/545', response=response) self.assertIsNone(task_monitor.task) def test_init_accepted_content(self): self.assertIsNotNone(self.task_monitor.task) def test_init_no_response(self): self.conn.reset_mock() self.conn.get.return_value.status_code = 202 self.conn.get.return_value.headers = {'Content-Length': 42} task_monitor = taskmonitor.TaskMonitor(self.conn, '/Task/545') self.conn.get.assert_called_with(path='/Task/545') self.assertEqual(1, self.conn.get.call_count) self.assertIsNotNone(task_monitor.task) def test_refresh_no_content(self): self.conn.reset_mock() self.conn.get.return_value.status_code = 202 self.conn.get.return_value.headers = {'Content-Length': 0} self.conn.get.return_value.content = None self.task_monitor.refresh() self.conn.get.assert_called_with(path='/Task/545') self.assertEqual(1, self.conn.get.call_count) self.assertIsNone(self.task_monitor.task) def test_refresh_content_no_task(self): self.conn.reset_mock() self.conn.get.return_value.status_code = 202 self.conn.get.return_value.headers = {'Content-Length': 42} self.task_monitor._task = None self.task_monitor.refresh() self.conn.get.assert_called_with(path='/Task/545') self.assertEqual(1, self.conn.get.call_count) self.assertIsNotNone(self.task_monitor.task) def test_refresh_content_task(self): self.conn.reset_mock() self.conn.get.return_value.status_code = 202 self.conn.get.return_value.headers = {'Content-Length': 42} self.task_monitor.refresh() self.conn.get.assert_called_with(path='/Task/545') self.assertEqual(1, self.conn.get.call_count) self.assertIsNotNone(self.task_monitor.task) def test_refresh_done(self): self.conn.reset_mock() self.conn.get.return_value.status_code = 200 self.task_monitor.refresh() self.conn.get.assert_called_once_with(path='/Task/545') self.assertIsNone(self.task_monitor.task) def test_task_monitor_uri(self): self.assertEqual('/Task/545', self.task_monitor.task_monitor_uri) def test_is_processing(self): self.assertTrue(self.task_monitor.is_processing) def test_check_is_processing_not_processing(self): response = mock.Mock() response.status_code = http_client.OK response.headers = {} response.content = None task_monitor = taskmonitor.TaskMonitor( self.conn, '/Task/545', response=response) self.assertEqual(False, task_monitor.check_is_processing) def test_check_is_processing_refreshing(self): self.conn.reset_mock() self.conn.get.return_value.status_code = 202 self.conn.get.return_value.headers = {} self.conn.get.return_value.content = None task_monitor = taskmonitor.TaskMonitor( self.conn, '/Task/545') self.assertEqual(True, task_monitor.check_is_processing) def test_cancellable(self): self.assertTrue(self.task_monitor.cancellable) def test_sleep_for_retry_after_empty(self): self.task_monitor._response.headers["Retry-After"] = None self.assertEqual(1, self.task_monitor.sleep_for) def test_sleep_for_retry_after_digit(self): self.assertEqual(20, self.task_monitor.sleep_for) def test_sleep_for_retry_after_date_past(self): self.task_monitor._response.headers["Retry-After"] =\ 'Fri, 31 Dec 1999 23:59:59 GMT' self.assertEqual(0, self.task_monitor.sleep_for) def test_not_cancellable_no_header(self): response = mock.Mock() response.status_code = http_client.ACCEPTED response.headers = { 'Content-Length': 42, 'Location': '/Task/545', 'Retry-After': 20} response.json.return_value = self.json_doc task_monitor = taskmonitor.TaskMonitor( self.conn, '/Task/545', response=response ) self.assertFalse(task_monitor.cancellable) def test_not_cancellable(self): response = mock.Mock() response.status_code = http_client.ACCEPTED response.headers = { 'Content-Length': 42, 'Location': '/Task/545', 'Retry-After': 20, 'Allow': 'GET'} response.json.return_value = self.json_doc task_monitor = taskmonitor.TaskMonitor( self.conn, '/Task/545', response=response ) self.assertFalse(task_monitor.cancellable) def test_task(self): tm_task = self.task_monitor.task self.assertIsInstance(tm_task, task.Task) self.assertEqual('545', tm_task.identity) def test_get_task(self): tm_task = self.task_monitor.get_task() self.assertIsInstance(tm_task, task.Task) self.assertEqual('545', tm_task.identity) @mock.patch('time.sleep', autospec=True) def test_wait(self, mock_time): self.conn.reset_mock() response1 = mock.MagicMock(spec=requests.Response) response1.status_code = http_client.ACCEPTED response1.headers = { 'Retry-After': 5, 'Location': '/redfish/v1/taskmon/1', 'Content-Length': 10 } response1.json.return_value = {'Id': 3, 'Name': 'Test'} response2 = mock.MagicMock(spec=requests.Response) response2.status_code = http_client.OK response2.headers = { 'Retry-After': 5, 'Location': '/redfish/v1/taskmon/1', 'Content-Length': 10 } response2.json.return_value = {'Id': 3, 'Name': 'Test'} self.conn.get.side_effect = [response1, response2] self.task_monitor.wait(60) self.assertFalse(self.task_monitor.is_processing) self.assertEqual(response2, self.task_monitor.response) @mock.patch('time.sleep', autospec=True) def test_wait_timeout(self, mock_time): self.conn.reset_mock() response1 = mock.MagicMock(spec=requests.Response) response1.status_code = http_client.ACCEPTED response1.headers = { 'Retry-After': 5, 'Location': '/redfish/v1/taskmon/1', 'Content-Length': 10 } response1.json.return_value = {'Id': 3, 'Name': 'Test'} self.conn.get.side_effect = [response1, response1] self.assertRaises(exceptions.ConnectionError, self.task_monitor.wait, -10) def test_from_response_no_content(self): self.conn.reset_mock() self.conn.get.return_value.status_code = 202 response = mock.Mock() response.content = None response.headers = {'Location': '/Task/545'} response.status_code = http_client.ACCEPTED tm = taskmonitor.TaskMonitor.from_response( self.conn, response, '/redfish/v1/UpdateService/Actions/SimpleUpdate') self.assertIsInstance(tm, taskmonitor.TaskMonitor) self.assertEqual('/Task/545', tm.task_monitor_uri) self.assertIsNotNone(tm.task) self.assertEqual('545', tm.task.identity) def test_from_response_odata_id(self): response = mock.Mock() response.content = "something" response.json.return_value = {'Id': '545', 'Name': 'test', '@odata.id': '545'} response.headers = {'Location': '/TaskMonitor/'} response.status_code = http_client.ACCEPTED tm = taskmonitor.TaskMonitor.from_response( self.conn, response, '/redfish/v1/UpdateService/Actions/SimpleUpdate') self.assertIsInstance(tm, taskmonitor.TaskMonitor) self.assertEqual('/TaskMonitor/545', tm.task_monitor_uri) self.assertIsNotNone(tm.task) self.assertEqual('545', tm.task.identity) def test_from_response_location_header_missing(self): response = mock.Mock() response.content = "something" response.json.return_value = {'Id': '545', 'Name': 'test'} response.headers = {} response.status_code = http_client.ACCEPTED self.assertRaises(exceptions.MissingHeaderError, taskmonitor.TaskMonitor.from_response, self.conn, response, '/redfish/v1/UpdateService/Actions/SimpleUpdate') def test_from_response(self): response = mock.Mock() response.content = "something" response.json.return_value = {'Id': '545', 'Name': 'test'} response.headers = {'Location': '/Task/545'} response.status_code = http_client.ACCEPTED tm = taskmonitor.TaskMonitor.from_response( self.conn, response, '/redfish/v1/UpdateService/Actions/SimpleUpdate') self.assertIsInstance(tm, taskmonitor.TaskMonitor) self.assertEqual('/Task/545', tm.task_monitor_uri) self.assertIsNotNone(tm.task) self.assertEqual('545', tm.task.identity) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/tests/unit/test_utils.py0000664000175000017500000002567700000000000021005 0ustar00zuulzuul00000000000000# Copyright 2017 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 datetime import json from unittest import mock import sushy from sushy import exceptions from sushy.resources import base as resource_base from sushy.resources.system import system from sushy.tests.unit import base from sushy import utils class UtilsTestCase(base.TestCase): def test_revert_dictionary(self): source = {'key0': 'value0', 'key1': 'value1'} expected = {'value0': 'key0', 'value1': 'key1'} self.assertEqual(expected, utils.revert_dictionary(source)) @mock.patch.object(utils.LOG, 'warning', autospec=True) def test_get_members_identities(self, log_mock): members = [{"@odata.id": "/redfish/v1/Systems/FOO"}, {"other_key": "/redfish/v1/Systems/FUN"}, {"@odata.id": "/redfish/v1/Systems/BAR/"}] expected = ('/redfish/v1/Systems/FOO', '/redfish/v1/Systems/BAR') self.assertEqual(expected, utils.get_members_identities(members)) self.assertEqual(1, log_mock.call_count) def test_int_or_none(self): self.assertEqual(1, utils.int_or_none('1')) self.assertIsNone(utils.int_or_none(None)) def test_bool_or_none_none(self): self.assertIsNone(utils.bool_or_none(None)) def test_bool_or_none_bool(self): self.assertEqual(True, utils.bool_or_none(True)) def setUp(self): super().setUp() self.conn = mock.MagicMock() with open('sushy/tests/unit/json_samples/system.json') as f: system_json = json.load(f) self.conn.get.return_value.json.return_value = system_json self.sys_inst = system.System(self.conn, '/redfish/v1/Systems/437XR1138R2', redfish_version='1.0.2') def test_get_sub_resource_path_by(self): subresource_path = 'EthernetInterfaces' expected_result = '/redfish/v1/Systems/437XR1138R2/EthernetInterfaces' value = utils.get_sub_resource_path_by(self.sys_inst, subresource_path) self.assertEqual(expected_result, value) def test_get_sub_resource_path_by_list(self): subresource_path = ['EthernetInterfaces'] expected_result = '/redfish/v1/Systems/437XR1138R2/EthernetInterfaces' value = utils.get_sub_resource_path_by(self.sys_inst, subresource_path) self.assertEqual(expected_result, value) def test_get_sub_resource_path_by_collection(self): subresource_path = ["Links", "ManagedBy"] expected_result = ['/redfish/v1/Managers/BMC'] value = utils.get_sub_resource_path_by(self.sys_inst, subresource_path, is_collection=True) self.assertEqual(expected_result, value) def test_get_sub_resource_path_by_fails(self): subresource_path = ['Links', 'Chassis'] expected_result = 'attribute Links/Chassis/@odata.id is missing' self.assertRaisesRegex( exceptions.MissingAttributeError, expected_result, utils.get_sub_resource_path_by, self.sys_inst, subresource_path) def test_get_sub_resource_path_by_fails_with_empty_path(self): self.assertRaisesRegex( ValueError, '"subresource_name" cannot be empty', utils.get_sub_resource_path_by, self.sys_inst, []) def test_get_sub_resource_path_by_fails_with_empty_string(self): self.assertRaisesRegex( ValueError, '"subresource_name" cannot be empty', utils.get_sub_resource_path_by, self.sys_inst, '') def test_max_safe(self): self.assertEqual(10, utils.max_safe([1, 3, 2, 8, 5, 10, 6])) self.assertEqual(821, utils.max_safe([15, 300, 270, None, 821, None])) self.assertEqual(0, utils.max_safe([])) self.assertIsNone(utils.max_safe([], default=None)) def test_camelcase_to_underscore_joined(self): input_vs_expected = [ ('GarbageCollection', 'garbage_collection'), ('DD', 'dd'), ('rr', 'rr'), ('AABbbC', 'aa_bbb_c'), ('AABbbCCCDd', 'aa_bbb_ccc_dd'), ('Manager', 'manager'), ('EthernetInterfaceCollection', 'ethernet_interface_collection'), (' ', ' '), ] for inp, exp in input_vs_expected: self.assertEqual(exp, utils.camelcase_to_underscore_joined(inp)) def test_camelcase_to_underscore_joined_fails_with_empty_string(self): self.assertRaisesRegex( ValueError, '"camelcase_str" cannot be empty', utils.camelcase_to_underscore_joined, '') class NestedResource(resource_base.ResourceBase): def _parse_attributes(self, json_doc): pass class BaseResource(resource_base.ResourceBase): def _parse_attributes(self, json_doc): pass def _do_some_crunch_work_to_get_a(self): return 'a' @utils.cache_it def get_a(self): return self._do_some_crunch_work_to_get_a() def _do_some_crunch_work_to_get_b(self): return 'b' @utils.cache_it def get_b(self): return self._do_some_crunch_work_to_get_b() @property @utils.cache_it def nested_resource(self): return NestedResource( self._conn, "path/to/nested_resource", redfish_version=self.redfish_version) @property @utils.cache_it def few_nested_resources(self): return [NestedResource(self._conn, "/nested_res1", redfish_version=self.redfish_version), NestedResource(self._conn, "/nested_res2", redfish_version=self.redfish_version)] class CacheTestCase(base.TestCase): def setUp(self): super().setUp() self.conn = mock.Mock() self.res = BaseResource(connector=self.conn, path='/Foo', redfish_version='1.0.2') def test_cache_nested_resource_retrieval(self): nested_res = self.res.nested_resource few_nested_res = self.res.few_nested_resources self.assertIsInstance(nested_res, NestedResource) self.assertIs(nested_res, self.res.nested_resource) self.assertIsInstance(few_nested_res, list) for n_res in few_nested_res: self.assertIsInstance(n_res, NestedResource) self.assertIs(few_nested_res, self.res.few_nested_resources) self.res.invalidate() self.res.refresh(force=False) self.assertIsNotNone(self.res._cache_nested_resource) self.assertTrue(self.res._cache_nested_resource._is_stale) self.assertIsNotNone(self.res._cache_few_nested_resources) for n_res in self.res._cache_few_nested_resources: self.assertTrue(n_res._is_stale) self.assertIsInstance(self.res.nested_resource, NestedResource) self.assertFalse(self.res._cache_nested_resource._is_stale) self.assertIsInstance(self.res.few_nested_resources, list) for n_res in self.res._cache_few_nested_resources: self.assertFalse(n_res._is_stale) def test_cache_non_resource_retrieval(self): with mock.patch.object( self.res, '_do_some_crunch_work_to_get_a', wraps=self.res._do_some_crunch_work_to_get_a, autospec=True) as do_work_to_get_a_spy: result = self.res.get_a() self.assertTrue(do_work_to_get_a_spy.called) do_work_to_get_a_spy.reset_mock() # verify subsequent invocation self.assertEqual(result, self.res.get_a()) self.assertFalse(do_work_to_get_a_spy.called) def test_cache_clear_only_selected_attr(self): self.res.nested_resource self.res.get_a() self.res.get_b() utils.cache_clear(self.res, False, only_these=['get_a']) # cache cleared (set to None) self.assertIsNone(self.res._cache_get_a) # cache retained self.assertEqual('b', self.res._cache_get_b) self.assertFalse(self.res._cache_nested_resource._is_stale) def test_cache_clear_failure(self): self.assertRaises( TypeError, utils.cache_clear, self.res, False, only_these=10) def test_sanitize(self): orig = {'UserName': 'admin', 'Password': 'pwd', 'nested': {'answer': 42, 'password': 'secret'}} expected = {'UserName': 'admin', 'Password': '***', 'nested': {'answer': 42, 'password': '***'}} self.assertEqual(expected, utils.sanitize(orig)) class ProcessApplyTimeTestCase(base.TestCase): def test_process_apply_time_input(self): payload = utils.process_apply_time_input( {'test': 'value'}, sushy.ApplyTime.ON_RESET, None, None) self.assertEqual( {'@Redfish.SettingsApplyTime': { '@odata.type': '#Settings.v1_0_0.PreferredApplyTime', 'ApplyTime': 'OnReset'}, 'test': 'value'}, payload) def test_process_apply_time_input_maintenance_window(self): payload = utils.process_apply_time_input( {'test': 'value'}, sushy.ApplyTime.AT_MAINTENANCE_WINDOW_START, datetime.datetime(2020, 9, 1, 4, 30, 0), 600) self.assertEqual( {'@Redfish.SettingsApplyTime': { '@odata.type': '#Settings.v1_0_0.PreferredApplyTime', 'ApplyTime': 'AtMaintenanceWindowStart', 'MaintenanceWindowDurationInSeconds': 600, 'MaintenanceWindowStartTime': '2020-09-01T04:30:00'}, 'test': 'value'}, payload) def test_process_apply_time_missing(self): self.assertRaises( ValueError, utils.process_apply_time_input, {'test': 'value'}, None, datetime.datetime(2020, 9, 1, 4, 30, 0), 600) def test_process_apply_time_maint_window_start_time_missing(self): self.assertRaises( ValueError, utils.process_apply_time_input, {'test': 'value'}, sushy.ApplyTime.AT_MAINTENANCE_WINDOW_START, None, 600) def test_process_apply_time_maint_window_duration_missing(self): self.assertRaises( ValueError, utils.process_apply_time_input, {'test': 'value'}, sushy.ApplyTime.AT_MAINTENANCE_WINDOW_START, datetime.datetime.now(), None) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/sushy/utils.py0000664000175000017500000003223600000000000015612 0ustar00zuulzuul00000000000000# Copyright 2017 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 collections import functools import logging import threading from sushy import exceptions from sushy.resources import constants as res_cons LOG = logging.getLogger(__name__) CACHE_ATTR_NAMES_VAR_NAME = '_cache_attr_names' def revert_dictionary(dictionary): """Given a dictionary revert it's mapping :param dictionary: A dictionary to be reverted :returns: A dictionary with the keys and values reverted """ return {v: k for k, v in dictionary.items()} def get_members_identities(members): """Extract and return a tuple of members identities :param members: A list of members in JSON format :returns: A tuple containing the members paths """ members_list = [] for member in members: path = member.get('@odata.id') if not path: LOG.warning('Could not find the \'@odata.id\' attribute for ' 'member %s', member) continue members_list.append(path.rstrip('/')) return tuple(members_list) def int_or_none(x): """Given a value x it cast as int or None :param x: The value to transform and return :returns: Either None or x cast to an int """ if x is None: return None return int(x) def bool_or_none(x): """Given a value x this method returns either a bool or None :param x: The value to transform and return :returns: Either None or x cast to a bool """ if x is None: return None return bool(x) def get_sub_resource_path_by(resource, subresource_name, is_collection=False): """Helper function to find the subresource path :param resource: ResourceBase instance on which the name gets queried upon. :param subresource_name: name of the resource field to fetch the '@odata.id' from. :param is_collection: if `True`, expect a list of resources to fetch the '@odata.id' from. :returns: Resource path (if `is_collection` is `False`) or a list of resource paths (if `is_collection` is `True`). """ if not subresource_name: raise ValueError('"subresource_name" cannot be empty') if not isinstance(subresource_name, list): subresource_name = [subresource_name] body = resource.json for path_item in subresource_name: body = body.get(path_item, {}) if not body: raise exceptions.MissingAttributeError( attribute='/'.join(subresource_name), resource=resource.path) elements = [] try: if is_collection: for element in body: elements.append(element['@odata.id']) return elements return body['@odata.id'] except (TypeError, KeyError): attribute = '/'.join(subresource_name) if is_collection: attribute += f'[{len(elements)}]' attribute += '/@odata.id' raise exceptions.MissingAttributeError( attribute=attribute, resource=resource.path) def max_safe(iterable, default=0): """Helper wrapper over builtin max() function. This function is just a wrapper over builtin max() w/o ``key`` argument. The ``default`` argument specifies an object to return if the provided ``iterable`` is empty. Also it filters out the None type values. :param iterable: an iterable :param default: 0 by default """ try: return max(x for x in iterable if x is not None) except ValueError: # TypeError is not caught here as that should be thrown. return default def setdefaultattr(obj, name, default): """Python's ``dict.setdefault`` applied on Python objects. If name is an attribute with obj, return its value. If not, set name attribute with a value of default and return default. :param obj: a python object :param name: name of attribute :param default: default value to be set """ try: return getattr(obj, name) except AttributeError: setattr(obj, name, default) return default def cache_it(res_accessor_method): """Utility decorator to cache the return value of the decorated method. This decorator is to be used with any Sushy resource class method. This will internally create an attribute on the resource namely ``_cache_``. This is referred to as the "caching attribute". This attribute will eventually hold the resultant value from the method invocation (when method gets first time called) and for every subsequent calls to that method this cached value will get returned. It expects the decorated method to contain its own logic of evaluation. This also assigns a variable named ``_cache_attr_names`` on the resource. This variable maintains a collection of all the existing "caching attribute" names. To invalidate or clear the cache use :py:func:`~cache_clear`. Usage: .. code-block:: python class SomeResource(base.ResourceBase): ... @cache_it def get_summary(self): # do some calculation and return the result # and this result will be cached. return result ... def _do_refresh(self, force): cache_clear(self, force) If the returned value is a Sushy resource instance or a sequence whose element is of type Sushy resource it handles the case of calling the ``refresh()`` method of that resource. This is done to avoid unnecessary recreation of a new resource instance which got already created at the first place in contrast to fresh retrieval of the resource json data. Again, the ``force`` argument is deliberately set to False to do only the "light refresh" of the resource (only the fresh retrieval of resource) instead of doing the complete exhaustive "cascading refresh" (resource with all its nested subresources recursively). .. code-block:: python class SomeResource(base.ResourceBase): ... @property @cache_it def nested_resource(self): return NestedResource( self._conn, "Path/to/NestedResource", redfish_version=self.redfish_version) ... def _do_refresh(self, force): # selective attribute clearing cache_clear(self, force, only_these=['nested_resource']) Do note that this is not thread safe. So guard your code to protect it from any kind of concurrency issues while using this decorator. :param res_accessor_method: the resource accessor decorated method. """ cache_attr_name = '_cache_' + res_accessor_method.__name__ @functools.wraps(res_accessor_method) def func_wrapper(res_selfie): cache_attr_val = getattr(res_selfie, cache_attr_name, None) if cache_attr_val is None: cache_attr_val = res_accessor_method(res_selfie) setattr(res_selfie, cache_attr_name, cache_attr_val) # Note(deray): Each resource instance maintains a collection of # all the cache attribute names in a private attribute. cache_attr_names = setdefaultattr( res_selfie, CACHE_ATTR_NAMES_VAR_NAME, set()) cache_attr_names.add(cache_attr_name) from sushy.resources import base if isinstance(cache_attr_val, base.ResourceBase): cache_attr_val.refresh(force=False) elif isinstance(cache_attr_val, collections.abc.Sequence): for elem in cache_attr_val: if isinstance(elem, base.ResourceBase): elem.refresh(force=False) return cache_attr_val return func_wrapper def cache_clear(res_selfie, force_refresh, only_these=None): """Clear some or all cached values of the resource. If the cache variable refers to a resource instance then the ``invalidate()`` method is called on that. Otherwise it is set to None. Should there be a need to force refresh the resource and its sub-resources, "cascading refresh", ``force_refresh`` is to be set to True. This is the complimentary method of ``cache_it`` decorator. :param res_selfie: the resource instance. :param force_refresh: force_refresh argument of ``invalidate()`` method. :param only_these: expects a sequence of specific method names for which the cached value/s need to be cleared only. When None, all the cached values are cleared. """ cache_attr_names = setdefaultattr( res_selfie, CACHE_ATTR_NAMES_VAR_NAME, set()) if only_these is not None: if not isinstance(only_these, collections.abc.Sequence): raise TypeError("'only_these' must be a sequence.") cache_attr_names = cache_attr_names.intersection( '_cache_' + attr for attr in only_these) for cache_attr_name in cache_attr_names: cache_attr_val = getattr(res_selfie, cache_attr_name) from sushy.resources import base if isinstance(cache_attr_val, base.ResourceBase): cache_attr_val.invalidate(force_refresh) elif isinstance(cache_attr_val, collections.abc.Sequence): for elem in cache_attr_val: if isinstance(elem, base.ResourceBase): elem.invalidate(force_refresh) else: setattr(res_selfie, cache_attr_name, None) break else: setattr(res_selfie, cache_attr_name, None) def camelcase_to_underscore_joined(camelcase_str): """Convert camelCase string to underscore_joined string :param camelcase_str: The camelCase string :returns: the equivalent underscore_joined string """ if not camelcase_str: raise ValueError('"camelcase_str" cannot be empty') r = camelcase_str[0].lower() for i, letter in enumerate(camelcase_str[1:], 1): if letter.isupper(): try: if (camelcase_str[i - 1].islower() or camelcase_str[i + 1].islower()): r += '_' except IndexError: pass r += letter.lower() return r def synchronized(wrapped): """Simple synchronization decorator. Decorating a method like so: .. code-block:: python @synchronized def foo(self, *args): ... ensures that only one thread will execute the foo method at a time. """ lock = threading.RLock() @functools.wraps(wrapped) def wrapper(*args, **kwargs): with lock: return wrapped(*args, **kwargs) return wrapper _REMOVE = frozenset(['password', 'x-auth-token']) def sanitize(item): """Remove passwords from the item.""" if isinstance(item, dict): return {key: ('***' if key.lower() in _REMOVE else sanitize(value)) for key, value in item.items()} else: return item def process_apply_time_input( payload, apply_time, maint_window_start_time, maint_window_duration): """Validates apply time input for asynchronous operations :param payload: Payload for which to process apply time settings :param apply_time: When to update the attribute. Optional. An :py:class:`sushy.ApplyTime` value. :param maint_window_start_time: The start time of a maintenance window, datetime. Required when updating during maintenance window and default maintenance window not set by the system. :param maint_window_duration: Duration of maintenance time since maintenance window start time in seconds. Required when updating during maintenance window and default maintenance window not set by the system. :raises ValueError: When input apply time settings incorrect :returns: Payload with adjusted apply time settings if valid """ if (not apply_time and (maint_window_start_time or maint_window_duration)): raise ValueError('"apply_time" missing when passing maintenance ' 'window settings') if apply_time: prop = '@Redfish.SettingsApplyTime' payload[prop] = { '@odata.type': '#Settings.v1_0_0.PreferredApplyTime', 'ApplyTime': res_cons.ApplyTime(apply_time).value, } if maint_window_start_time and not maint_window_duration: raise ValueError('"maint_window_duration" missing') if not maint_window_start_time and maint_window_duration: raise ValueError('"maint_window_start_time" missing') if maint_window_start_time and maint_window_duration: payload[prop]['MaintenanceWindowStartTime'] =\ maint_window_start_time.isoformat() payload[prop]['MaintenanceWindowDurationInSeconds'] =\ maint_window_duration return payload ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6955147 sushy-5.5.0/sushy.egg-info/0000775000175000017500000000000000000000000015564 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655096.0 sushy-5.5.0/sushy.egg-info/PKG-INFO0000644000175000017500000000420700000000000016662 0ustar00zuulzuul00000000000000Metadata-Version: 2.1 Name: sushy Version: 5.5.0 Summary: Sushy is a small Python library to communicate with Redfish based systems Home-page: https://docs.openstack.org/sushy/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org 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 :: Implementation :: CPython Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Requires-Python: >=3.9 License-File: LICENSE Requires-Dist: pbr>=6.0.0 Requires-Dist: requests>=2.14.2 Requires-Dist: python-dateutil>=2.7.0 Requires-Dist: stevedore>=1.29.0 Overview ======== Sushy is a Python library to communicate with `Redfish`_ based systems. The goal of the library is to be extremely simple, small, have as few dependencies as possible and be very conservative when dealing with BMCs by issuing just enough requests to it (BMCs are very flaky). Therefore, the scope of the library has been limited to what is supported by the `OpenStack Ironic `_ project. As the project grows and more features from `Redfish`_ are needed we can expand Sushy to fulfill those requirements. * Free software: Apache license * Includes Redfish registry files licensed under Creative Commons Attribution 4.0 License: https://creativecommons.org/licenses/by/4.0/ * Documentation: https://docs.openstack.org/sushy/latest/ * Usage: https://docs.openstack.org/sushy/latest/reference/usage.html * Source: https://opendev.org/openstack/sushy * Bugs: https://bugs.launchpad.net/sushy * Release Notes: https://docs.openstack.org/releasenotes/sushy/ .. _Redfish: http://www.dmtf.org/standards/redfish ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655096.0 sushy-5.5.0/sushy.egg-info/SOURCES.txt0000664000175000017500000005515500000000000017463 0ustar00zuulzuul00000000000000.coveragerc .mailmap .pre-commit-config.yaml .stestr.conf AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst bindep.txt pyproject.toml requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/requirements.txt doc/source/conf.py doc/source/index.rst doc/source/contributor/index.rst doc/source/install/index.rst doc/source/reference/index.rst doc/source/reference/usage.rst releasenotes/notes/.placeholder releasenotes/notes/HttpBootUri-to-settings-6fcbdeed2b4e84df.yaml releasenotes/notes/accept-encoding-4646ea43998f80bd.yaml releasenotes/notes/action-parameter-missing-7d234b96b5b1d81a.yaml releasenotes/notes/add-apply-time-support-to-bios-315ebad429dcab3d.yaml releasenotes/notes/add-bios-bf69ac56c4ae8f50.yaml releasenotes/notes/add-bios-update-status-cc59816c374b78e4.yaml releasenotes/notes/add-chassis-linkage-d8e567f9c791169d.yaml releasenotes/notes/add-chassis-support-5b97daffe1c61a2b.yaml releasenotes/notes/add-custom-connector-support-0a49c6649d5f7eaf.yaml releasenotes/notes/add-default-identity-10c5dd23bed0e915.yaml releasenotes/notes/add-drive-led-97b687013fec88c9.yaml releasenotes/notes/add-drive-revision-a0c069fff236479d.yaml releasenotes/notes/add-drive-volumes-971d80644c3bd1e0.yaml releasenotes/notes/add-endpoint-subresource-to-fabric-b03e5fd99ece1bf4.yaml releasenotes/notes/add-fabric-support-1520f7fcb0e12539.yaml releasenotes/notes/add-http-boot-uri-support-5c25816e13ccdb27.yaml releasenotes/notes/add-initial-redfish-oem-extension-support-50c9849bb7b6b25c.yaml releasenotes/notes/add-mapped-list-field-04c671f7a73d83f6.yaml releasenotes/notes/add-network-adapter-26d01d8d9fb1d7ad.yaml releasenotes/notes/add-network-device-function-and-port-e880d8f461e3723d.yaml releasenotes/notes/add-odata-version-header-96dc8179c0e2e9bd.yaml releasenotes/notes/add-partial-key-match-27bed73d577b1187.yaml releasenotes/notes/add-port-e57ec6759ee70bf7.yaml releasenotes/notes/add-power-resource-e141ddf298673305.yaml releasenotes/notes/add-processor-id-and-status-b81d4c6e6c14c25f.yaml releasenotes/notes/add-raid-type-properties-2090da5bea37c660.yaml releasenotes/notes/add-read-and-connect-timeout-9f7dc3ed63c192c8.yaml releasenotes/notes/add-response-cb-65d448ee2690d0b2.yaml releasenotes/notes/add-simple-storage-915464811737bb05.yaml releasenotes/notes/add-storage-and-simple-storage-attributes-to-system-16e81f9b15b1897d.yaml releasenotes/notes/add-storage-da766d3dbf9fb385.yaml releasenotes/notes/add-sushy-root-to-resources-1f221794557aa5fc.yaml releasenotes/notes/add-system-bootprogress-42ee452cfa279c63.yaml releasenotes/notes/add-system-manager-linkage-86be69c9df4cb359.yaml releasenotes/notes/add-system-status-field-41b3f2a8c4b85f38.yaml releasenotes/notes/add-system-type-mapping-bf456c5c15a90877.yaml releasenotes/notes/add-task-monitor-support-21f711927ad6ec91.yaml releasenotes/notes/add-task-service-c751ce51e0b8dc11.yaml releasenotes/notes/add-thermal-resource-5c965a3c940f9028.yaml releasenotes/notes/add-virtual-media-support-f522fbec4420341c.yaml releasenotes/notes/add_composition_service-84750d8d1d96474a.yaml releasenotes/notes/add_ethernet_interface-df308f814f0e4bce.yaml releasenotes/notes/add_keyword_argument_for_connector-cea5dc4e6c01b548.yaml releasenotes/notes/add_product_and_protocol_features_supported-59de3f89b7382434.yaml releasenotes/notes/add_update_service-b54c9bb0177e3468.yaml releasenotes/notes/allow_empty_context_eventdestination-9a96c34dd7edbeca.yaml releasenotes/notes/apply-time-support-for-volume-ops-f2ebc412e3b4290a.yaml releasenotes/notes/bios-attribute-registry-a55c2d81c730a795.yaml releasenotes/notes/bug-1754514-ca6ebe16c4e4b3b0.yaml releasenotes/notes/catch-general-requests-exceptions-b5fd706597708fb6.yaml releasenotes/notes/certificate-collection-acc67488c240274c.yaml releasenotes/notes/change-bootdev-smc-ab30317eaf5c37d9.yaml releasenotes/notes/change-vmedia-write-protected-attr-586370a552288801.yaml releasenotes/notes/check-for-boot-attrs-in-settingsuri-1cad07b6eb1c81b3.yaml releasenotes/notes/config-server-side-retries-d16824019bd709a2.yaml releasenotes/notes/decouple-boot-params-c75e80f5951abb12.yaml releasenotes/notes/deprecate-system-leds-f1a72422c53d281e.yaml releasenotes/notes/disable-conn-pooling-3456782afe56ac94.yaml releasenotes/notes/do-not-offer-compression-encoding-884ca8a7458cb096.yaml releasenotes/notes/drop-py-2-7-cc931c210ce08e33.yaml releasenotes/notes/enhance-oem-extension-design-3143717e710b3eaf.yaml releasenotes/notes/enhance-storage-volume-drive-support-16314d30f3631fb3.yaml releasenotes/notes/enums-3aff03d940012f33.yaml releasenotes/notes/ethernet-interfaces-manager-5529326417a38da9.yaml releasenotes/notes/event-service-d6607420effc3df8.yaml releasenotes/notes/expand-drive-schema-042901f919be646c.yaml releasenotes/notes/fix-2008198-bios-factory-reset-400-bad-request-3f4a7a2aada0835b.yaml releasenotes/notes/fix-eject-media-empty-dict-573b4c9e06f52ce7.yaml releasenotes/notes/fix-exceeding-retries-663ab543cc14f261.yaml releasenotes/notes/fix-extended-info-error-handling-73fecb6bf5c852ff.yaml releasenotes/notes/fix-insert-media-payload-b5d4c707f81d9603.yaml releasenotes/notes/fix-malformed-boot-mode-1ba1117cad8dcc47.yaml releasenotes/notes/fix-manager-action-d71fd415cea29aa6.yaml releasenotes/notes/fix-missing-etags-ded8c0bb31fafef7.yaml releasenotes/notes/fix-oem-loading-52da045252b6c33e.yaml releasenotes/notes/fix-refine-resource-refresh-86c21ce230967251.yaml releasenotes/notes/fix-required-oem-attribute-parsing-205e4186275aa293.yaml releasenotes/notes/fix-retry_volume_operation-on_sys518-009f2b16e5c38a27.yaml releasenotes/notes/fix-return-full-weak-etag-04265472cbea9c0e.yaml releasenotes/notes/fix-simple-storage-device-capacity-bytes-null-0672eed36d9da70a.yaml releasenotes/notes/fix-simple-update-e88838fab4170920.yaml releasenotes/notes/fix-software-firmware-inventory-3e0e79e052aa76d9.yaml releasenotes/notes/fix-subprocessors-3b619434dba4636d.yaml releasenotes/notes/fix-taskmonitor-init-calls-in-volume-module-0f8a747acd0cfe3f.yaml releasenotes/notes/fix-to-close-session-on-dealloc-c3687d4dcb1441b8.yaml releasenotes/notes/fix-update-service-constants-b8c3f48ccee6ce1f.yaml releasenotes/notes/fix-use-headers-for-options-736940b87c06c189.yaml releasenotes/notes/fix-virtual-media-fallback-15a559414a65c014.yaml releasenotes/notes/fix-volume-actions-not-required-730fd637dd2587ce.yaml releasenotes/notes/fix-volume-delete-configuration-unsuported-operational_time_property-f53f650d8612a847.yaml releasenotes/notes/fixes-ilo5-redfish-firmware-update-issue-273862b2a11e3536.yaml releasenotes/notes/get-retry-9ca311caf8a0b7bb.yaml releasenotes/notes/handle-basic-auth-access-errors-393b368b31f5cad2.yaml releasenotes/notes/handle_transfer_method-a51d5a17e381ebee.yaml releasenotes/notes/health_literals_change-0e3fc0c439b765e3.yaml releasenotes/notes/increase-server-retries-5f11edde8ee0b461.yaml releasenotes/notes/indicator-led-mappings-e7b34da03f6abb06.yaml releasenotes/notes/lazily-load-registries-0e9441e435c2471d.yaml releasenotes/notes/make-leds-settable-c82cb513de0171f5.yaml releasenotes/notes/make-volume-ops-blocking-de5c2ae032041d5d.yaml releasenotes/notes/message-parsing-resilience-534da532515a15da.yaml releasenotes/notes/message-registry-logging-39624ae114c02e15.yaml releasenotes/notes/monitor_firmware_update-664b0c6c1a0307cf.yaml releasenotes/notes/more-transferprotocoltype-739ce8bdedbcb51c.yaml releasenotes/notes/no-passwords-295207ac891d27ab.yaml releasenotes/notes/non-default-language-registries-f73bdecc98ba2cc8.yaml releasenotes/notes/property-missing-7602c421ec177e9a.yaml releasenotes/notes/raise-error-on-async-task-failure-b67c7bc189a4d6ca.yaml releasenotes/notes/re-raise-1fe9f912691e893e.yaml releasenotes/notes/reauthentication-session-fallback-failure-fixes-4f0dcfdad1afd2d7.yaml releasenotes/notes/redfish-response-log-294f3f10b770e356.yaml releasenotes/notes/refactor-taskmonitor-update-volume-ba99380188395852.yaml releasenotes/notes/releasenote-d7138d1e1d414632.yaml releasenotes/notes/remove-deprecated-task-monitors-58c505d43e1fa6a7.yaml releasenotes/notes/remove-py38-6eebcd268c6f8e37.yaml releasenotes/notes/retry-if-transferprototype-missing-9cae57f3ecf470a9.yaml releasenotes/notes/retry-ilo-not-ready-error-0b4dce882282eaac.yaml releasenotes/notes/retry-on-missing-managedby-b2a5240eab8b4262.yaml releasenotes/notes/secure-boot-76c5b80371ea85d1.yaml releasenotes/notes/secure-boot-database-7fae673722d7cf4f.yaml releasenotes/notes/sessions.yml releasenotes/notes/skip-empty-mac-address-0d758a9c0d70e2a9.yaml releasenotes/notes/standard-registry-license-0ded489afd6cfad1.yaml releasenotes/notes/storage-controller-name-6924b71a97f78481.yaml releasenotes/notes/storage-controllers-resource-7ab112f5d2c34ca0.yaml releasenotes/notes/story-2006246-reset-bios-return-http-error-415-08170df7fe6300f8.yaml releasenotes/notes/story-2007216-fix-to-message-registry-cff37659f03ba815.yaml releasenotes/notes/sushy-system-virtualmedia-7a61bd77780f7b0e.yaml releasenotes/notes/update-apply-time-support-53c5445b58cd3b42.yaml releasenotes/notes/update_sushy_models-9b8ea0350eb4d4d0.yaml releasenotes/notes/use-sessions-url-from-root-8b8eca57dc450705.yaml releasenotes/notes/use-settingsobject-if-supported-12a332f9905d64ce.yaml releasenotes/notes/vmedia-1.4.0-9957460fed59d85c.yaml releasenotes/notes/vmedia-blank-auth-c01916469219a39e.yaml releasenotes/notes/vmedia-certificate-06c367c6ef33d139.yaml releasenotes/notes/vmedia-credentials-14b7705c3c94cc07.yaml releasenotes/notes/workaround-sushy-requests-verify-handling-6879c273b651246f.yaml releasenotes/source/2023.1.rst releasenotes/source/2023.2.rst releasenotes/source/2024.1.rst releasenotes/source/2024.2.rst releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/pike.rst releasenotes/source/queens.rst releasenotes/source/rocky.rst releasenotes/source/stein.rst releasenotes/source/train.rst releasenotes/source/unreleased.rst releasenotes/source/ussuri.rst releasenotes/source/victoria.rst releasenotes/source/wallaby.rst releasenotes/source/xena.rst releasenotes/source/yoga.rst releasenotes/source/zed.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder sushy/__init__.py sushy/auth.py sushy/connector.py sushy/exceptions.py sushy/main.py sushy/taskmonitor.py sushy/utils.py sushy.egg-info/PKG-INFO sushy.egg-info/SOURCES.txt sushy.egg-info/dependency_links.txt sushy.egg-info/entry_points.txt sushy.egg-info/not-zip-safe sushy.egg-info/pbr.json sushy.egg-info/requires.txt sushy.egg-info/top_level.txt sushy/resources/__init__.py sushy/resources/base.py sushy/resources/common.py sushy/resources/constants.py sushy/resources/ipaddresses.py sushy/resources/settings.py sushy/resources/certificateservice/__init__.py sushy/resources/certificateservice/certificate.py sushy/resources/certificateservice/certificateservice.py sushy/resources/certificateservice/constants.py sushy/resources/chassis/__init__.py sushy/resources/chassis/chassis.py sushy/resources/chassis/constants.py sushy/resources/chassis/power/__init__.py sushy/resources/chassis/power/constants.py sushy/resources/chassis/power/power.py sushy/resources/chassis/thermal/__init__.py sushy/resources/chassis/thermal/constants.py sushy/resources/chassis/thermal/thermal.py sushy/resources/compositionservice/__init__.py sushy/resources/compositionservice/compositionservice.py sushy/resources/compositionservice/constants.py sushy/resources/compositionservice/resourceblock.py sushy/resources/compositionservice/resourcezone.py sushy/resources/eventservice/__init__.py sushy/resources/eventservice/constants.py sushy/resources/eventservice/eventdestination.py sushy/resources/eventservice/eventservice.py sushy/resources/fabric/__init__.py sushy/resources/fabric/constants.py sushy/resources/fabric/endpoint.py sushy/resources/fabric/fabric.py sushy/resources/manager/__init__.py sushy/resources/manager/constants.py sushy/resources/manager/manager.py sushy/resources/manager/virtual_media.py sushy/resources/oem/__init__.py sushy/resources/oem/base.py sushy/resources/oem/common.py sushy/resources/oem/fake.py sushy/resources/registry/__init__.py sushy/resources/registry/attribute_registry.py sushy/resources/registry/constants.py sushy/resources/registry/message_registry.py sushy/resources/registry/message_registry_file.py sushy/resources/sessionservice/__init__.py sushy/resources/sessionservice/session.py sushy/resources/sessionservice/sessionservice.py sushy/resources/system/__init__.py sushy/resources/system/bios.py sushy/resources/system/constants.py sushy/resources/system/ethernet_interface.py sushy/resources/system/port.py sushy/resources/system/processor.py sushy/resources/system/secure_boot.py sushy/resources/system/secure_boot_database.py sushy/resources/system/simple_storage.py sushy/resources/system/system.py sushy/resources/system/network/__init__.py sushy/resources/system/network/adapter.py sushy/resources/system/network/constants.py sushy/resources/system/network/device_function.py sushy/resources/system/network/port.py sushy/resources/system/storage/__init__.py sushy/resources/system/storage/constants.py sushy/resources/system/storage/controller.py sushy/resources/system/storage/drive.py sushy/resources/system/storage/storage.py sushy/resources/system/storage/volume.py sushy/resources/taskservice/__init__.py sushy/resources/taskservice/constants.py sushy/resources/taskservice/task.py sushy/resources/taskservice/taskservice.py sushy/resources/updateservice/__init__.py sushy/resources/updateservice/constants.py sushy/resources/updateservice/softwareinventory.py sushy/resources/updateservice/updateservice.py sushy/standard_registries/Base.1.0.0.json sushy/standard_registries/Base.1.2.0.json sushy/standard_registries/Base.1.3.0.json sushy/standard_registries/Base.1.3.1.json sushy/standard_registries/Base.1.4.0.json sushy/tests/__init__.py sushy/tests/unit/__init__.py sushy/tests/unit/base.py sushy/tests/unit/test_auth.py sushy/tests/unit/test_connector.py sushy/tests/unit/test_main.py sushy/tests/unit/test_taskmonitor.py sushy/tests/unit/test_utils.py sushy/tests/unit/json_samples/TestRegistry.zip sushy/tests/unit/json_samples/bare_minimum_root.json sushy/tests/unit/json_samples/bios.json sushy/tests/unit/json_samples/bios_attribute_registry.json sushy/tests/unit/json_samples/bios_attribute_registry_file.json sushy/tests/unit/json_samples/bios_attribute_registry_file_zthardware.json sushy/tests/unit/json_samples/bios_attribute_registry_zthardware.json sushy/tests/unit/json_samples/bios_settings.json sushy/tests/unit/json_samples/bios_zt.json sushy/tests/unit/json_samples/certificate.json sushy/tests/unit/json_samples/certificate_collection.json sushy/tests/unit/json_samples/certificate_locations.json sushy/tests/unit/json_samples/certificateservice.json sushy/tests/unit/json_samples/chassis.json sushy/tests/unit/json_samples/chassis_collection.json sushy/tests/unit/json_samples/compositionservice.json sushy/tests/unit/json_samples/credentials_required_error.json sushy/tests/unit/json_samples/drive.json sushy/tests/unit/json_samples/drive2.json sushy/tests/unit/json_samples/drive3.json sushy/tests/unit/json_samples/endpoint.json sushy/tests/unit/json_samples/endpoint_collection.json sushy/tests/unit/json_samples/error.json sushy/tests/unit/json_samples/error_single_ext_info.json sushy/tests/unit/json_samples/ethernet_interfaces.json sushy/tests/unit/json_samples/ethernet_interfaces_collection.json sushy/tests/unit/json_samples/eventdestination1.json sushy/tests/unit/json_samples/eventdestination2.json sushy/tests/unit/json_samples/eventdestination3.json sushy/tests/unit/json_samples/eventdestination_collection.json sushy/tests/unit/json_samples/eventservice.json sushy/tests/unit/json_samples/fabric.json sushy/tests/unit/json_samples/fabric_collection.json sushy/tests/unit/json_samples/firmwareinventory_collection.json sushy/tests/unit/json_samples/manager.json sushy/tests/unit/json_samples/manager_collection.json sushy/tests/unit/json_samples/manager_ethernet_interfaces.json sushy/tests/unit/json_samples/manager_ethernet_interfaces_collection.json sushy/tests/unit/json_samples/managerv1_18.json sushy/tests/unit/json_samples/message_registry.json sushy/tests/unit/json_samples/message_registry_file.json sushy/tests/unit/json_samples/message_registry_file_collection.json sushy/tests/unit/json_samples/network_adapter.json sushy/tests/unit/json_samples/network_adapter_collection.json sushy/tests/unit/json_samples/network_device_function.json sushy/tests/unit/json_samples/network_device_function_collection.json sushy/tests/unit/json_samples/network_port.json sushy/tests/unit/json_samples/network_port_collection.json sushy/tests/unit/json_samples/port.json sushy/tests/unit/json_samples/port_collection.json sushy/tests/unit/json_samples/power.json sushy/tests/unit/json_samples/processor.json sushy/tests/unit/json_samples/processor2.json sushy/tests/unit/json_samples/processor_collection.json sushy/tests/unit/json_samples/resourceblock.json sushy/tests/unit/json_samples/resourceblock_collection.json sushy/tests/unit/json_samples/resourcezone.json sushy/tests/unit/json_samples/resourcezone_collection.json sushy/tests/unit/json_samples/root.json sushy/tests/unit/json_samples/secure_boot.json sushy/tests/unit/json_samples/secure_boot_database.json sushy/tests/unit/json_samples/secure_boot_database_collection.json sushy/tests/unit/json_samples/session.json sushy/tests/unit/json_samples/session_collection.json sushy/tests/unit/json_samples/session_creation_headers.json sushy/tests/unit/json_samples/session_creation_headers_no_location.json sushy/tests/unit/json_samples/session_error.json sushy/tests/unit/json_samples/session_service.json sushy/tests/unit/json_samples/settings-body-bootsourceoverridemode-only.json sushy/tests/unit/json_samples/settings-body-lenovo-se450.json sushy/tests/unit/json_samples/settings-body-nokia.json sushy/tests/unit/json_samples/settings-lenovo-se450.json sushy/tests/unit/json_samples/settings-nokia.json sushy/tests/unit/json_samples/settings.json sushy/tests/unit/json_samples/simple_storage.json sushy/tests/unit/json_samples/simple_storage_collection.json sushy/tests/unit/json_samples/softwareinventory.json sushy/tests/unit/json_samples/storage.json sushy/tests/unit/json_samples/storage_collection.json sushy/tests/unit/json_samples/storage_controller.json sushy/tests/unit/json_samples/storage_controller_collection.json sushy/tests/unit/json_samples/storage_controller_settings.json sushy/tests/unit/json_samples/subprocessor.json sushy/tests/unit/json_samples/subprocessor_collection.json sushy/tests/unit/json_samples/system.json sushy/tests/unit/json_samples/system_collection.json sushy/tests/unit/json_samples/system_managedby_missing_manager_present.json sushy/tests/unit/json_samples/systemv1_20.json sushy/tests/unit/json_samples/task.json sushy/tests/unit/json_samples/task2.json sushy/tests/unit/json_samples/task_collection.json sushy/tests/unit/json_samples/task_monitor.json sushy/tests/unit/json_samples/taskservice.json sushy/tests/unit/json_samples/thermal.json sushy/tests/unit/json_samples/transfer_method_required_error.json sushy/tests/unit/json_samples/transfer_proto_required_error.json sushy/tests/unit/json_samples/transfer_proto_required_error2.json sushy/tests/unit/json_samples/transfer_proto_required_error3.json sushy/tests/unit/json_samples/updateservice.json sushy/tests/unit/json_samples/updateservice_no_inv.json sushy/tests/unit/json_samples/virtual_media.json sushy/tests/unit/json_samples/virtual_media_collection.json sushy/tests/unit/json_samples/virtual_media_collectionv1_6.json sushy/tests/unit/json_samples/virtual_mediav1_6.json sushy/tests/unit/json_samples/volume.json sushy/tests/unit/json_samples/volume2.json sushy/tests/unit/json_samples/volume3.json sushy/tests/unit/json_samples/volume4.json sushy/tests/unit/json_samples/volume_collection.json sushy/tests/unit/resources/__init__.py sushy/tests/unit/resources/test_base.py sushy/tests/unit/resources/test_settings.py sushy/tests/unit/resources/certificateservice/__init__.py sushy/tests/unit/resources/certificateservice/test_certificate.py sushy/tests/unit/resources/certificateservice/test_certificateservice.py sushy/tests/unit/resources/chassis/__init__.py sushy/tests/unit/resources/chassis/test_chassis.py sushy/tests/unit/resources/chassis/test_power.py sushy/tests/unit/resources/chassis/test_thermal.py sushy/tests/unit/resources/compositionservice/__init__.py sushy/tests/unit/resources/compositionservice/test_compositionservice.py sushy/tests/unit/resources/compositionservice/test_resourceblock.py sushy/tests/unit/resources/compositionservice/test_resourcezone.py sushy/tests/unit/resources/eventservice/__init__.py sushy/tests/unit/resources/eventservice/test_evendestination.py sushy/tests/unit/resources/eventservice/test_eventservice.py sushy/tests/unit/resources/fabric/__init__.py sushy/tests/unit/resources/fabric/test_endpoint.py sushy/tests/unit/resources/fabric/test_fabric.py sushy/tests/unit/resources/manager/__init__.py sushy/tests/unit/resources/manager/test_manager.py sushy/tests/unit/resources/manager/test_virtual_media.py sushy/tests/unit/resources/oem/__init__.py sushy/tests/unit/resources/oem/test_common.py sushy/tests/unit/resources/oem/test_fake.py sushy/tests/unit/resources/registry/__init__.py sushy/tests/unit/resources/registry/test_attribute_registry.py sushy/tests/unit/resources/registry/test_message_registry.py sushy/tests/unit/resources/registry/test_message_registry_file.py sushy/tests/unit/resources/sessionservice/__init__.py sushy/tests/unit/resources/sessionservice/test_session.py sushy/tests/unit/resources/sessionservice/test_sessionservice.py sushy/tests/unit/resources/system/__init__.py sushy/tests/unit/resources/system/test_bios.py sushy/tests/unit/resources/system/test_ethernet_interfaces.py sushy/tests/unit/resources/system/test_port.py sushy/tests/unit/resources/system/test_processor.py sushy/tests/unit/resources/system/test_secure_boot.py sushy/tests/unit/resources/system/test_secure_boot_database.py sushy/tests/unit/resources/system/test_simple_storage.py sushy/tests/unit/resources/system/test_system.py sushy/tests/unit/resources/system/network/__init__.py sushy/tests/unit/resources/system/network/test_adapter.py sushy/tests/unit/resources/system/network/test_device_function.py sushy/tests/unit/resources/system/network/test_port.py sushy/tests/unit/resources/system/storage/__init__.py sushy/tests/unit/resources/system/storage/test_controller.py sushy/tests/unit/resources/system/storage/test_drive.py sushy/tests/unit/resources/system/storage/test_storage.py sushy/tests/unit/resources/system/storage/test_volume.py sushy/tests/unit/resources/taskservice/__init__.py sushy/tests/unit/resources/taskservice/test_task.py sushy/tests/unit/resources/taskservice/test_taskservice.py sushy/tests/unit/resources/updateservice/__init__.py sushy/tests/unit/resources/updateservice/test_softwareinventory.py sushy/tests/unit/resources/updateservice/test_updateservice.py tools/generate-enum.py zuul.d/project.yaml zuul.d/sushy-jobs.yaml././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655096.0 sushy-5.5.0/sushy.egg-info/dependency_links.txt0000664000175000017500000000000100000000000021632 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655096.0 sushy-5.5.0/sushy.egg-info/entry_points.txt0000664000175000017500000000011700000000000021061 0ustar00zuulzuul00000000000000[sushy.resources.system.oems] contoso = sushy.resources.oem.fake:get_extension ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655096.0 sushy-5.5.0/sushy.egg-info/not-zip-safe0000664000175000017500000000000100000000000020012 0ustar00zuulzuul00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655096.0 sushy-5.5.0/sushy.egg-info/pbr.json0000664000175000017500000000005600000000000017243 0ustar00zuulzuul00000000000000{"git_version": "d0752a5", "is_release": true}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655096.0 sushy-5.5.0/sushy.egg-info/requires.txt0000664000175000017500000000010500000000000020160 0ustar00zuulzuul00000000000000pbr>=6.0.0 requests>=2.14.2 python-dateutil>=2.7.0 stevedore>=1.29.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655096.0 sushy-5.5.0/sushy.egg-info/top_level.txt0000664000175000017500000000000600000000000020312 0ustar00zuulzuul00000000000000sushy ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/test-requirements.txt0000664000175000017500000000047100000000000017162 0ustar00zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. # unit tests coverage!=4.4,>=4.0 # Apache-2.0 oslotest>=3.2.0 # Apache-2.0 stestr>=2.0.0 # Apache-2.0 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6955147 sushy-5.5.0/tools/0000775000175000017500000000000000000000000014057 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/tools/generate-enum.py0000775000175000017500000000514000000000000017170 0ustar00zuulzuul00000000000000#!/usr/bin/env python3 # 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 json import re import sys import textwrap import requests def to_underscores(item): words = re.findall(r'[A-Z](?:[a-z0-9]+|[A-Z0-9]*(?=[A-Z]|$))', item) return '_'.join(w.upper() for w in words) def main(): parser = argparse.ArgumentParser() parser.add_argument("url", help="URL of a DMTF definition") parser.add_argument("name", help="name of the enumeration") parser.add_argument("--compat", help="generate old-style constants", action="store_true") args = parser.parse_args() if args.url.startswith("https://") or args.url.startswith("http://"): resp = requests.get(args.url, timeout=30) resp.raise_for_status() content = resp.json() else: with open(args.url, "t") as fp: content = json.load(fp) try: definition = content["definitions"][args.name] except KeyError as exc: sys.exit(f"Key {exc} was not found in definition at {args.url}") try: items = definition["enum"] descriptions = definition.get("enumDescriptions", {}) except (TypeError, KeyError): sys.exit(f"Definition {args.name} is malformed or not en enumeration") items = [(to_underscores(item), item) for item in items] print(f"class {args.name}(enum.Enum):") for varname, item in items: print(f" {varname} = '{item}'") try: description = descriptions[item] except KeyError: pass else: # 79 (expected) - 4 (indentation) - 2 * 3 (quotes) = 69 lines = textwrap.wrap(description, 69) lines[0] = '"""' + lines[0] lines[-1] += '"""' for line in lines: print(f' {line}') print() if args.compat: print() print("# Backward compatibility") for varname, item in items: print(f"{to_underscores(args.name)}_{varname} = " f"{args.name}.{varname}") if __name__ == '__main__': sys.exit(main()) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/tox.ini0000664000175000017500000000602700000000000014237 0ustar00zuulzuul00000000000000[tox] minversion = 4.4.0 envlist = py3,pep8 [testenv] constrain_package_deps = true usedevelop = True setenv = VIRTUAL_ENV={envdir} PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 PYTHONWARNINGS=default::DeprecationWarning deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = stestr run --slowest {posargs} [testenv:pep8] deps = pre-commit allowlist_externals = pre-commit commands = pre-commit run --all-files --show-diff-on-failure {posargs} [testenv:codespell] description = Run codespell to check spelling deps = pre-commit commands = pre-commit run --all-files --show-diff-on-failure codespell [testenv:venv] setenv = PYTHONHASHSEED=0 deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt -r{toxinidir}/doc/requirements.txt commands = {posargs} [testenv:cover] setenv = {[testenv]setenv} PYTHON=coverage run --parallel-mode # After running this target, visit sushy/cover/index.html # in your browser, to see a nicer presentation report with annotated # HTML listings detailing missed lines. commands = coverage erase stestr run {posargs} coverage combine coverage report coverage html coverage xml -o cover/coverage.xml [testenv:docs] deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = sphinx-build -W -b html doc/source doc/build/html [testenv:pdf-docs] usedevelop = False allowlist_externals = make deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -b latex doc/source doc/build/pdf make -C doc/build/pdf [testenv:releasenotes] usedevelop = False deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:debug] commands = oslo_debug_helper -t sushy/tests {posargs} [flake8] show-source = True # E123, E125 skipped as they are invalid PEP-8. # E741 ambiguous variable name. # W503 Line break occurred before a binary operator. Conflicts with W504. ignore = E123,E125,E741,W503 # [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 builtins = _ exclude=.*,dist,doc,*lib/python*,*egg,build import-order-style = pep8 application-import-names = sushy filename = *.py ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1740655096.6955147 sushy-5.5.0/zuul.d/0000775000175000017500000000000000000000000014140 5ustar00zuulzuul00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/zuul.d/project.yaml0000664000175000017500000000071000000000000016470 0ustar00zuulzuul00000000000000- project: templates: - check-requirements - openstack-cover-jobs - openstack-python3-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 check: jobs: - ironic-cross-sushy - sushy-tempest-bios-redfish-pxe - sushy-tempest-uefi-redfish-vmedia gate: jobs: - ironic-cross-sushy - sushy-tempest-bios-redfish-pxe - sushy-tempest-uefi-redfish-vmedia ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1740655021.0 sushy-5.5.0/zuul.d/sushy-jobs.yaml0000664000175000017500000000111100000000000017124 0ustar00zuulzuul00000000000000- job: name: sushy-tempest-bios-redfish-pxe parent: ironic-tempest-bios-redfish-pxe irrelevant-files: - ^.*\.rst$ - ^\.pre-commit-config\.yaml$ - ^doc/.*$ - ^test-requirements.txt$ - ^sushy/tests/.*$ required-projects: - openstack/sushy - job: name: sushy-tempest-uefi-redfish-vmedia parent: ironic-tempest-uefi-redfish-vmedia irrelevant-files: - ^.*\.rst$ - ^\.pre-commit-config\.yaml$ - ^doc/.*$ - ^test-requirements.txt$ - ^sushy/tests/.*$ required-projects: - openstack/sushy