186 lines
6.0 KiB
Python
Executable File
186 lines
6.0 KiB
Python
Executable File
#!/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_CONN = None
|
|
|
|
BOOT_DEVICE_MAP = {
|
|
'Pxe': 'network',
|
|
'Hdd': 'hd',
|
|
'Cd': 'cdrom',
|
|
}
|
|
|
|
BOOT_DEVICE_MAP_REV = {v: k for k, v in BOOT_DEVICE_MAP.items()}
|
|
|
|
|
|
def _get_libvirt_domain(domain):
|
|
try:
|
|
return LIBVIRT_CONN.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():
|
|
domains = LIBVIRT_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):
|
|
domain = _get_libvirt_domain(identity)
|
|
if flask.request.method == 'GET':
|
|
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)
|
|
|
|
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)
|
|
LIBVIRT_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):
|
|
domain = _get_libvirt_domain(identity)
|
|
reset_type = flask.request.json.get('ResetType')
|
|
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_CONN = libvirt.open(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)
|