Files
sushy-tools/sushy_tools/emulator/main.py
Steve Baker fafb4b0b86 Add virtual-media-boot to openstack driver
The implementation does the following.

On insert:
- Upload the image directly to glance from the URL (long running)
- Create and attach a new volume the same size as the root disk
- Rebuild the server with the image, replacing the contents of the root
  disk
- Delete the image

On eject:
- Assume the attached volume has been rewritten with a new image (an ISO
  installer or IPA)
- Detach the volume
- Create an image from the volume (long running)
- Rebuild the server with the new image
- Delete the volume
- Delete the image

The long running operations are performed in a background thread task.
Only one long running operation (insert or eject) can be performed
concurrently for each server. If a long running operation fails, the
only way to feed that back to the user is by re-raising the error during
the next insert/eject request for that server.

The documentation is updated to describe OpenStack driver specifics.
Also the Redfish spec has deprecated accessing VirtualMedia via Managers
so the documentation is updated to refer via Systems.

Change-Id: I24ea943325a23a06887a185801211b4a9570e284
2024-03-13 09:41:54 +13:00

1031 lines
34 KiB
Python
Executable File

# 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
from datetime import datetime
import json
import os
import ssl
import sys
import flask
from ironic_lib import auth_basic
from werkzeug import exceptions as wz_exc
from sushy_tools.emulator import api_utils
from sushy_tools.emulator.controllers import certificate_service as certctl
from sushy_tools.emulator.controllers import virtual_media as vmctl
from sushy_tools.emulator import memoize
from sushy_tools.emulator.resources import chassis as chsdriver
from sushy_tools.emulator.resources import drives as drvdriver
from sushy_tools.emulator.resources import indicators as inddriver
from sushy_tools.emulator.resources import managers as mgrdriver
from sushy_tools.emulator.resources import storage as stgdriver
from sushy_tools.emulator.resources.systems import fakedriver
from sushy_tools.emulator.resources.systems import ironicdriver
from sushy_tools.emulator.resources.systems import libvirtdriver
from sushy_tools.emulator.resources.systems import novadriver
from sushy_tools.emulator.resources import vmedia as vmddriver
from sushy_tools.emulator.resources import volumes as voldriver
from sushy_tools import error
def _render_error(message):
return {
"error": {
"code": "Base.1.0.GeneralError",
"message": message,
"@Message.ExtendedInfo": [
{
"@odata.type": ("/redfish/v1/$metadata"
"#Message.1.0.0.Message"),
"MessageId": "Base.1.0.GeneralError"
}
]
}
}
class RedfishAuthMiddleware(auth_basic.BasicAuthMiddleware):
_EXCLUDE_PATHS = frozenset(['', 'redfish', 'redfish/v1'])
def __call__(self, env, start_response):
path = env.get('PATH_INFO', '')
if path.strip('/') in self._EXCLUDE_PATHS:
return self.app(env, start_response)
else:
return super().__call__(env, start_response)
def format_exception(self, e):
response = super().format_exception(e)
response.json_body = _render_error(str(e))
return response
class Application(flask.Flask):
def __init__(self):
super().__init__(__name__)
# Turn off strict_slashes on all routes
self.url_map.strict_slashes = False
# This is needed for WSGI since it cannot process argv
self.configure(config_file=os.environ.get('SUSHY_EMULATOR_CONFIG'))
@self.before_request
def reset_cache():
self._cache = {}
def configure(self, config_file=None, extra_config=None):
if config_file:
self.config.from_pyfile(config_file)
if extra_config:
self.config.update(extra_config)
auth_file = self.config.get("SUSHY_EMULATOR_AUTH_FILE")
if auth_file and not isinstance(self.wsgi_app, RedfishAuthMiddleware):
self.wsgi_app = RedfishAuthMiddleware(self.wsgi_app, auth_file)
feature_set = self.config.get('SUSHY_EMULATOR_FEATURE_SET', 'full')
if feature_set not in ('full', 'vmedia', 'minimum'):
raise RuntimeError(f"Invalid feature set {self.feature_set}")
@property
def feature_set(self):
return self.config.get('SUSHY_EMULATOR_FEATURE_SET', 'full')
def render_template(self, template_name, /, **params):
params.setdefault('feature_set', self.feature_set)
return flask.render_template(template_name, **params)
@property
@memoize.memoize()
def systems(self):
fake = self.config.get('SUSHY_EMULATOR_FAKE_DRIVER')
os_cloud = self.config.get('SUSHY_EMULATOR_OS_CLOUD')
ironic_cloud = self.config.get('SUSHY_EMULATOR_IRONIC_CLOUD')
if fake:
result = fakedriver.FakeDriver.initialize(
self.config, self.logger)()
elif os_cloud:
if not novadriver.is_loaded:
self.logger.error('Nova driver not loaded')
sys.exit(1)
result = novadriver.OpenStackDriver.initialize(
self.config, self.logger, os_cloud)()
elif ironic_cloud:
if not ironicdriver.is_loaded:
self.logger.error('Ironic driver not loaded')
sys.exit(1)
result = ironicdriver.IronicDriver.initialize(
self.config, self.logger, ironic_cloud)()
else:
if not libvirtdriver.is_loaded:
self.logger.error('libvirt driver not loaded')
sys.exit(1)
libvirt_uri = self.config.get('SUSHY_EMULATOR_LIBVIRT_URI', '')
result = libvirtdriver.LibvirtDriver.initialize(
self.config, self.logger, libvirt_uri)()
self.logger.debug('Initialized system resource backed by %s driver',
result)
return result
@property
@memoize.memoize()
def managers(self):
return mgrdriver.FakeDriver(self.config, self.logger,
self.systems, self.chassis)
@property
@memoize.memoize()
def chassis(self):
return chsdriver.StaticDriver(self.config, self.logger)
@property
@memoize.memoize()
def indicators(self):
return inddriver.StaticDriver(self.config, self.logger)
@property
@memoize.memoize()
def vmedia(self):
os_cloud = self.config.get('SUSHY_EMULATOR_OS_CLOUD')
if os_cloud:
return vmddriver.OpenstackDriver(self.config, self.logger,
self.systems)
return vmddriver.StaticDriver(self.config, self.logger)
@property
@memoize.memoize()
def storage(self):
return stgdriver.StaticDriver(self.config, self.logger)
@property
@memoize.memoize()
def drives(self):
return drvdriver.StaticDriver(self.config, self.logger)
@property
@memoize.memoize()
def volumes(self):
return voldriver.StaticDriver(self.config, self.logger)
app = Application()
app.register_blueprint(certctl.certificate_service)
app.register_blueprint(vmctl.virtual_media)
@app.errorhandler(Exception)
@api_utils.returns_json
def all_exception_handler(message):
if isinstance(message, error.AliasAccessError):
url = flask.url_for(flask.request.endpoint, identity=message.args[0])
return flask.redirect(url, code=307, Response=flask.Response)
code = getattr(message, 'code', 500)
if (isinstance(message, error.FishyError)
or isinstance(message, wz_exc.HTTPException)):
app.logger.debug(
'Request failed with %s: %s', message.__class__.__name__, message)
else:
app.logger.exception(
'Unexpected %s: %s', message.__class__.__name__, message)
return flask.render_template('error.json', message=message), code
@app.route('/redfish/v1/')
@api_utils.returns_json
def root_resource():
return app.render_template('root.json')
@app.route('/redfish/v1/Chassis')
@api_utils.returns_json
def chassis_collection_resource():
if app.feature_set != "full":
raise error.FeatureNotAvailable("Chassis")
app.logger.debug('Serving chassis list')
return app.render_template(
'chassis_collection.json',
manager_count=len(app.chassis.chassis),
chassis=app.chassis.chassis)
@app.route('/redfish/v1/Chassis/<identity>', methods=['GET', 'PATCH'])
@api_utils.returns_json
def chassis_resource(identity):
if app.feature_set != "full":
raise error.FeatureNotAvailable("Chassis")
chassis = app.chassis
uuid = chassis.uuid(identity)
if flask.request.method == 'GET':
app.logger.debug('Serving resources for chassis "%s"', identity)
# the first chassis gets all resources
if uuid == chassis.chassis[0]:
systems = app.systems.systems
managers = app.managers.managers
storage = app.storage.get_all_storage()
drives = app.drives.get_all_drives()
else:
systems = []
managers = []
storage = []
drives = []
return app.render_template(
'chassis.json',
identity=identity,
name=chassis.name(identity),
uuid=uuid,
contained_by=None,
contained_systems=systems,
contained_managers=managers,
contained_chassis=[],
managers=managers[:1],
indicator_led=app.indicators.get_indicator_state(uuid),
storage=storage,
drives=drives
)
elif flask.request.method == 'PATCH':
indicator_led_state = flask.request.json.get('IndicatorLED')
if not indicator_led_state:
return 'PATCH only works for IndicatorLED element', 400
app.indicators.set_indicator_state(uuid, indicator_led_state)
app.logger.info('Set indicator LED to "%s" for chassis "%s"',
indicator_led_state, identity)
return '', 204
@app.route('/redfish/v1/Chassis/<identity>/Thermal', methods=['GET'])
@api_utils.returns_json
def thermal_resource(identity):
if app.feature_set != "full":
raise error.FeatureNotAvailable("Chassis")
chassis = app.chassis
uuid = chassis.uuid(identity)
app.logger.debug(
'Serving thermal resources for chassis "%s"', identity)
# the first chassis gets all resources
if uuid == chassis.chassis[0]:
systems = app.systems.systems
else:
systems = []
return app.render_template(
'thermal.json',
chassis=identity,
systems=systems
)
@app.route('/redfish/v1/Managers')
@api_utils.returns_json
def manager_collection_resource():
if app.feature_set == "minimum":
raise error.FeatureNotAvailable("Managers")
app.logger.debug('Serving managers list')
return app.render_template(
'manager_collection.json',
manager_count=len(app.managers.managers),
managers=app.managers.managers)
def jsonify(obj_type, obj_version, obj):
obj.update({
"@odata.type": "#{0}.{1}.{0}".format(obj_type, obj_version),
"@odata.context": "/redfish/v1/$metadata#{0}.{0}".format(obj_type),
"@Redfish.Copyright": ("Copyright 2014-2017 Distributed Management "
"Task Force, Inc. (DMTF). For the full DMTF "
"copyright policy, see http://www.dmtf.org/"
"about/policies/copyright.")
})
return flask.jsonify(obj)
@app.route('/redfish/v1/Managers/<identity>', methods=['GET'])
@api_utils.returns_json
def manager_resource(identity):
if app.feature_set == "minimum":
raise error.FeatureNotAvailable("Managers")
app.logger.debug('Serving resources for manager "%s"', identity)
manager = app.managers.get_manager(identity)
systems = app.managers.get_managed_systems(manager)
chassis = app.managers.get_managed_chassis(manager)
uuid = manager['UUID']
result = {
"Id": manager['Id'],
"Name": manager.get('Name'),
"UUID": uuid,
"ManagerType": "BMC",
"VirtualMedia": {
"@odata.id": "/redfish/v1/Systems/%s/VirtualMedia" % systems[0],
},
"Links": {
"ManagerForServers": [
{
"@odata.id": "/redfish/v1/Systems/%s" % system
}
for system in systems
],
"ManagerForChassis": [
{
"@odata.id": "/redfish/v1/Chassis/%s" % ch
}
for ch in chassis if app.feature_set == "full"
]
},
"@odata.id": "/redfish/v1/Managers/%s" % uuid,
}
if app.feature_set == "full":
result.update({
"ServiceEntryPointUUID": manager.get('ServiceEntryPointUUID'),
"Description": "Contoso BMC",
"Model": "Joo Janta 200",
"DateTime": datetime.now().strftime('%Y-%M-%dT%H:%M:%S+00:00'),
"DateTimeLocalOffset": "+00:00",
"Status": {
"State": "Enabled",
"Health": "OK"
},
"PowerState": "On",
"FirmwareVersion": "1.00",
})
return jsonify('Manager', 'v1_3_1', result)
@app.route('/redfish/v1/Systems')
@api_utils.returns_json
def system_collection_resource():
systems = [system for system in app.systems.systems
if not api_utils.instance_denied(identity=system)]
app.logger.debug('Serving systems list')
return app.render_template(
'system_collection.json', system_count=len(systems), systems=systems)
@app.route('/redfish/v1/Systems/<identity>', methods=['GET', 'PATCH'])
@api_utils.ensure_instance_access
@api_utils.returns_json
def system_resource(identity):
uuid = app.systems.uuid(identity)
if flask.request.method == 'GET':
app.logger.debug('Serving resources for system "%s"', identity)
def try_get(call):
try:
return call(identity)
except error.NotSupportedError:
return None
return app.render_template(
'system.json',
identity=identity,
name=app.systems.name(identity),
uuid=app.systems.uuid(identity),
power_state=app.systems.get_power_state(identity),
total_memory_gb=try_get(app.systems.get_total_memory),
total_cpus=try_get(app.systems.get_total_cpus),
boot_source_target=app.systems.get_boot_device(identity),
boot_source_mode=try_get(app.systems.get_boot_mode),
uefi_mode=(try_get(app.systems.get_boot_mode) == 'UEFI'),
managers=app.managers.get_managers_for_system(identity),
chassis=app.chassis.chassis[:1],
indicator_led=app.indicators.get_indicator_state(
app.systems.uuid(identity)),
http_boot_uri=try_get(app.systems.get_http_boot_uri)
)
elif flask.request.method == 'PATCH':
boot = flask.request.json.get('Boot')
indicator_led_state = flask.request.json.get('IndicatorLED')
if not boot and not indicator_led_state:
return ('PATCH only works for Boot and '
'IndicatorLED elements'), 400
if indicator_led_state and app.feature_set != "full":
raise error.FeatureNotAvailable("IndicatorLED", code=400)
if boot:
target = boot.get('BootSourceOverrideTarget')
mode = boot.get('BootSourceOverrideMode')
http_uri = boot.get('HttpBootUri')
if http_uri and target == 'UefiHttp':
try:
# Download the image
image_path = app.vmedia.insert_image(
identity, 'Cd', http_uri)
except Exception as e:
app.logger.error('Unable to insert image for HttpBootUri '
'request processing. Error: %s', e)
return 'Failed to download and attach HttpBootUri.', 400
try:
# Mount it as an ISO
app.systems.set_boot_image(
uuid,
'Cd', boot_image=image_path,
write_protected=True)
# Set it for our emulator's API surface to return it
# if queried.
except Exception as e:
app.logger.error('Unable to attach HttpBootUri for boot '
'operation. Error: %s', e)
return (('Failed to set the supplied media as the next '
'bootdevice.'), 400)
try:
app.systems.set_http_boot_uri(http_uri)
except Exception as e:
app.logger.error('Unable to record HttpBootUri for boot '
'operation. Error: %s', e)
return 'Failed to save HttpBootUri field value.', 400
if target == 'UefiHttp':
# Reset to Cd, in our case, since we can't force override
# the network boot to a specific URL. This is sort of a hack
# but testing functionality overall is a bit more important.
target = 'Cd'
if target:
# NOTE(lucasagomes): In libvirt we always set the boot
# device frequency to "continuous" so, we are ignoring the
# BootSourceOverrideEnabled element here
app.systems.set_boot_device(identity, target)
app.logger.info('Set boot device to "%s" for system "%s"',
target, identity)
if mode:
app.systems.set_boot_mode(identity, mode)
app.logger.info('Set boot mode to "%s" for system "%s"',
mode, identity)
if not target and not mode and not http_uri:
return ('Missing the BootSourceOverrideTarget and/or '
'BootSourceOverrideMode and/or HttpBootUri '
'element', 400)
if indicator_led_state:
app.indicators.set_indicator_state(
uuid, indicator_led_state)
app.logger.info('Set indicator LED to "%s" for system "%s"',
indicator_led_state, identity)
return '', 204
@app.route('/redfish/v1/Systems/<identity>/EthernetInterfaces',
methods=['GET'])
@api_utils.ensure_instance_access
@api_utils.returns_json
def ethernet_interfaces_collection(identity):
if app.feature_set == "minimum":
raise error.FeatureNotAvailable("EthernetInterfaces")
nics = app.systems.get_nics(identity)
return app.render_template(
'ethernet_interfaces_collection.json', identity=identity,
nics=nics)
@app.route('/redfish/v1/Systems/<identity>/EthernetInterfaces/<nic_id>',
methods=['GET'])
@api_utils.ensure_instance_access
@api_utils.returns_json
def ethernet_interface(identity, nic_id):
if app.feature_set == "minimum":
raise error.FeatureNotAvailable("EthernetInterfaces")
nics = app.systems.get_nics(identity)
for nic in nics:
if nic['id'] == nic_id:
return app.render_template(
'ethernet_interface.json', identity=identity, nic=nic)
raise error.NotFound()
@app.route('/redfish/v1/Systems/<identity>/Processors',
methods=['GET'])
@api_utils.ensure_instance_access
@api_utils.returns_json
def processors_collection(identity):
if app.feature_set != "full":
raise error.FeatureNotAvailable("Processors")
processors = app.systems.get_processors(identity)
return app.render_template(
'processors_collection.json', identity=identity,
processors=processors)
@app.route('/redfish/v1/Systems/<identity>/Processors/<processor_id>',
methods=['GET'])
@api_utils.ensure_instance_access
@api_utils.returns_json
def processor(identity, processor_id):
if app.feature_set != "full":
raise error.FeatureNotAvailable("Processors")
processors = app.systems.get_processors(identity)
for proc in processors:
if proc['id'] == processor_id:
return app.render_template(
'processor.json', identity=identity, processor=proc)
raise error.NotFound()
@app.route('/redfish/v1/Systems/<identity>/Actions/ComputerSystem.Reset',
methods=['POST'])
@api_utils.ensure_instance_access
@api_utils.returns_json
def system_reset_action(identity):
reset_type = flask.request.json.get('ResetType')
app.systems.set_power_state(identity, reset_type)
app.logger.info('System "%s" power state set to "%s"',
identity, reset_type)
return '', 204
@app.route('/redfish/v1/Systems/<identity>/BIOS', methods=['GET'])
@api_utils.ensure_instance_access
@api_utils.returns_json
def bios(identity):
if app.feature_set != "full":
raise error.FeatureNotAvailable("BIOS")
bios = app.systems.get_bios(identity)
app.logger.debug('Serving BIOS for system "%s"', identity)
return app.render_template(
'bios.json',
identity=identity,
bios_current_attributes=json.dumps(bios, sort_keys=True, indent=6))
@app.route('/redfish/v1/Systems/<identity>/BIOS/Settings',
methods=['GET', 'PATCH'])
@api_utils.ensure_instance_access
@api_utils.returns_json
def bios_settings(identity):
if app.feature_set != "full":
raise error.FeatureNotAvailable("BIOS")
if flask.request.method == 'GET':
bios = app.systems.get_bios(identity)
app.logger.debug('Serving BIOS Settings for system "%s"', identity)
return app.render_template(
'bios_settings.json',
identity=identity,
bios_pending_attributes=json.dumps(bios, sort_keys=True, indent=6))
elif flask.request.method == 'PATCH':
attributes = flask.request.json.get('Attributes')
app.systems.set_bios(identity, attributes)
app.logger.info('System "%s" BIOS attributes "%s" updated',
identity, attributes)
return '', 204
@app.route('/redfish/v1/Systems/<identity>/BIOS/Actions/Bios.ResetBios',
methods=['POST'])
@api_utils.ensure_instance_access
@api_utils.returns_json
def system_reset_bios(identity):
if app.feature_set != "full":
raise error.FeatureNotAvailable("BIOS")
app.systems.reset_bios(identity)
app.logger.info('BIOS for system "%s" reset', identity)
return '', 204
@app.route('/redfish/v1/Systems/<identity>/SecureBoot',
methods=['GET', 'PATCH'])
@api_utils.ensure_instance_access
@api_utils.returns_json
def secure_boot(identity):
if app.feature_set != "full":
raise error.FeatureNotAvailable("SecureBoot")
if flask.request.method == 'GET':
secure = app.systems.get_secure_boot(identity)
app.logger.debug('Serving secure boot for system "%s"', identity)
return app.render_template(
'secure_boot.json',
identity=identity,
secure_boot_enable=secure,
secure_boot_current_boot=secure and 'Enabled' or 'Disabled')
elif flask.request.method == 'PATCH':
secure = flask.request.json.get('SecureBootEnable')
app.systems.set_secure_boot(identity, secure)
app.logger.info('System "%s" secure boot updated to "%s"',
identity, secure)
return '', 204
@app.route('/redfish/v1/Systems/<identity>/SimpleStorage',
methods=['GET'])
@api_utils.ensure_instance_access
@api_utils.returns_json
def simple_storage_collection(identity):
if app.feature_set != "full":
raise error.FeatureNotAvailable("SimpleStorage")
simple_storage_controllers = (
app.systems.get_simple_storage_collection(identity))
return app.render_template(
'simple_storage_collection.json', identity=identity,
simple_storage_controllers=simple_storage_controllers)
@app.route('/redfish/v1/Systems/<identity>/SimpleStorage/<simple_storage_id>',
methods=['GET'])
@api_utils.ensure_instance_access
@api_utils.returns_json
def simple_storage(identity, simple_storage_id):
if app.feature_set != "full":
raise error.FeatureNotAvailable("SimpleStorage")
simple_storage_controllers = (
app.systems.get_simple_storage_collection(identity))
try:
storage_controller = simple_storage_controllers[simple_storage_id]
except KeyError:
app.logger.debug('"%s" Simple Storage resource was not found')
raise error.NotFound()
return app.render_template('simple_storage.json', identity=identity,
simple_storage=storage_controller)
@app.route('/redfish/v1/Systems/<identity>/Storage',
methods=['GET'])
@api_utils.ensure_instance_access
@api_utils.returns_json
def storage_collection(identity):
if app.feature_set != "full":
raise error.FeatureNotAvailable("Storage")
uuid = app.systems.uuid(identity)
storage_col = app.storage.get_storage_col(uuid)
return app.render_template(
'storage_collection.json', identity=identity,
storage_col=storage_col)
@app.route('/redfish/v1/Systems/<identity>/Storage/<storage_id>',
methods=['GET'])
@api_utils.ensure_instance_access
@api_utils.returns_json
def storage(identity, storage_id):
if app.feature_set != "full":
raise error.FeatureNotAvailable("Storage")
uuid = app.systems.uuid(identity)
storage_col = app.storage.get_storage_col(uuid)
for stg in storage_col:
if stg['Id'] == storage_id:
return app.render_template(
'storage.json', identity=identity, storage=stg)
raise error.NotFound()
@app.route('/redfish/v1/Systems/<identity>/Storage/<stg_id>/Drives/<drv_id>',
methods=['GET'])
@api_utils.ensure_instance_access
@api_utils.returns_json
def drive_resource(identity, stg_id, drv_id):
if app.feature_set != "full":
raise error.FeatureNotAvailable("Storage")
uuid = app.systems.uuid(identity)
drives = app.drives.get_drives(uuid, stg_id)
for drv in drives:
if drv['Id'] == drv_id:
return app.render_template(
'drive.json', identity=identity, storage_id=stg_id, drive=drv)
raise error.NotFound()
@app.route('/redfish/v1/Systems/<identity>/Storage/<storage_id>/Volumes',
methods=['GET', 'POST'])
@api_utils.ensure_instance_access
@api_utils.returns_json
def volumes_collection(identity, storage_id):
if app.feature_set != "full":
raise error.FeatureNotAvailable("Storage")
uuid = app.systems.uuid(identity)
if flask.request.method == 'GET':
vol_col = app.volumes.get_volumes_col(uuid, storage_id)
vol_ids = []
for vol in vol_col:
vol_id = app.systems.find_or_create_storage_volume(vol)
if not vol_id:
app.volumes.delete_volume(uuid, storage_id, vol)
else:
vol_ids.append(vol_id)
return app.render_template(
'volume_collection.json', identity=identity,
storage_id=storage_id, volume_col=vol_ids)
elif flask.request.method == 'POST':
data = {
"Name": flask.request.json.get('Name'),
"VolumeType": flask.request.json.get('VolumeType'),
"CapacityBytes": flask.request.json.get('CapacityBytes'),
"Id": str(os.getpid()) + datetime.now().strftime("%H%M%S")
}
data['libvirtVolName'] = data['Id']
new_id = app.systems.find_or_create_storage_volume(data)
if new_id:
app.volumes.add_volume(uuid, storage_id, data)
app.logger.debug('New storage volume created with ID "%s"',
new_id)
vol_url = ("/redfish/v1/Systems/%s/Storage/%s/"
"Volumes/%s" % (identity, storage_id, new_id))
return flask.Response(status=201,
headers={'Location': vol_url})
@app.route('/redfish/v1/Systems/<identity>/Storage/<stg_id>/Volumes/<vol_id>',
methods=['GET'])
@api_utils.ensure_instance_access
@api_utils.returns_json
def volume(identity, stg_id, vol_id):
if app.feature_set != "full":
raise error.FeatureNotAvailable("Storage")
uuid = app.systems.uuid(identity)
vol_col = app.volumes.get_volumes_col(uuid, stg_id)
for vol in vol_col:
if vol['Id'] == vol_id:
vol_id = app.systems.find_or_create_storage_volume(vol)
if not vol_id:
app.volumes.delete_volume(uuid, stg_id, vol)
else:
return app.render_template(
'volume.json', identity=identity, storage_id=stg_id,
volume=vol)
raise error.NotFound()
@app.route('/redfish/v1/Registries')
@api_utils.returns_json
def registry_file_collection():
if app.feature_set != "full":
raise error.FeatureNotAvailable("Registries")
app.logger.debug('Serving registry file collection')
return app.render_template(
'registry_file_collection.json')
@app.route('/redfish/v1/Registries/BiosAttributeRegistry.v1_0_0')
@api_utils.returns_json
def bios_attribute_registry_file():
if app.feature_set != "full":
raise error.FeatureNotAvailable("Registries")
app.logger.debug('Serving BIOS attribute registry file')
return app.render_template(
'bios_attribute_registry_file.json')
@app.route('/redfish/v1/Registries/Messages')
@api_utils.returns_json
def message_registry_file():
if app.feature_set != "full":
raise error.FeatureNotAvailable("Registries")
app.logger.debug('Serving message registry file')
return app.render_template(
'message_registry_file.json')
@app.route('/redfish/v1/Systems/Bios/BiosRegistry')
@api_utils.returns_json
def bios_registry():
if app.feature_set != "full":
raise error.FeatureNotAvailable("Registries")
app.logger.debug('Serving BIOS registry')
return app.render_template('bios_registry.json')
@app.route('/redfish/v1/Registries/Messages/Registry')
@api_utils.returns_json
def message_registry():
if app.feature_set != "full":
raise error.FeatureNotAvailable("Registries")
app.logger.debug('Serving message registry')
return app.render_template('message_registry.json')
def parse_args():
parser = argparse.ArgumentParser('sushy-emulator')
parser.add_argument('--config',
type=str,
help='Config file path. Can also be set via '
'environment variable SUSHY_EMULATOR_CONFIG.')
parser.add_argument('--debug', action='store_true',
help='Enables debug mode when running sushy-emulator.')
parser.add_argument('-i', '--interface',
type=str,
help='IP address of the local interface to listen '
'at. Can also be set via config variable '
'SUSHY_EMULATOR_LISTEN_IP. Default is all '
'local interfaces.')
parser.add_argument('-p', '--port',
type=int,
help='TCP port to bind the server to. Can also be '
'set via config variable '
'SUSHY_EMULATOR_LISTEN_PORT. Default is 8000.')
parser.add_argument('--ssl-certificate',
type=str,
help='SSL certificate to use for HTTPS. Can also be '
'set via config variable SUSHY_EMULATOR_SSL_CERT.')
parser.add_argument('--ssl-key',
type=str,
help='SSL key to use for HTTPS. Can also be set'
'via config variable SUSHY_EMULATOR_SSL_KEY.')
parser.add_argument('--feature-set',
type=str, choices=['full', 'vmedia', 'minimum'],
help='Feature set to provide. Can also be set'
'via config variable SUSHY_EMULATOR_FEATURE_SET.')
backend_group = parser.add_mutually_exclusive_group()
backend_group.add_argument('--os-cloud',
type=str,
help='OpenStack cloud name. Can also be set '
'via environment variable OS_CLOUD or '
'config variable SUSHY_EMULATOR_OS_CLOUD.'
)
backend_group.add_argument('--libvirt-uri',
type=str,
help='The libvirt URI. Can also be set via '
'environment variable '
'SUSHY_EMULATOR_LIBVIRT_URI. '
'Default is qemu:///system')
backend_group.add_argument('--fake', action='store_true',
help='Use the fake driver. Can also be set '
'via environment variable '
'SUSHY_EMULATOR_FAKE_DRIVER.')
backend_group.add_argument('--ironic-cloud',
type=str,
help='Ironic cloud name. Can also be set via '
'via config variable '
'SUSHY_EMULATOR_IRONIC_CLOUD.')
return parser.parse_args()
def main():
args = parse_args()
app.debug = args.debug
app.configure(config_file=args.config)
if args.os_cloud:
app.config['SUSHY_EMULATOR_OS_CLOUD'] = args.os_cloud
if args.libvirt_uri:
app.config['SUSHY_EMULATOR_LIBVIRT_URI'] = args.libvirt_uri
if args.ironic_cloud:
app.config['SUSHY_EMULATOR_IRONIC_CLOUD'] = args.ironic_cloud
if args.fake:
app.config['SUSHY_EMULATOR_FAKE_DRIVER'] = True
else:
for envvar in ('SUSHY_EMULATOR_LIBVIRT_URL', # backward compatibility
'SUSHY_EMULATOR_LIBVIRT_URI'):
envvar = os.environ.get(envvar)
if envvar:
app.config['SUSHY_EMULATOR_LIBVIRT_URI'] = envvar
if args.interface:
app.config['SUSHY_EMULATOR_LISTEN_IP'] = args.interface
if args.port:
app.config['SUSHY_EMULATOR_LISTEN_PORT'] = args.port
if args.ssl_certificate:
app.config['SUSHY_EMULATOR_SSL_CERT'] = args.ssl_certificate
if args.ssl_key:
app.config['SUSHY_EMULATOR_SSL_KEY'] = args.ssl_key
if args.feature_set:
app.config['SUSHY_EMULATOR_FEATURE_SET'] = args.feature_set
ssl_context = None
ssl_certificate = app.config.get('SUSHY_EMULATOR_SSL_CERT')
ssl_key = app.config.get('SUSHY_EMULATOR_SSL_KEY')
if ssl_certificate and ssl_key:
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
ssl_context.load_cert_chain(ssl_certificate, ssl_key)
app.run(host=app.config.get('SUSHY_EMULATOR_LISTEN_IP'),
port=app.config.get('SUSHY_EMULATOR_LISTEN_PORT', 8000),
ssl_context=ssl_context)
return 0
if __name__ == '__main__':
sys.exit(main())