diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..15772ba2 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,6 @@ +[run] +branch = True +source = sushy-tools + +[report] +ignore_errors = True diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..963e589a --- /dev/null +++ b/.gitignore @@ -0,0 +1,58 @@ +*.py[cod] + +# C extensions +*.so + +# Packages +*.egg* +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg +lib +lib64 + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +cover/ +.coverage* +!.coveragerc +.tox +nosetests.xml +.testrepository +.venv + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Complexity +output/*.html +output/*/index.html + +# Sphinx +doc/build + +# pbr generates these +AUTHORS +ChangeLog + +# Editors +*~ +.*.swp +.*sw? + +# Files created by releasenotes build +releasenotes/build \ No newline at end of file diff --git a/.mailmap b/.mailmap new file mode 100644 index 00000000..516ae6fe --- /dev/null +++ b/.mailmap @@ -0,0 +1,3 @@ +# Format is: +# +# diff --git a/.testr.conf b/.testr.conf new file mode 100644 index 00000000..6d83b3c4 --- /dev/null +++ b/.testr.conf @@ -0,0 +1,7 @@ +[DEFAULT] +test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ + OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ + OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ + ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION +test_id_option=--load-list $IDFILE +test_list_option=--list diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 00000000..3765c1b3 --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,17 @@ +If 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 + +Pull requests submitted through GitHub will be ignored. + +Bugs should be filed on Launchpad, not GitHub: + + https://bugs.launchpad.net/sushy diff --git a/HACKING.rst b/HACKING.rst new file mode 100644 index 00000000..c011cb20 --- /dev/null +++ b/HACKING.rst @@ -0,0 +1,4 @@ +Sushy Tools Style Commandments +============================== + +Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..68c771a0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,176 @@ + + 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. + diff --git a/README.rst b/README.rst new file mode 100644 index 00000000..f55af4fd --- /dev/null +++ b/README.rst @@ -0,0 +1,11 @@ +=========== +sushy-tools +=========== + +A set of tools to support the development and test of the Sushy library +(http://sushy.readthedocs.io) + +* Free software: Apache license +* Documentation: http://sushy.readthedocs.io +* Source: http://git.openstack.org/cgit/openstack/sushy-tools +* Bugs: http://bugs.launchpad.net/sushy diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100755 index 00000000..cb046c36 --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +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 = [ + 'sphinx.ext.autodoc', + #'sphinx.ext.intersphinx', + 'oslosphinx' +] + +# 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. +project = u'sushy-tools' +copyright = u'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 = 'sphinx' + +# -- Options for HTML output -------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +# html_theme_path = ["."] +# html_theme = '_theme' +# html_static_path = ['static'] + +# Output file base name for HTML help builder. +htmlhelp_basename = '%sdoc' % project + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass +# [howto/manual]). +latex_documents = [ + ('index', + '%s.tex' % project, + u'%s Documentation' % project, + u'OpenStack Foundation', 'manual'), +] + +# Example configuration for intersphinx: refer to the Python standard library. +#intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 00000000..34441731 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1 @@ +The sushy-tools uses the main Sushy documentation, you can access it at: http://sushy.readthedocs.io diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..57610e03 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. + +pbr>=2.0.0 # Apache-2.0 +Flask>=0.10,!=0.11,<1.0 # BSD +libvirt-python>=1.2.5 # LGPLv2+ diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..57cbf30a --- /dev/null +++ b/setup.cfg @@ -0,0 +1,54 @@ +[metadata] +name = sushy-tools +summary = A set of tools to support the development and test of the Sushy library (http://sushy.readthedocs.io/) +description-file = + README.rst +author = OpenStack +author-email = openstack-dev@lists.openstack.org +home-page = http://www.openstack.org/ +classifier = + Environment :: OpenStack + Intended Audience :: Information Technology + Intended Audience :: System Administrators + License :: OSI Approved :: Apache Software License + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.3 + Programming Language :: Python :: 3.4 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + +[files] +packages = + sushy_tools + +[entry_points] +console_scripts = + sushy-emulator = sushy_tools.emulator.main:main + sushy-static = sushy_tools.static.main:main + +[build_sphinx] +all-files = 1 +warning-is-error = 1 +source-dir = doc/source +build-dir = doc/build + +[upload_sphinx] +upload-dir = doc/build/html + +[compile_catalog] +directory = sushy_tools/locale +domain = sushy_tools + +[update_catalog] +domain = sushy_tools +output_dir = sushy_tools/locale +input_file = sushy_tools/locale/sushy_tools.pot + +[extract_messages] +keywords = _ gettext ngettext l_ lazy_gettext +mapping_file = babel.cfg +output_file = sushy_tools/locale/sushy_tools.pot diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..3887303f --- /dev/null +++ b/setup.py @@ -0,0 +1,29 @@ +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT +import setuptools + +# In python < 2.7.4, a lazy loading of package `pbr` will break +# setuptools if some other modules registered functions in `atexit`. +# solution from: http://bugs.python.org/issue15881#msg170215 +try: + import multiprocessing # noqa +except ImportError: + pass + +setuptools.setup( + setup_requires=['pbr>=2.0'], + pbr=True) diff --git a/sushy_tools/__init__.py b/sushy_tools/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sushy_tools/emulator/__init__.py b/sushy_tools/emulator/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sushy_tools/emulator/main.py b/sushy_tools/emulator/main.py new file mode 100755 index 00000000..d0006a81 --- /dev/null +++ b/sushy_tools/emulator/main.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python +# +# 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 argparse +import ssl +import xml.etree.ElementTree as ET + +import flask +import libvirt + +app = flask.Flask(__name__) +# Turn off strict_slashes on all routes +app.url_map.strict_slashes = False + +LIBVIRT_URI = None + +BOOT_DEVICE_MAP = { + 'Pxe': 'network', + 'Hdd': 'hd', + 'Cd': 'cdrom', +} + +BOOT_DEVICE_MAP_REV = {v: k for k, v in BOOT_DEVICE_MAP.items()} + + +class libvirt_open(object): + + def __init__(self, uri, readonly=False): + self.uri = uri + self.readonly = readonly + + def __enter__(self): + try: + self._conn = (libvirt.openReadOnly(self.uri) + if self.readonly else + libvirt.open(self.uri)) + return self._conn + except libvirt.libvirtError as e: + print('Error when connecting to the libvirt URI "%(uri)s": ' + '%(error)s' % {'uri': self.uri, 'error': e}) + flask.abort(500) + + def __exit__(self, type, value, traceback): + self._conn.close() + + +def get_libvirt_domain(connection, domain): + try: + return connection.lookupByName(domain) + except libvirt.libvirtError: + flask.abort(404) + + +@app.route('/redfish/v1/') +def root_resource(): + return flask.render_template('root.json') + + +@app.route('/redfish/v1/Systems') +def system_collection_resource(): + with libvirt_open(LIBVIRT_URI, readonly=True) as conn: + domains = conn.listDefinedDomains() + return flask.render_template( + 'system_collection.json', system_count=len(domains), + systems=domains) + + +def _get_total_cpus(domain, tree): + total_cpus = 0 + if domain.isActive(): + total_cpus = domain.maxVcpus() + else: + # If we can't get it from maxVcpus() try to find it by + # inspecting the domain XML + if total_cpus <= 0: + vcpu_element = tree.find('.//vcpu') + if vcpu_element is not None: + total_cpus = int(vcpu_element.text) + return total_cpus + + +def _get_boot_source_target(tree): + boot_source_target = None + boot_element = tree.find('.//boot') + if boot_element is not None: + boot_source_target = ( + BOOT_DEVICE_MAP_REV.get(boot_element.get('dev'))) + return boot_source_target + + +@app.route('/redfish/v1/Systems/', methods=['GET', 'PATCH']) +def system_resource(identity): + if flask.request.method == 'GET': + with libvirt_open(LIBVIRT_URI, readonly=True) as conn: + domain = get_libvirt_domain(conn, identity) + power_state = 'On' if domain.isActive() else 'Off' + total_memory_gb = int(domain.maxMemory() / 1024 / 1024) + + tree = ET.fromstring(domain.XMLDesc()) + total_cpus = _get_total_cpus(domain, tree) + boot_source_target = _get_boot_source_target(tree) + + return flask.render_template( + 'system.json', identity=identity, uuid=domain.UUIDString(), + power_state=power_state, total_memory_gb=total_memory_gb, + total_cpus=total_cpus, boot_source_target=boot_source_target) + + elif flask.request.method == 'PATCH': + boot = flask.request.json.get('Boot') + if not boot: + return 'PATCH only works for the Boot element', 400 + + target = BOOT_DEVICE_MAP.get(boot.get('BootSourceOverrideTarget')) + if not target: + return 'Missing the BootSourceOverrideTarget element', 400 + + # NOTE(lucasagomes): In libvirt we always set the boot + # device frequency to "continuous" so, we are ignoring the + # BootSourceOverrideEnabled element here + + # TODO(lucasagomes): We should allow changing the boot mode from + # BIOS to UEFI (and vice-versa) + + with libvirt_open(LIBVIRT_URI) as conn: + domain = get_libvirt_domain(conn, identity) + tree = ET.fromstring(domain.XMLDesc()) + for os_element in tree.findall('os'): + # Remove all "boot" elements + for boot_element in os_element.findall('boot'): + os_element.remove(boot_element) + + # Add a new boot element with the request boot device + boot_element = ET.SubElement(os_element, 'boot') + boot_element.set('dev', target) + + conn.defineXML(ET.tostring(tree).decode('utf-8')) + + return '', 204 + + +@app.route('/redfish/v1/Systems//Actions/ComputerSystem.Reset', + methods=['POST']) +def system_reset_action(identity): + reset_type = flask.request.json.get('ResetType') + with libvirt_open(LIBVIRT_URI) as conn: + domain = get_libvirt_domain(conn, identity) + try: + if reset_type in ('On', 'ForceOn'): + if not domain.isActive(): + domain.create() + elif reset_type == 'ForceOff': + if domain.isActive(): + domain.destroy() + elif reset_type == 'GracefulShutdown': + if domain.isActive(): + domain.shutdown() + elif reset_type == 'GracefulRestart': + if domain.isActive(): + domain.reboot() + elif reset_type == 'ForceRestart': + if domain.isActive(): + domain.reset() + elif reset_type == 'Nmi': + if domain.isActive(): + domain.injectNMI() + except libvirt.libvirtError: + flask.abort(500) + + return '', 204 + + +def parse_args(): + parser = argparse.ArgumentParser('sushy-emulator') + parser.add_argument('-p', '--port', + type=int, + default=8000, + help='The port to bind the server to') + parser.add_argument('-u', '--libvirt-uri', + type=str, + default='qemu:///system', + help='The libvirt URI') + parser.add_argument('-c', '--ssl-certificate', + type=str, + help='SSL certificate to use for HTTPS') + parser.add_argument('-k', '--ssl-key', + type=str, + help='SSL key to use for HTTPS') + return parser.parse_args() + + +def main(): + global LIBVIRT_URI + args = parse_args() + LIBVIRT_URI = args.libvirt_uri + + ssl_context = None + if args.ssl_certificate and args.ssl_key: + ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) + ssl_context.load_cert_chain(args.ssl_certificate, args.ssl_key) + + app.run(host='', port=args.port, ssl_context=ssl_context) + + +if __name__ == '__main__': + main() diff --git a/sushy_tools/emulator/templates/root.json b/sushy_tools/emulator/templates/root.json new file mode 100644 index 00000000..7ee304d6 --- /dev/null +++ b/sushy_tools/emulator/templates/root.json @@ -0,0 +1,36 @@ +{ + "@odata.type": "#ServiceRoot.v1_0_2.ServiceRoot", + "Id": "RedvirtService", + "Name": "Redvirt Service", + "RedfishVersion": "1.0.2", + "UUID": "85775665-c110-4b85-8989-e6162170b3ec", + "Systems": { + "@odata.id": "/redfish/v1/Systems" + }, + "Chassis": { + "@odata.id": "/redfish/v1/Chassis" + }, + "Managers": { + "@odata.id": "/redfish/v1/Managers" + }, + "Tasks": { + "@odata.id": "/redfish/v1/TaskService" + }, + "SessionService": { + "@odata.id": "/redfish/v1/SessionService" + }, + "AccountService": { + "@odata.id": "/redfish/v1/AccountService" + }, + "EventService": { + "@odata.id": "/redfish/v1/EventService" + }, + "Links": { + "Sessions": { + "@odata.id": "/redfish/v1/SessionService/Sessions" + } + }, + "@odata.context": "/redfish/v1/$metadata#ServiceRoot.ServiceRoot", + "@odata.id": "/redfish/v1/", + "@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." +} diff --git a/sushy_tools/emulator/templates/system.json b/sushy_tools/emulator/templates/system.json new file mode 100644 index 00000000..d567e7bd --- /dev/null +++ b/sushy_tools/emulator/templates/system.json @@ -0,0 +1,120 @@ +{ + "@odata.type": "#ComputerSystem.v1_1_0.ComputerSystem", + "Id": "{{ identity }}", + "Name": "WebFrontEnd483", + "SystemType": "Physical", + "AssetTag": "Chicago-45Z-2381", + "Manufacturer": "Contoso", + "Model": "3500RX", + "SKU": "8675309", + "SerialNumber": "437XR1138R2", + "PartNumber": "224071-J23", + "Description": "Web Front End node", + "UUID": "{{ uuid }}", + "HostName": "web483", + "Status": { + "State": "Enabled", + "Health": "OK", + "HealthRollUp": "OK" + }, + "IndicatorLED": "Off", + "PowerState": "{{ power_state }}", + "Boot": { + "BootSourceOverrideEnabled": "Continuous", + "BootSourceOverrideTarget": "{{ boot_source_target }}", + "BootSourceOverrideTarget@Redfish.AllowableValues": [ + "Pxe", + "Cd", + "Hdd" + ], + "BootSourceOverrideMode": "UEFI", + "UefiTargetBootSourceOverride": "/0x31/0x33/0x01/0x01" + }, + "TrustedModules": [ + { + "FirmwareVersion": "1.13b", + "InterfaceType": "TPM1_2", + "Status": { + "State": "Enabled", + "Health": "OK" + } + } + ], + "Oem": { + "Contoso": { + "@odata.type": "http://Contoso.com/Schema#Contoso.ComputerSystem", + "ProductionLocation": { + "FacilityName": "PacWest Production Facility", + "Country": "USA" + } + }, + "Chipwise": { + "@odata.type": "http://Chipwise.com/Schema#Chipwise.ComputerSystem", + "Style": "Executive" + } + }, + "BiosVersion": "P79 v1.33 (02/28/2015)", + "ProcessorSummary": { + "Count": {{ total_cpus }}, + "ProcessorFamily": "Multi-Core Intel(R) Xeon(R) processor 7xxx Series", + "Status": { + "State": "Enabled", + "Health": "OK", + "HealthRollUp": "OK" + } + }, + "MemorySummary": { + "TotalSystemMemoryGiB": {{ total_memory_gb }}, + "Status": { + "State": "Enabled", + "Health": "OK", + "HealthRollUp": "OK" + } + }, + "Bios": { + "@odata.id": "/redfish/v1/Systems/{{ identity }}/BIOS" + }, + "Processors": { + "@odata.id": "/redfish/v1/Systems/{{ identity }}/Processors" + }, + "Memory": { + "@odata.id": "/redfish/v1/Systems/{{ identity }}/Memory" + }, + "EthernetInterfaces": { + "@odata.id": "/redfish/v1/Systems/{{ identity }}/EthernetInterfaces" + }, + "SimpleStorage": { + "@odata.id": "/redfish/v1/Systems/{{ identity }}/SimpleStorage" + }, + "LogServices": { + "@odata.id": "/redfish/v1/Systems/{{ identity }}/LogServices" + }, + "Links": { + "Chassis": [ + ], + "ManagedBy": [ + ] + }, + "Actions": { + "#ComputerSystem.Reset": { + "target": "/redfish/v1/Systems/{{ identity }}/Actions/ComputerSystem.Reset", + "ResetType@Redfish.AllowableValues": [ + "On", + "ForceOff", + "GracefulShutdown", + "GracefulRestart", + "ForceRestart", + "Nmi", + "ForceOn" + ] + }, + "Oem": { + "#Contoso.Reset": { + "target": "/redfish/v1/Systems/{{ identity }}/Oem/Contoso/Actions/Contoso.Reset" + } + } + }, + "@odata.context": "/redfish/v1/$metadata#ComputerSystem.ComputerSystem", + "@odata.id": "/redfish/v1/Systems/{{ identity }}", + "@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." +} diff --git a/sushy_tools/emulator/templates/system_collection.json b/sushy_tools/emulator/templates/system_collection.json new file mode 100644 index 00000000..bcfb1cb1 --- /dev/null +++ b/sushy_tools/emulator/templates/system_collection.json @@ -0,0 +1,15 @@ +{ + "@odata.type": "#ComputerSystemCollection.ComputerSystemCollection", + "Name": "Computer System Collection", + "Members@odata.count": {{ system_count }}, + "Members": [ + {% for system in systems %} + { + "@odata.id": "/redfish/v1/Systems/{{ system }}" + }{% if not loop.last %},{% endif %} + {% endfor %} + ], + "@odata.context": "/redfish/v1/$metadata#ComputerSystemCollection.ComputerSystemCollection", + "@odata.id": "/redfish/v1/Systems", + "@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." +} diff --git a/sushy_tools/static/__init__.py b/sushy_tools/static/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sushy_tools/static/main.py b/sushy_tools/static/main.py new file mode 100755 index 00000000..a4c50604 --- /dev/null +++ b/sushy_tools/static/main.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +# +# 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 argparse +import os +import ssl +import sys + +try: + from http import server as http_server +except ImportError: + import BaseHTTPServer as http_server # Py2 + +REDFISH_MOCKUP_FILES = None + + +class RequestHandler(http_server.BaseHTTPRequestHandler): + + REDFISH_SUBURI = '/redfish/v1' + + def _log_request(self, method): + print(self.headers) + content_length = int(self.headers.get('content-length', 0)) + if content_length > 0: + print('Data: %s\n' % self.rfile.read(content_length)) + + def do_GET(self): + self._log_request('GET') + path = self.path.rstrip('/') + if not path.startswith(self.REDFISH_SUBURI): + self.send_error(404) + return + + resource_path = path.replace(self.REDFISH_SUBURI, '').lstrip('/') + fpath = os.path.join(REDFISH_MOCKUP_FILES, resource_path, 'index.json') + if not os.path.exists(fpath): + self.send_error(404, 'Sub-URI %s not found' % resource_path) + return + + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.end_headers() + + with open(fpath, 'r') as f: + self.wfile.write(f.read().encode('utf-8')) + + def do_POST(self): + self._log_request('POST') + self.send_response(204) + self.end_headers() + + def do_PATCH(self): + self._log_request('PATCH') + self.send_response(204) + self.end_headers() + + +def parse_args(): + parser = argparse.ArgumentParser('sushy-static') + parser.add_argument('-p', '--port', + type=int, + default=8000, + help='The port to bind the server to') + parser.add_argument('-m', '--mockup-files', + type=str, + required=True, + help=('The path to the Redfish Mockup files in ' + 'the filesystem')) + parser.add_argument('-c', '--ssl-certificate', + type=str, + help='SSL certificate to use for HTTPS') + parser.add_argument('-k', '--ssl-key', + type=str, + help='SSL key to use for HTTPS') + return parser.parse_args() + + +def main(): + global REDFISH_MOCKUP_FILES + args = parse_args() + if not os.path.exists(args.mockup_files): + print('Mockup files %s not found' % args.mockup_files) + sys.exit(1) + + REDFISH_MOCKUP_FILES = os.path.realpath(args.mockup_files) + httpd = http_server.HTTPServer(('', args.port), RequestHandler) + + if args.ssl_certificate and args.ssl_key: + httpd.socket = ssl.wrap_socket( + httpd.socket, keyfile=args.ssl_key, + certfile=args.ssl_certificate, server_side=True) + + httpd.serve_forever() + + +if __name__ == '__main__': + main() diff --git a/sushy_tools/tests/__init__.py b/sushy_tools/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sushy_tools/tests/unit/__init__.py b/sushy_tools/tests/unit/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sushy_tools/tests/unit/base.py b/sushy_tools/tests/unit/base.py new file mode 100644 index 00000000..97423efd --- /dev/null +++ b/sushy_tools/tests/unit/base.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- + +# 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""" diff --git a/sushy_tools/tests/unit/test.py b/sushy_tools/tests/unit/test.py new file mode 100644 index 00000000..f38d0296 --- /dev/null +++ b/sushy_tools/tests/unit/test.py @@ -0,0 +1,22 @@ +# 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 sushy_tools.tests.unit import base + + +class NoopTestCase(base.TestCase): + + def test_noop(self): + pass diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 00000000..96b2425b --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,14 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. + +hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 + +coverage>=4.0 # Apache-2.0 +python-subunit>=0.0.18 # Apache-2.0/BSD +sphinx>=1.5.1 # BSD +oslosphinx>=4.7.0 # Apache-2.0 +oslotest>=1.10.0 # Apache-2.0 +testrepository>=0.0.18 # Apache-2.0/BSD +testscenarios>=0.4 # Apache-2.0/BSD +testtools>=1.4.0 # MIT diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..338770fb --- /dev/null +++ b/tox.ini @@ -0,0 +1,36 @@ +[tox] +minversion = 2.0 +envlist = py34,py27,pypy,pep8 +skipsdist = True + +[testenv] +usedevelop = True +install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} +setenv = + VIRTUAL_ENV={envdir} + PYTHONWARNINGS=default::DeprecationWarning +deps = -r{toxinidir}/test-requirements.txt +commands = python setup.py test --slowest --testr-args='{posargs}' + +[testenv:pep8] +commands = flake8 {posargs} + +[testenv:venv] +commands = {posargs} + +[testenv:cover] +commands = python setup.py test --coverage --testr-args='{posargs}' + +[testenv:docs] +commands = python setup.py build_sphinx + +[testenv:debug] +commands = oslo_debug_helper {posargs} + +[flake8] +# E123, E125 skipped as they are invalid PEP-8. + +show-source = True +ignore = E123,E125 +builtins = _ +exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build