├── .coveragerc ├── .gitattributes ├── .gitignore ├── .gitreview ├── .mailmap ├── .testr.conf ├── CONTRIBUTING.rst ├── HACKING.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── babel.cfg ├── dmtf ├── DSP0266_1.0.0.pdf ├── DSP2043_0.99.0a.zip ├── DSP8010_1.0.0.zip ├── Dockerfile ├── README.rst ├── buildImage.sh ├── redfish-setup.sh └── run-redfish-simulator.sh ├── doc └── source │ ├── conf.py │ ├── contributing.rst │ ├── index.rst │ ├── installation.rst │ ├── readme.rst │ └── usage.rst ├── examples ├── __init__.py ├── simple.py └── walk-chassis.py ├── openstack-common.conf ├── python-redfish.spec ├── redfish ├── __init__.py ├── exception.py ├── functions.py ├── server.py ├── tests │ ├── __init__.py │ ├── base.py │ └── test_redfish.py └── types.py ├── requirements.txt ├── setup.cfg ├── setup.py ├── test-requirements.txt └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = redfish 4 | omit = redfish/tests/*,redfish/openstack/* 5 | 6 | [report] 7 | ignore-errors = True 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | 45 | # Translations 46 | *.mo 47 | *.pot 48 | 49 | # Django stuff: 50 | *.log 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | # PyBuilder 56 | target/ -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=review.openstack.org 3 | port=29418 4 | project=stackforge/python-redfish.git 5 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | # Format is: 2 | # 3 | # 4 | -------------------------------------------------------------------------------- /.testr.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ 3 | OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ 4 | OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ 5 | ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION 6 | test_id_option=--load-list $IDFILE 7 | test_list_option=--list 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | If you would like to contribute to the development of OpenStack, 2 | you must follow the steps in this page: 3 | 4 | http://docs.openstack.org/infra/manual/developers.html 5 | 6 | Once those steps have been completed, changes to OpenStack 7 | should be submitted for review via the Gerrit tool, following 8 | the workflow documented at: 9 | 10 | http://docs.openstack.org/infra/manual/developers.html#development-workflow 11 | 12 | Pull requests submitted through GitHub will be ignored. 13 | 14 | Bugs should be filed on Launchpad, not GitHub: 15 | 16 | https://bugs.launchpad.net/python-redfish 17 | -------------------------------------------------------------------------------- /HACKING.rst: -------------------------------------------------------------------------------- 1 | python-redfish Style Commandments 2 | =============================================== 3 | 4 | Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS 2 | include ChangeLog 3 | exclude .gitignore 4 | exclude .gitreview 5 | 6 | global-exclude *.pyc 7 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | python-redfish 2 | ============== 3 | 4 | This repository will be used to house the Redfish python library, a reference 5 | implementation to enable Python developers to communicate with the Redfish API 6 | (http://www.dmtf.org/standards/redfish). 7 | 8 | NOTE:: 9 | 10 | DRAFT - WORK IN PROGRESS 11 | 12 | The current Redfish specification revision is 1.0.0 - Note that the mockup 13 | is still at version 0.99.0a and may not reflect what the standard provides 14 | fully 15 | 16 | 17 | Project Structure 18 | ------------------- 19 | 20 | This project follows the same convention as OpenStack projects, eg. using pbr 21 | for build and test automation:: 22 | 23 | doc/ # documentation 24 | doc/source # the doc source files live here 25 | doc/build/html # output of building any docs will go here 26 | examples/ # any sample code using this library, eg. for education 27 | # should be put here 28 | redfish/ # the redfish library 29 | redfish/tests/ # python unit test suite 30 | 31 | Requirements 32 | ------------ 33 | 34 | To use the enclosed examples, you will need Python 2.7 35 | (https://www.python.org/downloads/). Note that Python 2.7.9 enforces greater 36 | SSL verification requiring server certificates be installed. Parameters to 37 | relax the requirements are available in the library, but these configurations 38 | are discouraged due to security. 39 | 40 | Python requirements are listed in requirements.txt; additional requirements for 41 | running the unit test suite are listed in test-requirements.txt. 42 | 43 | Developer setup 44 | --------------- 45 | 46 | To initialize a local development environment (eg, so you can run unit tests) 47 | you should run the following commands:: 48 | 49 | 50 | Further References 51 | ------------------ 52 | 53 | The data model documentation can be found here: 54 | http://www.redfishspecification.org/redfish-data-model-and-schema/ 55 | 56 | The overall protocol documentation can be found here: 57 | http://www.redfishspecification.org/ 58 | -------------------------------------------------------------------------------- /babel.cfg: -------------------------------------------------------------------------------- 1 | [python: **.py] 2 | 3 | -------------------------------------------------------------------------------- /dmtf/DSP0266_1.0.0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AevaOnline/python-redfish/359b5db8db222f7fe89d80cd5bb8811affb5d4bc/dmtf/DSP0266_1.0.0.pdf -------------------------------------------------------------------------------- /dmtf/DSP2043_0.99.0a.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AevaOnline/python-redfish/359b5db8db222f7fe89d80cd5bb8811affb5d4bc/dmtf/DSP2043_0.99.0a.zip -------------------------------------------------------------------------------- /dmtf/DSP8010_1.0.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AevaOnline/python-redfish/359b5db8db222f7fe89d80cd5bb8811affb5d4bc/dmtf/DSP8010_1.0.0.zip -------------------------------------------------------------------------------- /dmtf/Dockerfile: -------------------------------------------------------------------------------- 1 | # Invoke with docker run -p 8000:80 2 | # Then use by browsing http://localhost:8000 3 | FROM ubuntu:15.04 4 | MAINTAINER bruno.cornec@hp.com 5 | ENV DEBIAN_FRONTEND noninterative 6 | # Install deps for Redfish mockup 7 | RUN apt-get update 8 | RUN apt-get -y install apache2 unzip sed 9 | EXPOSE 80 10 | COPY redfish-setup.sh /tmp/redfish-setup.sh 11 | COPY DSP2043_0.99.0a.zip /tmp/DSP2043_0.99.0a.zip 12 | RUN chmod 755 /tmp/redfish-setup.sh 13 | CMD /tmp/redfish-setup.sh 14 | -------------------------------------------------------------------------------- /dmtf/README.rst: -------------------------------------------------------------------------------- 1 | DMTF Redfish specification 2 | -------------------------- 3 | 4 | This directory contains the current references from the DMTF on the Redfish 5 | specification (1.0.0 at the time of the writing) 6 | 7 | In order to ease test, the DMTF has published a mockup environment to simulate 8 | a Redfish based system so it is possible to write programs without real Redfish 9 | compliant hardware platform. 10 | 11 | Docker container 12 | ---------------- 13 | 14 | In order to help testing python-redfish, this directory provides a script which 15 | you should be able to run on your system (providing you have docker support and 16 | a docker registry) which will create a docker container running the DMTF Redfish 17 | mockup on the port 8000. 18 | 19 | To build your container, just issue: ./buildImage.sh 20 | To launch it, just issue: ./run-redfish-simulator.sh 21 | To use it, just issue: firefox http://localhost:8000/redfish/v1 22 | 23 | Systems entry point: 24 | http://localhost:8000/redfish/v1/Systems 25 | 26 | Chassis entry point: 27 | http://localhost:8000/redfish/v1/Chassis 28 | 29 | Managers entry point: 30 | http://localhost:8000/redfish/v1/Managers 31 | -------------------------------------------------------------------------------- /dmtf/buildImage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Build and tag images 4 | docker rmi redfish-simulator 5 | docker build -t "redfish-simulator" . 6 | docker tag -f redfish-simulator:latest localhost:5000/redfish-simulator 7 | -------------------------------------------------------------------------------- /dmtf/redfish-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function start_apache { 4 | echo "Launching apache2 in foreground with /usr/sbin/apache2ctl -DFOREGROUND -k start" 5 | /usr/sbin/apache2ctl -DFOREGROUND -k start 6 | } 7 | 8 | function stop_apache { 9 | echo "Stopping apache2" 10 | /usr/sbin/apache2ctl stop 11 | } 12 | 13 | # Trap to have a clean exit 14 | trap stop_apache HUP INT QUIT KILL TERM 15 | 16 | 17 | # Main 18 | cd /var/www/html 19 | unzip -q -o /tmp/DSP2043_0.99.0a.zip 20 | chmod 755 DSP2043_0.99.0a 21 | ln -sf DSP2043_0.99.0a redfish 22 | cd redfish 23 | ln -sf . v1 24 | cd .. 25 | ip a 26 | #sed -i -e 's/Listen 80/Listen 8000/' /etc/apache2/ports.conf 27 | start_apache 28 | -------------------------------------------------------------------------------- /dmtf/run-redfish-simulator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Build the docker container first 4 | docker build -t localhost:5000/redfish-simulator:latest . 5 | # Th -p option needs to be after the run command. No warning is given if before but doesn't work 6 | docker rm "redfish-simulator" 7 | docker run -d -p 8000:80 --name "redfish-simulator" localhost:5000/redfish-simulator:latest 8 | echo "Launch your browser and load http://localhost:8000/redfish/v1" 9 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 11 | # implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | import sys 17 | 18 | sys.path.insert(0, os.path.abspath('../..')) 19 | # -- General configuration ---------------------------------------------------- 20 | 21 | # Add any Sphinx extension module names here, as strings. They can be 22 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 23 | extensions = [ 24 | 'sphinx.ext.autodoc', 25 | #'sphinx.ext.intersphinx', 26 | 'oslosphinx' 27 | ] 28 | 29 | # autodoc generation is a bit aggressive and a nuisance when doing heavy 30 | # text edit cycles. 31 | # execute "export SPHINX_DEBUG=1" in your terminal to disable 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The master toctree document. 37 | master_doc = 'index' 38 | 39 | # General information about the project. 40 | project = u'python-redfish' 41 | copyright = u'2013, OpenStack Foundation' 42 | 43 | # If true, '()' will be appended to :func: etc. cross-reference text. 44 | add_function_parentheses = True 45 | 46 | # If true, the current module name will be prepended to all description 47 | # unit titles (such as .. function::). 48 | add_module_names = True 49 | 50 | # The name of the Pygments (syntax highlighting) style to use. 51 | pygments_style = 'sphinx' 52 | 53 | # -- Options for HTML output -------------------------------------------------- 54 | 55 | # The theme to use for HTML and HTML Help pages. Major themes that come with 56 | # Sphinx are currently 'default' and 'sphinxdoc'. 57 | # html_theme_path = ["."] 58 | # html_theme = '_theme' 59 | # html_static_path = ['static'] 60 | 61 | # Output file base name for HTML help builder. 62 | htmlhelp_basename = '%sdoc' % project 63 | 64 | # Grouping the document tree into LaTeX files. List of tuples 65 | # (source start file, target name, title, author, documentclass 66 | # [howto/manual]). 67 | latex_documents = [ 68 | ('index', 69 | '%s.tex' % project, 70 | u'%s Documentation' % project, 71 | u'OpenStack Foundation', 'manual'), 72 | ] 73 | 74 | # Example configuration for intersphinx: refer to the Python standard library. 75 | #intersphinx_mapping = {'http://docs.python.org/': None} 76 | -------------------------------------------------------------------------------- /doc/source/contributing.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | .. include:: ../../CONTRIBUTING.rst 5 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | .. python-redfish documentation master file, created by 2 | sphinx-quickstart on Tue Jul 9 22:26:36 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to python-redfish's documentation! 7 | ======================================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | readme 15 | installation 16 | usage 17 | contributing 18 | 19 | Indices and tables 20 | ================== 21 | 22 | * :ref:`genindex` 23 | * :ref:`modindex` 24 | * :ref:`search` 25 | 26 | -------------------------------------------------------------------------------- /doc/source/installation.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Installation 3 | ============ 4 | 5 | At the command line:: 6 | 7 | $ pip install python-redfish 8 | 9 | Or, if you have virtualenvwrapper installed:: 10 | 11 | $ mkvirtualenv python-redfish 12 | $ pip install python-redfish 13 | -------------------------------------------------------------------------------- /doc/source/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../README.rst 2 | -------------------------------------------------------------------------------- /doc/source/usage.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Usage 3 | ======== 4 | 5 | To use python-redfish in a project:: 6 | 7 | import redfish 8 | -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'deva' 2 | -------------------------------------------------------------------------------- /examples/simple.py: -------------------------------------------------------------------------------- 1 | from redfish import connection 2 | 3 | host = '127.0.0.1' 4 | user_name = 'Admin' 5 | password = 'password' 6 | server = connection.RedfishConnection(host, user_name, password) -------------------------------------------------------------------------------- /examples/walk-chassis.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | #import logging 4 | import sys 5 | from oslo_config import cfg 6 | from oslo_log import log as logging 7 | 8 | import redfish 9 | 10 | # Sets up basic logging for this module 11 | #log_root = logging.getLogger('redfish') 12 | #log_root.addHandler(logging.StreamHandler(sys.stdout)) 13 | #log_root.setLevel(logging.DEBUG) 14 | 15 | CONF = cfg.CONF 16 | logging.set_defaults(['redfish=DEBUG']) 17 | logging.register_options(CONF) 18 | #logging.setup(CONF, "redfish") 19 | 20 | # Connect to a redfish API endpoint 21 | host = 'http://localhost' 22 | user_name = '' 23 | password = '' 24 | 25 | # This returns a RedfishConnection object, which implements 26 | # the low-level HTTP methods like GET, PUT, etc 27 | connection = redfish.server.connect(host, user_name, password) 28 | 29 | # From this connection, we can get the Root resource. 30 | # Note that the root resource is somewhat special - you create it from 31 | # the connection, but you create other resources from the root resource. 32 | # (You don't strictly have to do this, but it's simpler.) 33 | root = connection.get_root() 34 | 35 | print("\n") 36 | print("ROOT CONTROLLER") 37 | print("===============") 38 | print(root) 39 | 40 | 41 | # The Root class has well-defined top-level resources, such as 42 | # chassis, systems, managers, sessions, etc... 43 | chassis = root.get_chassis() 44 | 45 | print("\n") 46 | print("CHASSIS DATA") 47 | print("============") 48 | print(chassis) 49 | print("\n") 50 | print("WALKING CHASSIS") 51 | print("\n") 52 | print("CHASSIS contains %d items" % len(chassis)) 53 | print("\n") 54 | for item in chassis: 55 | print("SYSTEM") 56 | print("======") 57 | print(item) 58 | print("\n") 59 | -------------------------------------------------------------------------------- /openstack-common.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | 3 | # The list of modules to copy from oslo-incubator.git 4 | 5 | # The base module to hold the copy of openstack.common 6 | base=redfish 7 | -------------------------------------------------------------------------------- /python-redfish.spec: -------------------------------------------------------------------------------- 1 | %global srcname redfish 2 | 3 | Name: python-%{srcname} 4 | Version: 0.1 5 | Release: %mkrel 1 6 | Summary: Redfish python library 7 | 8 | Group: Development/Python 9 | License: Apache v2.0 10 | URL: https://github.com/devananda/%{name} 11 | Source0: %name-%version.tar.gz 12 | 13 | BuildArch: noarch 14 | BuildRequires: python-devel 15 | BuildRequires: python-setuptools 16 | 17 | %description 18 | The Redfish API supports dialoging with a Redfish compliant 19 | system such as defined by http://www.redfishcertification.org 20 | 21 | %prep 22 | %setup -q -n %{name} 23 | #-%{version} 24 | 25 | %build 26 | %{__python} setup.py build 27 | 28 | %install 29 | %{__python} setup.py install -O1 --skip-build --root %{buildroot} 30 | 31 | %files 32 | %doc README.rst examples/*.py 33 | %dir %{python_sitelib}/redfish 34 | %{python_sitelib}/redfish/*.py* 35 | %{python_sitelib}/redfish/tests/*.py* 36 | %{python_sitelib}/python_redfish* 37 | -------------------------------------------------------------------------------- /redfish/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | import pbr.version 16 | 17 | import redfish.server 18 | import redfish.types 19 | 20 | 21 | __version__ = pbr.version.VersionInfo( 22 | 'redfish').version_string() 23 | -------------------------------------------------------------------------------- /redfish/exception.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | 16 | class RedfishException(Exception): 17 | """Base class for redfish exceptions""" 18 | def __init__(self, message=None, **kwargs): 19 | self.kwargs = kwargs 20 | 21 | if not message: 22 | try: 23 | message = self.message % kwargs 24 | except Excetion as e: 25 | LOG.exception('Error in string format operation') 26 | message = self.message 27 | super(RedfishException, self).__init__(message) 28 | 29 | 30 | class ObjectLoadException(RedfishException): 31 | pass 32 | -------------------------------------------------------------------------------- /redfish/functions.py: -------------------------------------------------------------------------------- 1 | 2 | # Copyright 2014 Hewlett-Packard Development Company, L.P. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | 17 | """ 18 | 19 | Provides functions for using the Redfish RESTful API. 20 | 21 | """ 22 | 23 | import collections 24 | import json 25 | import sys 26 | from redfish import connection 27 | 28 | class RedfishOperation(connection.RedfishConnection): 29 | 30 | def reset_server(self): 31 | (status, headers, system) = self.rest_get('/redfish/v1/Systems', None) 32 | 33 | memberuri = system['links']['Member'][0]['href'] 34 | # verify expected type 35 | # hint: don't limit to version 0 here as we will rev to 1.0 at some point hopefully with minimal changes 36 | # assert(connection.get_type(system) == 'ComputerSystem.0' or connection.get_type(system) == 'ComputerSystem.1') 37 | 38 | # verify it supports POST 39 | # assert(connection.operation_allowed(headers, 'POST')) 40 | 41 | action = dict() 42 | action['Action'] = 'Reset' 43 | action['ResetType'] = 'ForceRestart' 44 | 45 | # perform the POST action 46 | print('POST ' + json.dumps(action) + ' to ' + memberuri) 47 | (status, headers, response) = self.rest_post(memberuri, None, action) 48 | print('POST response = ' + str(status)) 49 | connection.print_extended_error(response) 50 | -------------------------------------------------------------------------------- /redfish/server.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Hewlett-Packard Development Company, L.P. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | 16 | """ 17 | STARTING ASSUMPTIONS 18 | 19 | On URIs: 20 | 21 | The Redfish RESTful API is a "hypermedia API" by design. This is to avoid 22 | building in restrictive assumptions to the data model that will make it 23 | difficult to adapt to future hardware implementations. A hypermedia API avoids 24 | these assumptions by making the data model discoverable via links between 25 | resources. 26 | 27 | A URI should be treated by the client as opaque, and thus should not be 28 | attempted to be understood or deconstructed by the client. Only specific top 29 | level URIs (any URI in this sample code) may be assumed, and even these may be 30 | absent based upon the implementation (e.g. there might be no /redfish/v1/Systems 31 | collection on something that doesn't have compute nodes.) 32 | 33 | The other URIs must be discovered dynamically by following href links. This is 34 | because the API will eventually be implemented on a system that breaks any 35 | existing data model "shape" assumptions we may make now. In particular, 36 | clients should not make assumptions about the URIs for the resource members of 37 | a collection. For instance, the URI of a collection member will NOT always be 38 | /redfish/v1/.../collection/1, or 2. On systems with multiple compute nodes per 39 | manager, a System collection member might be /redfish/v1/Systems/C1N1. 40 | 41 | This sounds very complicated, but in reality (as these examples demonstrate), 42 | if you are looking for specific items, the traversal logic isn't too 43 | complicated. 44 | 45 | On Resource Model Traversal: 46 | 47 | Although the resources in the data model are linked together, because of cross 48 | link references between resources, a client may not assume the resource model 49 | is a tree. It is a graph instead, so any crawl of the data model should keep 50 | track of visited resources to avoid an infinite traversal loop. 51 | 52 | A reference to another resource is any property called "href" no matter where 53 | it occurs in a resource. 54 | 55 | An external reference to a resource outside the data model is referred to by a 56 | property called "extref". Any resource referred to by extref should not be 57 | assumed to follow the conventions of the API. 58 | 59 | On Resource Versions: 60 | 61 | Each resource has a "Type" property with a value of the format Tyepname.x.y.z 62 | where 63 | * x = major version - incrementing this is a breaking change to the schema y = 64 | * minor version - incrementing this is a non-breaking additive change to the 65 | * schema z = errata - non-breaking change 66 | 67 | Because all resources are versioned and schema also have a version, it is 68 | possible to design rules for "nearest" match (e.g. if you are interacting with 69 | multiple services using a common batch of schema files). The mechanism is not 70 | prescribed, but a client should be prepared to encounter both older and newer 71 | versions of resource types. 72 | 73 | On HTTP POST to create: 74 | 75 | WHen POSTing to create a resource (e.g. create an account or session) the 76 | guarantee is that a successful response includes a "Location" HTTP header 77 | indicating the resource URI of the newly created resource. The POST may also 78 | include a representation of the newly created object in a JSON response body 79 | but may not. Do not assume the response body, but test it. It may also be an 80 | ExtendedError object. 81 | 82 | HTTP REDIRECT: 83 | 84 | All clients must correctly handle HTTP redirect. We (or Redfish) may 85 | eventually need to use redirection as a way to alias portions of the data 86 | model. 87 | 88 | FUTURE: Asynchronous tasks 89 | 90 | In the future some operations may start asynchonous tasks. In this case, the 91 | client should recognized and handle HTTP 202 if needed and the 'Location' 92 | header will point to a resource with task information and status. 93 | 94 | JSON-SCHEMA: 95 | 96 | The json-schema available at /redfish/v1/Schemas governs the content of the 97 | resources, but keep in mind: 98 | * not every property in the schema is implemented in every implementation. 99 | * some properties are schemed to allow both null and anotehr type like string 100 | * or integer. 101 | 102 | Robust client code should check both the existence and type of interesting 103 | properties and fail gracefully if expectations are not met. 104 | 105 | GENERAL ADVICE: 106 | 107 | Clients should always be prepared for: 108 | * unimplemented properties (e.g. a property doesn't apply in a particular case) 109 | * null values in some cases if the value of a property is not currently known 110 | * due to system conditions HTTP status codes other than 200 OK. Can your code 111 | * handle an HTTP 500 Internal Server Error with no other info? URIs are case 112 | * insensitive HTTP header names are case insensitive JSON Properties and Enum 113 | * values are case sensitive A client should be tolerant of any set of HTTP 114 | * headers the service returns 115 | 116 | """ 117 | 118 | import base64 119 | import gzip 120 | import hashlib 121 | import httplib 122 | import json 123 | import ssl 124 | import StringIO 125 | import sys 126 | import urllib2 127 | from urlparse import urlparse 128 | 129 | from oslo_log import log as logging 130 | 131 | from redfish import exception 132 | from redfish import types 133 | 134 | 135 | LOG = logging.getLogger('redfish') 136 | 137 | 138 | def connect(host, user, password): 139 | return RedfishConnection(host, user, password) 140 | 141 | 142 | class RedfishConnection(object): 143 | """Implements basic connection handling for Redfish APIs.""" 144 | 145 | def __init__(self, host, user_name, password, 146 | auth_token=None, enforce_SSL=True): 147 | """Initialize a connection to a Redfish service.""" 148 | super(RedfishConnection, self).__init__() 149 | 150 | self.user_name = user_name 151 | self.password = password 152 | self.auth_token = auth_token 153 | self.enforce_SSL = enforce_SSL 154 | 155 | # context for the last status and header returned from a call 156 | self.status = None 157 | self.headers = None 158 | 159 | # If the http schema wasn't specified, default to HTTPS 160 | if host[0:4] != 'http': 161 | host = 'https://' + host 162 | self.host = host 163 | 164 | self._connect() 165 | 166 | if not self.auth_token: 167 | # TODO: if a token is returned by this call, cache it. However, 168 | # the sample HTML does not include any token data, so it's unclear 169 | # what we should do here. 170 | LOG.debug('Initiating session with host %s', self.host) 171 | auth_dict = {'Password': self.password, 'UserName': self.user_name} 172 | response = self.rest_post( 173 | '/redfish/v1/Sessions', None, json.dumps(auth_dict)) 174 | 175 | # TODO: do some schema discovery here and cache the result 176 | # self.schema = ... 177 | LOG.info('Connection established to host %s', self.host) 178 | 179 | def _connect(self): 180 | LOG.debug("Establishing connection to host %s", self.host) 181 | url = urlparse(self.host) 182 | if url.scheme == 'https': 183 | # New in Python 2.7.9, SSL enforcement is defaulted on. 184 | # It can be opted-out of, which might be useful for debugging 185 | # some things. The below case is the Opt-Out condition and 186 | # should be used with GREAT caution. 187 | if (sys.version_info.major == 2 188 | and sys.version_info.minor == 7 189 | and sys.version_info.micro >= 9 190 | and self.enforce_SSL == False): 191 | cont = ssl.SSLContext(ssl.PROTOCOL_TLSv1) 192 | cont.verify_mode = ssl.CERT_NONE 193 | self.connection = httplib.HTTPSConnection( 194 | host=url.netloc, strict=True, context=cont) 195 | else: 196 | self.connection = httplib.HTTPSConnection( 197 | host=url.netloc, strict=True) 198 | elif url.scheme == 'http': 199 | self.connection = httplib.HTTPConnection( 200 | host=url.netloc, strict=True) 201 | else: 202 | raise exception.RedfishException( 203 | message='Unknown connection schema') 204 | 205 | def _op(self, operation, suburi, request_headers=None, request_body=None): 206 | """ 207 | REST operation generic handler 208 | 209 | :param operation: GET, POST, etc 210 | :param suburi: the URI path to the resource 211 | :param request_headers: optional dict of headers 212 | :param request_body: optional JSON body 213 | """ 214 | # ensure trailing slash 215 | if suburi[-1:] != '/': 216 | suburi = suburi + '/' 217 | url = urlparse(self.host + suburi) 218 | 219 | if not isinstance(request_headers, dict): 220 | request_headers = dict() 221 | request_headers['Content-Type'] = 'application/json' 222 | 223 | # if X-Auth-Token specified, supply it instead of basic auth 224 | if self.auth_token is not None: 225 | request_headers['X-Auth-Token'] = self.auth_token 226 | # else use user_name/password and Basic Auth 227 | elif self.user_name is not None and self.password is not None: 228 | request_headers['Authorization'] = ("BASIC " + base64.b64encode( 229 | self.user_name + ":" + self.password)) 230 | # TODO: add support for other types of auth 231 | 232 | redir_count = 4 233 | while redir_count: 234 | # NOTE: Do not assume every HTTP operation will return a JSON body. 235 | # For example, ExtendedError structures are only required for 236 | # HTTP 400 errors and are optional elsewhere as they are mostly 237 | # redundant for many of the other HTTP status code. In particular, 238 | # 200 OK responses should not have to return any body. 239 | self.connection.request(operation, url.path, 240 | headers=request_headers, body=json.dumps(request_body)) 241 | resp = self.connection.getresponse() 242 | body = resp.read() 243 | # NOTE: this makes sure the headers names are all lower case 244 | # because HTTP says they are case insensitive 245 | headers = dict((x.lower(), y) for x, y in resp.getheaders()) 246 | 247 | # Follow HTTP redirect 248 | if resp.status == 301 and 'location' in headers: 249 | url = urlparse(headers['location']) 250 | # TODO: cache these redirects 251 | LOG.debug("Following redirect to %s", headers['location']) 252 | redir_count -= 1 253 | else: 254 | break 255 | 256 | response = dict() 257 | try: 258 | response = json.loads(body.decode('utf-8')) 259 | except ValueError: # if it doesn't decode as json 260 | # NOTE: resources may return gzipped content, so try to decode 261 | # as gzip (we should check the headers for Content-Encoding=gzip) 262 | try: 263 | gzipper = gzip.GzipFile(fileobj=StringIO.StringIO(body)) 264 | uncompressed_string = gzipper.read().decode('UTF-8') 265 | response = json.loads(uncompressed_string) 266 | except: 267 | raise exception.RedfishException(message= 268 | 'Failed to parse response as a JSON document, ' 269 | 'received "%s".' % body) 270 | 271 | self.status = resp.status 272 | self.headers = headers 273 | return response 274 | 275 | def rest_get(self, suburi, request_headers): 276 | """REST GET 277 | 278 | :param: suburi 279 | :param: request_headers 280 | """ 281 | # NOTE: be prepared for various HTTP responses including 500, 404, etc 282 | return self._op('GET', suburi, request_headers, None) 283 | 284 | def rest_patch(self, suburi, request_headers, request_body): 285 | """REST PATCH 286 | 287 | :param: suburi 288 | :param: request_headers 289 | :param: request_body 290 | NOTE: this body is a dict, not a JSONPATCH document. 291 | redfish does not follow IETF JSONPATCH standard 292 | https://tools.ietf.org/html/rfc6902 293 | """ 294 | # NOTE: be prepared for various HTTP responses including 500, 404, 202 295 | return self._op('PATCH', suburi, request_headers, request_body) 296 | 297 | def rest_put(self, suburi, request_headers, request_body): 298 | """REST PUT 299 | 300 | :param: suburi 301 | :param: request_headers 302 | :param: request_body 303 | """ 304 | # NOTE: be prepared for various HTTP responses including 500, 404, 202 305 | return self._op('PUT', suburi, request_headers, request_body) 306 | 307 | def rest_post(self, suburi, request_headers, request_body): 308 | """REST POST 309 | 310 | :param: suburi 311 | :param: request_headers 312 | :param: request_body 313 | """ 314 | # NOTE: don't assume any newly created resource is included in the 315 | # response. Only the Location header matters. 316 | # the response body may be the new resource, it may be an 317 | # ExtendedError, or it may be empty. 318 | return self._op('POST', suburi, request_headers, request_body) 319 | 320 | def rest_delete(self, suburi, request_headers): 321 | """REST DELETE 322 | 323 | :param: suburi 324 | :param: request_headers 325 | """ 326 | # NOTE: be prepared for various HTTP responses including 500, 404 327 | # NOTE: response may be an ExtendedError or may be empty 328 | return self._op('DELETE', suburi, request_headers, None) 329 | 330 | def get_root(self): 331 | return types.Root(self.rest_get('/redfish/v1', {}), connection=self) 332 | 333 | 334 | class Version(object): 335 | def __init__(self, string): 336 | try: 337 | buf = string.split('.') 338 | if len(buf) < 2: 339 | raise AttributeError 340 | except AttributeError: 341 | raise RedfishException(message="Failed to parse version string") 342 | self.major = int(buf[0]) 343 | self.minor = int(buf[1]) 344 | 345 | def __repr__(self): 346 | return str(self.major) + '.' + str(self.minor) 347 | 348 | 349 | # return the type of an object (down to the major version, skipping minor, and errata) 350 | def get_type(obj): 351 | typever = obj['Type'] 352 | typesplit = typever.split('.') 353 | return typesplit[0] + '.' + typesplit[1] 354 | 355 | 356 | # checks HTTP response headers for specified operation (e.g. 'GET' or 'PATCH') 357 | def operation_allowed(headers_dict, operation): 358 | if 'allow' in headers_dict: 359 | if headers_dict['allow'].find(operation) != -1: 360 | return True 361 | return False 362 | 363 | 364 | # Message registry support 365 | # XXX not supported yet 366 | message_registries = {} 367 | 368 | 369 | # Build a list of decoded messages from the extended_error using the message 370 | # registries An ExtendedError JSON object is a response from the with its own 371 | # schema. This function knows how to parse the ExtendedError object and, using 372 | # any loaded message registries, render an array of plain language strings that 373 | # represent the response. 374 | def render_extended_error_message_list(extended_error): 375 | messages = [] 376 | if isinstance(extended_error, dict): 377 | if 'Type' in extended_error and extended_error['Type'].startswith('ExtendedError.'): 378 | for msg in extended_error['Messages']: 379 | MessageID = msg['MessageID'] 380 | x = MessageID.split('.') 381 | registry = x[0] 382 | msgkey = x[len(x) - 1] 383 | 384 | # if the correct message registry is loaded, do string resolution 385 | if registry in message_registries: 386 | if registry in message_registries and msgkey in message_registries[registry]['Messages']: 387 | msg_dict = message_registries[registry]['Messages'][msgkey] 388 | msg_str = MessageID + ': ' + msg_dict['Message'] 389 | 390 | for argn in range(0, msg_dict['NumberOfArgs']): 391 | subst = '%' + str(argn+1) 392 | msg_str = msg_str.replace(subst, str(msg['MessageArgs'][argn])) 393 | 394 | if 'Resolution' in msg_dict and msg_dict['Resolution'] != 'None': 395 | msg_str += ' ' + msg_dict['Resolution'] 396 | 397 | messages.append(msg_str) 398 | else: # no message registry, simply return the msg object in string form 399 | messages.append('No Message Registry Info: '+ str(msg)) 400 | 401 | return messages 402 | 403 | 404 | # Print a list of decoded messages from the extended_error using the message registries 405 | def print_extended_error(extended_error): 406 | messages = render_extended_error_message_list(extended_error) 407 | msgcnt = 0 408 | for msg in messages: 409 | print('\t' + msg) 410 | msgcnt += 1 411 | if msgcnt == 0: # add a spacer 412 | print 413 | -------------------------------------------------------------------------------- /redfish/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AevaOnline/python-redfish/359b5db8db222f7fe89d80cd5bb8811affb5d4bc/redfish/tests/__init__.py -------------------------------------------------------------------------------- /redfish/tests/base.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright 2010-2011 OpenStack Foundation 4 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | from oslotest import base 19 | 20 | 21 | class TestCase(base.BaseTestCase): 22 | 23 | """Test case base class for all unit tests.""" 24 | -------------------------------------------------------------------------------- /redfish/tests/test_redfish.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """ 16 | test_redfish 17 | ---------------------------------- 18 | 19 | Tests for `redfish` module. 20 | """ 21 | 22 | import fixtures 23 | import httplib 24 | import json 25 | import mock 26 | import ssl 27 | 28 | from redfish.tests import base 29 | from redfish import server 30 | from redfish import types 31 | 32 | 33 | def get_fake_params(host=None, user=None, pword=None): 34 | if not host: 35 | host = 'https://127.0.0.1' 36 | if not user: 37 | user = 'admin' 38 | if not pword: 39 | pword = 'password' 40 | return (host, user, pword) 41 | 42 | 43 | def get_response(): 44 | class _response(object): 45 | status = 200 46 | def read(self): 47 | return '{"foo": "bar"}' 48 | def getheaders(self): 49 | return [('Fake-Header', 'fake value')] 50 | return _response() 51 | 52 | 53 | class TestException(Exception): 54 | pass 55 | 56 | 57 | class TestRedfishConnection(base.TestCase): 58 | 59 | def setUp(self): 60 | super(TestRedfishConnection, self).setUp() 61 | self.log_fixture = self.useFixture(fixtures.FakeLogger()) 62 | self.con_mock = mock.MagicMock() 63 | self.con_mock.getresponse = get_response 64 | 65 | self.http_mock = mock.patch.object(httplib, 'HTTPConnection').start() 66 | self.http_mock.return_value = self.con_mock 67 | self.https_mock = mock.patch.object(httplib, 'HTTPSConnection').start() 68 | self.https_mock.return_value = self.con_mock 69 | self.addCleanup(self.http_mock.stop) 70 | self.addCleanup(self.https_mock.stop) 71 | 72 | def test_create_ok(self): 73 | con = server.RedfishConnection(*get_fake_params()) 74 | self.assertEqual(1, self.https_mock.call_count) 75 | self.assertEqual(0, self.http_mock.call_count) 76 | 77 | def test_create_calls_https_connect(self): 78 | self.https_mock.side_effect = TestException() 79 | self.assertRaises(TestException, 80 | server.RedfishConnection, 81 | *get_fake_params(host='https://fake')) 82 | 83 | def test_create_calls_http_connect(self): 84 | self.http_mock.side_effect = TestException() 85 | self.assertRaises(TestException, 86 | server.RedfishConnection, 87 | *get_fake_params(host='http://fake')) 88 | 89 | # TODO: add test for unknown connection schema (eg, ftp://) 90 | 91 | # FIXME: ssl module has no attribute 'SSLContext' 92 | # NOTE: skip this test if sys.version_info (major, minor) != (2, 7) and micro < 9 93 | # @mock.patch.object(ssl, 'SSLContext') 94 | # def test_insecure_ssl(self, ssl_mock): 95 | # ssl_mock.return_value = mock.Mock() 96 | # con = connection.RedfishConnection(*get_fake_params) 97 | # ssl_mock.assert_called_once_with(ssl.PROTOCOL_TLSv1) 98 | 99 | def test_get_ok(self): 100 | con = server.RedfishConnection(*get_fake_params()) 101 | res = con.rest_get('/v1/test/', '') 102 | self.assertEqual(200, con.status) 103 | # Headers ae lower cased when returned 104 | self.assertIn('fake-header', con.headers.keys()) 105 | self.assertIn('foo', res.keys()) 106 | self.con_mock.request.assert_called_with( 107 | 'GET', '/v1/test/', body='null', headers=mock.ANY) 108 | 109 | # TODO: add test for redirects 110 | 111 | # TODO: add test for collections 112 | 113 | # TODO: add test for gzip'd body 114 | 115 | def test_post_ok(self): 116 | body = '{"fake": "body"}' 117 | json_body = json.dumps(body) 118 | con = server.RedfishConnection(*get_fake_params()) 119 | res = con.rest_post('/v1/test/', '', body) 120 | self.assertEqual(200, con.status) 121 | self.con_mock.request.assert_called_with( 122 | 'POST', '/v1/test/', body=json_body, headers=mock.ANY) 123 | -------------------------------------------------------------------------------- /redfish/types.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Hewlett-Packard Development Company, L.P. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | 16 | """ 17 | Redfish Resource Types 18 | """ 19 | 20 | import base64 21 | import gzip 22 | import hashlib 23 | import httplib 24 | import json 25 | import ssl 26 | import StringIO 27 | import sys 28 | import urllib2 29 | from urlparse import urlparse 30 | 31 | from oslo_log import log as logging 32 | from redfish import exception 33 | 34 | LOG = logging.getLogger('redfish') 35 | 36 | 37 | class Base(object): 38 | def __init__(self, obj, connection=None): 39 | self._conn = connection 40 | """handle to the redfish connection""" 41 | 42 | self._attrs = [] 43 | """list of discovered attributes""" 44 | 45 | self._links = [] 46 | """list of linked resources""" 47 | 48 | # parse the individual resources, appending them to 49 | # the list of object attributes 50 | for k in obj.keys(): 51 | ref = k.lower() 52 | if ref in ["links", "oem", "items"]: 53 | continue 54 | setattr(self, ref, obj[k]) 55 | self._attrs.append(ref) 56 | 57 | # make sure the required attributes are present 58 | if not getattr(self, 'name', False): 59 | raise ObjectLoadException( 60 | "Failed to load object. Reason: could not determine name.") 61 | if not getattr(self, 'type', False): 62 | raise ObjectLoadException( 63 | "Failed to load object. Reason: could not determine type.") 64 | 65 | if getattr(self, 'serviceversion', False): 66 | self.type = self.type.replace('.' + self.serviceversion, '') 67 | else: 68 | # TODO: use a regex here to strip and store the version 69 | # instead of assuming it is 7 chars long 70 | self.type = self.type[:-7] 71 | 72 | # Lastly, parse the 'links' resource. 73 | # Note that this may have different nested structure, depending on 74 | # what type of resource this is, or what vendor it is. 75 | # subclasses may follow this by parsing other resources / collections 76 | self._parse_links(obj) 77 | 78 | def _parse_links(self, obj): 79 | """Map linked resources to getter functions 80 | 81 | The root resource returns a dict of links to top-level resources 82 | """ 83 | def getter(connection, href): 84 | def _get(): 85 | return connection.rest_get(href, {}) 86 | return _get 87 | 88 | for k in obj['links']: 89 | ref = "get_" + k.lower() 90 | self._links.append(ref) 91 | href = obj['links'][k]['href'] 92 | setattr(self, ref, getter(self._conn, href)) 93 | 94 | def __repr__(self): 95 | """Return this object's _attrs as a dict""" 96 | res = {} 97 | for a in self._attrs: 98 | res[a] = getattr(self, a) 99 | return res 100 | 101 | def __str__(self): 102 | """Return the string representation of this object's _attrs""" 103 | return json.dumps(self.__repr__()) 104 | 105 | 106 | class BaseCollection(Base): 107 | """Base class for collection types""" 108 | def __init__(self, obj, connection=None): 109 | super(BaseCollection, self).__init__(obj, connection=connection) 110 | self._parse_items(obj) 111 | self._attrs.append('items') 112 | 113 | def _parse_links(self, obj): 114 | """links are special on a chassis; dont parse them""" 115 | pass 116 | 117 | def _parse_items(self, obj): 118 | """Map linked items to getter methods 119 | 120 | The chassis resource returns a list of items and corresponding 121 | link data in a separate entity. 122 | """ 123 | def getter(connection, href): 124 | def _get(): 125 | return connection.rest_get(href, {}) 126 | return _get 127 | 128 | self.items = [] 129 | self._item_getters = [] 130 | 131 | if 'links' in obj and 'Member' in obj['links']: 132 | # NOTE: this assumes the lists are ordered the same 133 | counter = 0 134 | for item in obj['links']['Member']: 135 | self.items.append(obj['Items'][counter]) 136 | self._item_getters.append( 137 | getter(self._conn, item['href'])) 138 | counter+=1 139 | elif 'Items' in obj: 140 | # TODO: find an example of this format and make sure it works 141 | for item in obj['Items']: 142 | if 'links' in item and 'self' in item['links']: 143 | href = item['links']['self']['href'] 144 | self.items.append(item) 145 | 146 | # TODO: implement paging support 147 | # if 'links' in obj and 'NextPage' in obj['links']: 148 | # next_page = THIS_URI + '?page=' + str(obj['links']['NextPage']['page']) 149 | # do something with next_page URI 150 | 151 | def __iter__(self): 152 | for getter in self._item_getters: 153 | yield getter() 154 | 155 | 156 | class Root(Base): 157 | """Root '/' resource class""" 158 | def _parse_links(self, obj): 159 | """Map linked resources to getter functions 160 | 161 | The root resource returns a dict of links to top-level resources 162 | 163 | TODO: continue implementing customizations for top-level resources 164 | 165 | """ 166 | mapping = { 167 | 'Systems': Systems, 168 | 'Chassis': Chassis, 169 | 'Managers': Base, 170 | 'Schemas': Base, 171 | 'Registries': Base, 172 | 'Tasks': Base, 173 | 'AccountService': Base, 174 | 'Sessions': Base, 175 | 'EventService': Base, 176 | } 177 | 178 | def getter(connection, href, type): 179 | def _get(): 180 | return mapping[type](connection.rest_get(href, {}), self._conn) 181 | return _get 182 | 183 | for k in obj['links']: 184 | ref = "get_" + k.lower() 185 | self._links.append(ref) 186 | href = obj['links'][k]['href'] 187 | setattr(self, ref, getter(self._conn, href, k)) 188 | 189 | 190 | class Chassis(BaseCollection): 191 | """Chassis resource class""" 192 | def __len__(self): 193 | return len(self.items) 194 | 195 | 196 | class Systems(Base): 197 | pass 198 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # The order of packages is significant, because pip processes them in the order 2 | # of appearance. Changing the order has an impact on the overall integration 3 | # process, which may cause wedges in the gate later. 4 | 5 | pbr>=0.6,!=0.7,<1.0 6 | oslo.log>=1.0,<2.0 7 | Babel>=1.3 8 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = python-redfish 3 | summary = Reference implementation of Redfish standard client. 4 | description-file = 5 | README.rst 6 | author = OpenStack 7 | author-email = openstack-dev@lists.openstack.org 8 | home-page = http://www.openstack.org/ 9 | classifier = 10 | Environment :: OpenStack 11 | Intended Audience :: Information Technology 12 | Intended Audience :: System Administrators 13 | License :: OSI Approved :: Apache Software License 14 | Operating System :: POSIX :: Linux 15 | Programming Language :: Python 16 | Programming Language :: Python :: 2 17 | Programming Language :: Python :: 2.7 18 | Programming Language :: Python :: 2.6 19 | Programming Language :: Python :: 3 20 | Programming Language :: Python :: 3.3 21 | Programming Language :: Python :: 3.4 22 | 23 | [files] 24 | packages = 25 | redfish 26 | 27 | [build_sphinx] 28 | source-dir = doc/source 29 | build-dir = doc/build 30 | all_files = 1 31 | 32 | [upload_sphinx] 33 | upload-dir = doc/build/html 34 | 35 | [compile_catalog] 36 | directory = redfish/locale 37 | domain = python-redfish 38 | 39 | [update_catalog] 40 | domain = python-redfish 41 | output_dir = redfish/locale 42 | input_file = redfish/locale/python-redfish.pot 43 | 44 | [extract_messages] 45 | keywords = _ gettext ngettext l_ lazy_gettext 46 | mapping_file = babel.cfg 47 | output_file = redfish/locale/python-redfish.pot 48 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 13 | # implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT 18 | import setuptools 19 | 20 | # In python < 2.7.4, a lazy loading of package `pbr` will break 21 | # setuptools if some other modules registered functions in `atexit`. 22 | # solution from: http://bugs.python.org/issue15881#msg170215 23 | try: 24 | import multiprocessing # noqa 25 | except ImportError: 26 | pass 27 | 28 | setuptools.setup( 29 | setup_requires=['pbr'], 30 | pbr=True) 31 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | # The order of packages is significant, because pip processes them in the order 2 | # of appearance. Changing the order has an impact on the overall integration 3 | # process, which may cause wedges in the gate later. 4 | 5 | hacking<0.11,>=0.10.0 6 | 7 | coverage>=3.6 8 | discover 9 | python-subunit>=0.0.18 10 | sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3 11 | oslosphinx>=2.2.0 # Apache-2.0 12 | oslotest>=1.2.0 # Apache-2.0 13 | testrepository>=0.0.18 14 | testscenarios>=0.4 15 | testtools>=0.9.36,!=1.2.0 16 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 1.6 3 | envlist = py33,py34,py26,py27,pypy,pep8 4 | skipsdist = True 5 | 6 | [testenv] 7 | usedevelop = True 8 | install_command = pip install -U {opts} {packages} 9 | setenv = VIRTUAL_ENV={envdir} 10 | PYTHONDONTWRITEBYTECODE = 1 11 | LANGUAGE=en_US 12 | deps = -r{toxinidir}/requirements.txt 13 | -r{toxinidir}/test-requirements.txt 14 | commands = 15 | bash -c "TESTS_DIR=./redfish/tests python setup.py testr --slowest --testr-args='{posargs}'" 16 | 17 | [testenv:pep8] 18 | commands = flake8 19 | 20 | [testenv:venv] 21 | commands = {posargs} 22 | 23 | [testenv:cover] 24 | commands = python setup.py testr --coverage --testr-args='{posargs}' 25 | 26 | [testenv:docs] 27 | commands = python setup.py build_sphinx 28 | 29 | [testenv:debug] 30 | commands = oslo_debug_helper {posargs} 31 | 32 | [flake8] 33 | # E123, E125 skipped as they are invalid PEP-8. 34 | 35 | show-source = True 36 | ignore = E123,E125 37 | builtins = _ 38 | exclude = .venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build 39 | --------------------------------------------------------------------------------