# 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 update_service as usctl 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(os.path.abspath(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.register_blueprint(usctl.update_service) @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/', 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//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/', 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/', methods=['GET', 'PATCH']) @api_utils.ensure_instance_access @api_utils.returns_json def system_resource(identity): uuid = app.systems.uuid(identity) try: versions = app.systems.get_versions(identity) except error.NotSupportedError: app.logger.debug('Fetching BIOS version information not supported ' 'for system "%s"', identity) versions = {} bios_version = versions.get('BiosVersion') 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), bios_version=bios_version, 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 # Explicitly set to CD as in this case we will boot a an iso # image provided, not precisely the same, but BMC facilitated # HTTPBoot is a little different and the overall functionality # test is more important. target = 'Cd' if target == 'UefiHttp' and not http_uri: # Reset to Pxe, 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 = 'Pxe' 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//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//EthernetInterfaces/', 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//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//Processors/', 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//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') if app.config.get('SUSHY_EMULATOR_DISABLE_POWER_OFF') is True and \ reset_type in ('ForceOff', 'GracefulShutdown'): raise error.BadRequest('Can not request power off transition. It is ' 'disabled via the ' 'SUSHY_EMULATOR_DISABLE_POWER_OFF configuration' 'option.') 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//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//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//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//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//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//SimpleStorage/', 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//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//Storage/', 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//Storage//Drives/', 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//Storage//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//Storage//Volumes/', 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') @app.route('/redfish/v1/TaskService', methods=['GET']) @api_utils.ensure_instance_access @api_utils.returns_json def simple_task_service(): return app.render_template('task_service.json') @app.route('/redfish/v1/TaskService/Tasks/42', methods=['GET']) @api_utils.ensure_instance_access @api_utils.returns_json def simple_task(): return app.render_template('task.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())