Remove the tools/ directory from the sushy tree

All the tools have been moved to the "sushy-tools" project and can now
be removed from the tree. The documentation was also updated to use the
new project instead.

Depends-On: I13df916ec8ddb3347c21105b32d354ade1bfd191
Change-Id: Icc7ab83b0091a4adebe81f379c20877bfa76b0c2
This commit is contained in:
Lucas Alvares Gomes 2017-04-10 12:05:40 +01:00
parent 392a4a6272
commit e24a9a9f82
10 changed files with 96 additions and 576 deletions

View File

@ -1,4 +1,97 @@
.. _contributing:
============
Contributing
============
.. include:: ../../CONTRIBUTING.rst
Running a Redfish emulator
==========================
Testing and/or developing Sushy without owning a real baremetal machine
that supports the Redfish protocol is possible by running an emulator,
the `sushy-tools`_ project ships with two emulators that can be used
for this purpose. To install it run::
sudo pip install --user sushy-tools
.. note::
Installing the dependencies requires libvirt development files.
For example, run the following command to install them on Fedora::
sudo dnf install -y libvirt-devel
Static emulator
~~~~~~~~~~~~~~~
After installing `sushy-tools`_ you will have a new CLI tool named
``sushy-static``. This tool creates a HTTP server to serve any of the
`Redfish mockups <https://www.dmtf.org/standards/redfish>`_. The files
are static so operations like changing the boot device or the power state
**will not** have any effect. But that should be enough for enabling
people to test parts of the library.
To use ``sushy-static`` we need the Redfish mockup files that can be
downloaded from https://www.dmtf.org/standards/redfish, for example::
wget https://www.dmtf.org/sites/default/files/standards/documents/DSP2043_1.0.0.zip
After the download, extract the files somewhere in the file-system::
unzip DSP2043_1.0.0.zip -d <output-path>
Now run ``sushy-static`` pointing to those files. For example to serve
the ``DSP2043-server`` mockup files, run::
sushy-static --mockup-files <output-path>/DSP2043-server
Libvirt emulator
~~~~~~~~~~~~~~~~
The second emulator shipped by `sushy-tools`_ is the CLI tool named
``sushy-emulator``. This tool starts a ReST API that users can use to
interact with virtual machines using the Redfish protocol. So operations
such as changing the boot device or the power state will actually affect
the virtual machines. This allows users to test the library in a more
dynamic way. To run it do
.. code-block:: sh
sushy-emulator
# Or, running with custom parameters
sushy-emulator --port 8000 --libvirt-uri "qemu:///system"
That's it, now you can test Sushy against the ``http://locahost:8000``
endpoint.
Enabling SSL
~~~~~~~~~~~~
Both mockup servers supports `SSL`_ if you want Sushy with it. To set it
up, first you need to generate key and certificate files with OpenSSL
use following command::
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365
Start the mockup server passing the ``--ssl-certificate`` and
``--ssl-key`` parameters to it to it, for example::
sushy-emulator --ssl-key key.pem --ssl-certificate cert.pem
Now to connect with `SSL`_ to the server use the ``verify`` parameter
pointing to the certificate file when instantiating Sushy, for example:
.. code-block:: python
import sushy
# Note the HTTP"S"
s = sushy.Sushy('https://localhost:8000', verify='cert.pem', username='foo', password='bar')
.. _SSL: https://en.wikipedia.org/wiki/Secure_Sockets_Layer
.. _sushy-tools: https://git.openstack.org/cgit/openstack/sushy-tools

View File

@ -73,82 +73,6 @@ To use sushy in a project:
print(sys_inst.get_allowed_system_boot_source_values())
Running a mockup server
-----------------------
Static mockup
~~~~~~~~~~~~~
Sushy ships a small script at ``tools/mockup_server.py``
that creates a HTTP server to serve any of the `Redfish mockups
<https://www.dmtf.org/standards/redfish>`_. The files are static so
operations like changing the boot device or the power state **will not**
have any effect. But that should be enough for enabling people to test
parts of the library. To setup it do:
#. Download the .zip containing the Redfish mockups files from
https://www.dmtf.org/standards/redfish, for example::
wget https://www.dmtf.org/sites/default/files/standards/documents/DSP2043_1.0.0.zip
#. Extract it somewhere in the file-system::
unzip DSP2043_1.0.0.zip -d <output-path>
#. Now run the ``mockup_server.py`` script::
python sushy/tools/mockup_server.py --mockup-files <output-path>/DSP2043-server --port 8000
Libvirt mockup
~~~~~~~~~~~~~~
Sushy also ships a small application at ``tools/mockup_server_libvirt``
that starts a ReST API that users can use to interact with virtual
machines using the Redfish protocol. So operations such as changing
the boot device or the power state will actually affect the virtual
machines. This allows users to test the library in a more dynamic way. To
setup it do:
.. code-block:: sh
tox -elibvirt-simulator
# Or, running with custom parameters
tox -elibvirt-simulator -- --port 8000 --libvirt-uri "qemu:///system"
.. note::
Installing the dependencies requires libvirt development files.
For example, run the following command to install them on Fedora::
sudo dnf install -y libvirt-devel
That's it, now you can test Sushy against the ``http://locahost:8000``
endpoint.
Enabling SSL
~~~~~~~~~~~~
Both mockup servers supports `SSL`_ if you want Sushy with it. To set it
up, first you need to generate key and certificate files with OpenSSL
use following command::
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365
Start the mockup server passing the ``--ssl-certificate`` and
``--ssl-key`` parameters to it to it, for example::
python sushy/tools/mockup_server.py --ssl-key key.pem --ssl-certificate cert.pem --mockup-files <output-path>/DSP2043-server --port 8000
Now to connect with `SSL`_ to the server use the ``verify`` parameter
pointing to the certificate file when instantiating Sushy, for example:
.. code-block:: python
import sushy
# Note the HTTP"S"
s = sushy.Sushy('https://localhost:8000', verify='cert.pem', username='foo', password='bar')
.. _SSL: https://en.wikipedia.org/wiki/Secure_Sockets_Layer
If you do not have any real baremetal machine that supports the Redfish
protocol you can look at the :ref:`contributing` page to learn how to
run a Redfish emulator.

View File

@ -1,106 +0,0 @@
#!/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('MockupServer')
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()
if __name__ == '__main__':
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()

View File

@ -1,214 +0,0 @@
#!/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/<identity>', 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/<identity>/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('MockupServerLibvirt')
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()
if __name__ == '__main__':
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)

View File

@ -1,2 +0,0 @@
flask
libvirt-python

View File

@ -1,36 +0,0 @@
{
"@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."
}

View File

@ -1,120 +0,0 @@
{
"@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."
}

View File

@ -1,15 +0,0 @@
{
"@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."
}

View File

@ -37,10 +37,6 @@ commands =
[testenv:debug]
commands = oslo_debug_helper {posargs}
[testenv:libvirt-simulator]
commands = python tools/mockup_server_libvirt/mockup_server_libvirt.py {posargs}
deps = -r{toxinidir}/tools/mockup_server_libvirt/requirements.txt
[flake8]
# E123, E125 skipped as they are invalid PEP-8.