From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mx0b-002e3701.pphosted.com (mx0b-002e3701.pphosted.com [148.163.143.35]) by mx.groups.io with SMTP id smtpd.web11.4664.1626937413510161341 for ; Thu, 22 Jul 2021 00:03:33 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@hpe.com header.s=pps0720 header.b=AoO+MqPq; spf=permerror, err=parse error for token &{10 18 %{ir}.%{v}.%{d}.spf.has.pphosted.com}: invalid domain name (domain: hpe.com, ip: 148.163.143.35, mailfrom: prvs=08370df290=abner.chang@hpe.com) Received: from pps.filterd (m0150245.ppops.net [127.0.0.1]) by mx0b-002e3701.pphosted.com (8.16.0.43/8.16.0.43) with SMTP id 16M731tn008052; Thu, 22 Jul 2021 07:03:27 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=hpe.com; h=from : to : cc : subject : date : message-id : in-reply-to : references : mime-version; s=pps0720; bh=hNpdk2db1cpQuDsqzf0qc2gAM5M1VwbuUKBz5vTXW9o=; b=AoO+MqPqVYRcOOsCz1UzHG5ek+l4jIzrTuRXBhSdgdLW4L/hpvChbyj/BIM+bWOzQepd Z5nvBKybec0Zm3oCTNsYYVgsthS1cAgyizlCzakU0CBFFbPnL3V8p/7Y2dUS3d6mmyPN hBt/FxA3Uv6pnThq3wJRRwoMijRM0RsI0VzR6gKx8yK+HbE8pMAoV9mP9RF5BoBt64Xg XvoxFmEev/Uo4hstvhXft+wJrERo6sT8uzOdbuGZtE7gS0F/V/pFzNTXzfG/ZXT2x+b1 baHGCngrFYiV/ESnGG1Wctb0SUTro15GgOkvnGB+8hlu2/DUfU3Qm1I5oaldzqjgEgIS 8w== Received: from g9t5008.houston.hpe.com (g9t5008.houston.hpe.com [15.241.48.72]) by mx0b-002e3701.pphosted.com with ESMTP id 39xs2a4f9x-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Thu, 22 Jul 2021 07:03:27 +0000 Received: from g9t2301.houston.hpecorp.net (g9t2301.houston.hpecorp.net [16.220.97.129]) by g9t5008.houston.hpe.com (Postfix) with ESMTP id AC0E472; Thu, 22 Jul 2021 07:03:26 +0000 (UTC) Received: from abner-virtual-machine.asiapacific.hpqcorp.net (abner-virtual-machine.asiapacific.hpqcorp.net [15.119.210.153]) by g9t2301.houston.hpecorp.net (Postfix) with ESMTP id C4F3957; Thu, 22 Jul 2021 07:03:25 +0000 (UTC) From: "Abner Chang" To: devel@edk2.groups.io Cc: Nickle Wang , Liming Gao Subject: [staging/edk2-redfish-client Tools PATCH 3/6] RedfishClientPkg/Redfish-Profile-Simulator: Add more features Date: Thu, 22 Jul 2021 14:08:14 +0800 Message-Id: <20210722060817.18564-4-abner.chang@hpe.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20210722060817.18564-1-abner.chang@hpe.com> References: <20210722060817.18564-1-abner.chang@hpe.com> X-Proofpoint-GUID: BE_mi9EPsChjp5l30kuR0oXxwWO0630b X-Proofpoint-ORIG-GUID: BE_mi9EPsChjp5l30kuR0oXxwWO0630b X-Proofpoint-UnRewURL: 0 URL was un-rewritten MIME-Version: 1.0 X-HPE-SCL: -1 X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10434:6.0.391,18.0.790 definitions=2021-07-22_03:2021-07-22,2021-07-22 signatures=0 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 mlxscore=0 clxscore=1015 adultscore=0 mlxlogscore=999 spamscore=0 malwarescore=0 bulkscore=0 phishscore=0 impostorscore=0 priorityscore=1501 suspectscore=0 lowpriorityscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2104190000 definitions=main-2107220043 - Add HTTPs support - Add ETAG support - Change default credential to admin/pwd123456 - Add HTTP methods on BIOS managed resource. Signed-off-by: Abner Chang Cc: Nickle Wang Cc: Liming Gao --- .../redfishProfileSimulator.py | 92 ++++++++-- .../v1sim/redfishURIs.py | 161 ++++++++++++------ .../v1sim/registry.py | 14 ++ .../v1sim/resource.py | 27 ++- .../v1sim/systems.py | 85 ++++++++- 5 files changed, 311 insertions(+), 68 deletions(-) create mode 100644 RedfishClientPkg/Tools/Redfish-Profile-Simulator/v1sim/registry.py diff --git a/RedfishClientPkg/Tools/Redfish-Profile-Simulator/redfishProfileSimulator.py b/RedfishClientPkg/Tools/Redfish-Profile-Simulator/redfishProfileSimulator.py index 24be52bafc..91c792a2b7 100644 --- a/RedfishClientPkg/Tools/Redfish-Profile-Simulator/redfishProfileSimulator.py +++ b/RedfishClientPkg/Tools/Redfish-Profile-Simulator/redfishProfileSimulator.py @@ -1,4 +1,9 @@ # Copyright Notice: +# +# Copyright (c) 2019, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +# +# Copyright Notice: # Copyright 2016 Distributed Management Task Force, Inc. All rights reserved. # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Profile-Simulator/blob/master/LICENSE.md @@ -9,13 +14,16 @@ import sys import getopt import os +import functools +import flask +import werkzeug rfVersion = "0.9.6" rfProgram1 = "redfishProfileSimulator" rfProgram2 = " " rfUsage1 = "[-Vh] [--Version][--help]" -rfUsage2 = "[-H] [-P] [-p]" -rfUsage3 = "[--Host=] [--Port=] [--profile_path=]" +rfUsage2 = "[-H] [-P] [-C] [-K] [-p]" +rfUsage3 = "[--Host=] [--Port=] [--Cert=] [--Key=] [--profile_path=]" def rf_usage(): @@ -27,18 +35,19 @@ def rf_usage(): def rf_help(): print(rfProgram1,"implements a simulation of a redfish service for the \"Simple OCP Server V1\" Mockup.") - print(" The simulation includes an http server, RestEngine, and dynamic Redfish datamodel.") + print(" The simulation includes an http/https server, RestEngine, and dynamic Redfish datamodel.") print(" You can GET, PATHCH,... to the service just like a real Redfish service.") print(" Both Basic and Redfish Session/Token authentication is supported (for a single user/passwd and token") print(" the user/passwd is: root/password123456. The authToken is: 123456SESSIONauthcode") print(" these can be changed by editing the redfishURIs.py file. will make dynamic later.") - print(" The http service and Rest engine is built on Flask, and all code is Python 3.4+") + print(" The http/https service and Rest engine is built on Flask, and all code is Python 3.4+") print(" The data model resources are \"initialized\" from the SPMF \"SimpleOcpServerV1\" Mockup.") print(" and stored as python dictionaries--then the dictionaries are updated with patches, posts, deletes.") print(" The program can be extended to support other mockup \"profiles\".") print("") - print(" By default, the simulation runs on localhost (127.0.0.1), on port 5000.") - print(" These can be changed with CLI options: -P -H | --port= --host=") + print(" By default, the simulation runs over http, on localhost (127.0.0.1), on port 5000.") + print(" These can be changed with CLI options: -P -C -K -H | --port= --Cert= --Key= --host=") + print(" -C -K | --Cert= --Key= options must be used together with port 443 to enable https session.") print("") print("Version: ", rfVersion) rf_usage() @@ -47,19 +56,69 @@ def rf_help(): print(" -h, --help, --- help") print(" -H, --Host= --- host IP address. dflt=127.0.0.1") print(" -P, --Port= --- the port to use. dflt=5000") + print(" -C, --Cert= --- Server certificate.") + print(" -K, --Key= --- Server key.") print(" -p, --profile= --- the path to the Redfish profile to use. " "dflt=\"./MockupData/SimpleOcpServerV1\" ") +# Conditional Requests with ETags +# http://flask.pocoo.org/snippets/95/ +def conditional(func): + '''Start conditional method execution for this resource''' + @functools.wraps(func) + def wrapper(*args, **kwargs): + flask.g.condtnl_etags_start = True + return func(*args, **kwargs) + return wrapper + +class NotModified(werkzeug.exceptions.HTTPException): + code = 304 + def get_response(self, environment): + return flask.Response(status=304) + +class PreconditionRequired(werkzeug.exceptions.HTTPException): + code = 428 + description = ('

This request is required to be ' + 'conditional; try using "If-Match".') + name = 'Precondition Required' + def get_response(self, environment): + resp = super(PreconditionRequired, + self).get_response(environment) + resp.status = str(self.code) + ' ' + self.name.upper() + return resp def main(argv): + #Monkey patch the set_etag() method for conditional request. + _old_set_etag = werkzeug.ETagResponseMixin.set_etag + @functools.wraps(werkzeug.ETagResponseMixin.set_etag) + def _new_set_etag(self, etag, weak=False): + # only check the first time through; when called twice + # we're modifying + if (hasattr(flask.g, 'condtnl_etags_start') and + flask.g.condtnl_etags_start): + if flask.request.method in ('PUT', 'DELETE', 'PATCH'): + if not flask.request.if_match: + raise PreconditionRequired + if etag not in flask.request.if_match: + flask.abort(412) + elif (flask.request.method == 'GET' and + flask.request.if_none_match and + etag in flask.request.if_none_match): + raise NotModified + flask.g.condtnl_etags_start = False + _old_set_etag(self, etag, weak) + werkzeug.ETagResponseMixin.set_etag = _new_set_etag + # set default option args rf_profile_path = os.path.abspath("./MockupData/SimpleOcpServerV1") - rf_host = "127.0.0.1" + rf_host = "0.0.0.0" rf_port = 5000 + rf_cert ="" + rf_key="" try: - opts, args = getopt.getopt(argv[1:], "VhH:P:p:", - ["Version", "help", "Host=", "Port=", "profile="]) + opts, args = getopt.getopt(argv[1:], "VhH:P:C:K:p:", + ["Version", "help", "Host=", "Port=", "Cert=", "Key=", "profile="]) except getopt.GetoptError: print(rfProgram1, ": Error parsing options") rf_usage() @@ -77,11 +136,24 @@ def main(argv): rf_host = arg elif opt in "--Port=": rf_port=int(arg) + elif opt in "--Cert=": + rf_cert=arg + elif opt in "--Key=": + rf_key=arg else: print(" ", rfProgram1, ": Error: unsupported option") rf_usage() sys.exit(2) + if rf_port == 443: + if rf_cert == "" or rf_key == "": + print(" ", rfProgram1, ": Error: port 443 must be used together with -C and -K to enable https session") + sys.exit(2) + else: + if rf_cert != "" or rf_key != "": + print(" ", rfProgram1, ": Error: -C and -K options must be used together with port 443 to enable https session") + sys.exit(2) + print("{} Version: {}".format(rfProgram1,rfVersion)) print(" Starting redfishProfileSimulator at: hostIP={}, port={}".format(rf_host, rf_port)) print(" Using Profile at {}".format(rf_profile_path)) @@ -102,7 +174,7 @@ def main(argv): root = RfServiceRoot(rf_profile_path, root_path) # start the flask REST API service - rfApi_SimpleServer(root, versions, host=rf_host, port=rf_port) + rfApi_SimpleServer(root, versions, host=rf_host, port=rf_port, cert=rf_cert, key=rf_key) else: print("invalid profile path") diff --git a/RedfishClientPkg/Tools/Redfish-Profile-Simulator/v1sim/redfishURIs.py b/RedfishClientPkg/Tools/Redfish-Profile-Simulator/v1sim/redfishURIs.py index 2380a4058a..3c912f7ce1 100644 --- a/RedfishClientPkg/Tools/Redfish-Profile-Simulator/v1sim/redfishURIs.py +++ b/RedfishClientPkg/Tools/Redfish-Profile-Simulator/v1sim/redfishURIs.py @@ -1,17 +1,23 @@ +# +# Copyright Notice: +# Copyright (c) 2019, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +# # Copyright Notice: # Copyright 2016 Distributed Management Task Force, Inc. All rights reserved. # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Profile-Simulator/blob/master/LICENSE.md import json +from collections import OrderedDict from flask import Flask from flask import request from .flask_redfish_auth import RfHTTPBasicOrTokenAuth -from .resource import RfResource, RfResourceRaw, RfCollection +from werkzeug.serving import WSGIRequestHandler -def rfApi_SimpleServer(root, versions, host="127.0.0.1", port=5000): +def rfApi_SimpleServer(root, versions, host="127.0.0.1", port=5000, cert="", key=""): app = Flask(__name__) # create auth class that does basic or redifish session auth @@ -21,8 +27,8 @@ def rfApi_SimpleServer(root, versions, host="127.0.0.1", port=5000): # for basic auth, we only support user=catfish, passwd=hunter @auth.verify_basic_password def verify_rf_passwd(user, passwd): - if user == "root": - if passwd == "password123456": + if user == "admin": + if passwd == "pwd123456": return True return False @@ -43,13 +49,13 @@ def rfApi_SimpleServer(root, versions, host="127.0.0.1", port=5000): # GET /redfish @app.route("/redfish", methods=['GET']) - @app.route("/redfish/", methods=['GET']) + #@app.route("/redfish/", methods=['GET']) def rf_versions(): return versions.get_resource() # GET /redfish/v1 @app.route("/redfish/v1", methods=['GET']) - @app.route("/redfish/v1/", methods=['GET']) + #@app.route("/redfish/v1/", methods=['GET']) def rf_service_root(): return root.get_resource() @@ -65,8 +71,9 @@ def rfApi_SimpleServer(root, versions, host="127.0.0.1", port=5000): return resolve_path(root, rf_path) @app.route("/redfish/v1/", methods=['GET']) - @app.route("/redfish/v1//", methods=['GET']) + #@app.route("/redfish/v1//", methods=['GET']) @auth.rfAuthRequired + @conditional def rf_subsystems(rf_path): return resolve_path(root, rf_path) @@ -78,135 +85,189 @@ def rfApi_SimpleServer(root, versions, host="127.0.0.1", port=5000): return root.get_resource() @app.route("/redfish/v1/Systems/", methods=['PATCH']) - @app.route("/redfish/v1/Systems//", methods=['PATCH']) + #@app.route("/redfish/v1/Systems//", methods=['PATCH']) @auth.rfAuthRequired def rf_computer_systempatch(sys_path): - rdata = request.get_json(cache=True) + rdata = json.loads(request.data,object_pairs_hook=OrderedDict) print("rdata:{}".format(rdata)) - obj = patch_path(root.systems, sys_path) + obj = patch_path(root.components['Systems'], sys_path) rc, status_code, err_string, resp = obj.patch_resource(rdata) if rc == 0: - return "", status_code + return resp, status_code + else: + return err_string, status_code + + @app.route("/redfish/v1/Systems//BootOptions/", methods=['GET']) + @auth.rfAuthRequired + def rf_computer_bootoptions_get(system_id, bootOptIdx): + return root.components['Systems'].get_element(system_id).components['BootOptions'].get_bootOpt(bootOptIdx) + + @app.route("/redfish/v1/Systems//BootOptions/", methods=['DELETE']) + @auth.rfAuthRequired + def rf_computer_bootoptions_del(system_id, bootOptIdx): + print("in rf_computer_bootoptions_del") + rc, status_code, err_string, resp = root.components['Systems'].get_element(system_id).components['BootOptions'].delete_bootOpt(bootOptIdx) + if rc == 0: + return resp, status_code + else: + return err_string, status_code + + @app.route("/redfish/v1/Systems//BootOptions/", methods=['PATCH']) + @auth.rfAuthRequired + def rf_computer_bootoption_patch(system_id, bootOptIdx): + print ("in POST boot options") + rdata = json.loads(request.data,object_pairs_hook=OrderedDict) + print("rdata:{}".format(rdata)) + rc, status_code, err_string, resp = root.components['Systems'].get_element(system_id).components['BootOptions'].patch_bootOpt(bootOptIdx, rdata) + if rc == 0: + return resp, status_code + else: + return err_string, status_code + + @app.route("/redfish/v1/Systems//BootOptions", methods=['POST']) + @auth.rfAuthRequired + def rf_computer_bootoptions_post(system_id): + print ("in POST boot options") + rdata = json.loads(request.data,object_pairs_hook=OrderedDict) + print("rdata:{}".format(rdata)) + rc, status_code, err_string, resp = root.components['Systems'].get_element(system_id).components['BootOptions'].post_resource(rdata) + if rc == 0: + return resp, status_code else: return err_string, status_code @app.route("/redfish/v1/Systems//Actions/ComputerSystem.Reset", methods=['POST']) - @app.route("/redfish/v1/Systems//Actions/ComputerSystem.Reset/", methods=['POST']) + #@app.route("/redfish/v1/Systems//Actions/ComputerSystem.Reset/", methods=['POST']) @auth.rfAuthRequired def rf_computer_systemreset(system_id): # print("in reset") - rdata = request.get_json(cache=True) + rdata = json.loads(request.data,object_pairs_hook=OrderedDict) # print("rdata:{}".format(rdata)) rc, status_code, err_string, resp = root.components['Systems'].get_element(system_id).reset_resource(rdata) if rc == 0: - return "", status_code + return resp, status_code else: return err_string, status_code - @app.route("/redfish/v1/Systems//bios/Actions/Bios.ResetBios", methods=['POST']) - @app.route("/redfish/v1/Systems//bios/Actions/Bios.ResetBios/", methods=['POST']) + @app.route("/redfish/v1/Systems//Bios/Actions/Bios.ResetBios", methods=['POST']) + #@app.route("/redfish/v1/Systems//Bios/Actions/Bios.ResetBios/", methods=['POST']) @auth.rfAuthRequired def rf_computer_biosreset(system_id): # print("in reset") - rdata = request.get_json(cache=True) + rdata = json.loads(request.data,object_pairs_hook=OrderedDict) # print("rdata:{}".format(rdata)) system = root.systems.get_element(system_id) - bios = system.get_component("bios") + bios = system.get_component("Bios") rc, status_code, err_string, resp = bios.reset_resource(rdata) if rc == 0: - return "", status_code + return resp, status_code else: return err_string, status_code - @app.route("/redfish/v1/Systems//bios/Actions/Bios.ChangePassword", methods=['PATCH']) - @app.route("/redfish/v1/Systems//bios/Actions/Bios.ChangePassword/", methods=['PATCH']) + @app.route("/redfish/v1/Systems//Bios/Actions/Bios.ChangePassword", methods=['PATCH']) + #@app.route("/redfish/v1/Systems//Bios/Actions/Bios.ChangePassword/", methods=['PATCH']) @auth.rfAuthRequired def rf_computer_change_pswd(system_id): # print("in reset") - rdata = request.get_json(cache=True) + rdata = json.loads(request.data,object_pairs_hook=OrderedDict) # print("rdata:{}".format(rdata)) system = root.systems.get_element(system_id) - bios = system.get_component("bios") + bios = system.get_component("Bios") rc, status_code, err_string, resp = bios.change_password(rdata) if rc == 0: - return "", status_code + return resp, status_code else: return err_string, status_code @app.route("/redfish/v1/Chassis//Actions/Chassis.Reset", methods=['POST']) - @app.route("/redfish/v1/Chassis//Actions/Chassis.Reset/", methods=['POST']) + #@app.route("/redfish/v1/Chassis//Actions/Chassis.Reset/", methods=['POST']) @auth.rfAuthRequired def rf_computer_chassisreset(chassis_id): # print("in reset") - rdata = request.get_json(cache=True) + rdata = json.loads(request.data,object_pairs_hook=OrderedDict) # print("rdata:{}".format(rdata)) rc, status_code, err_string, resp = root.chassis.get_element(chassis_id).reset_resource(rdata) if rc == 0: - return "", status_code + return resp, status_code else: return err_string, status_code @app.route("/redfish/v1/Chassis//Power", methods=['PATCH']) - @app.route("/redfish/v1/Chassis//Power/", methods=['PATCH']) + #@app.route("/redfish/v1/Chassis//Power/", methods=['PATCH']) @auth.rfAuthRequired def rf_chassis_powerpatch(chassis_id): # rawdata=request.data - rdata = request.get_json(cache=True) + rdata = json.loads(request.data,object_pairs_hook=OrderedDict) # print("RRrdata:{}".format(rdata)) rc, status_code, err_string, resp = root.chassis.get_element(chassis_id).power.patch_resource(rdata) + if rc == 0: + return resp, status_code + else: + return err_string, status_code + + @app.route("/redfish/v1/Registries/", methods=['PATCH']) + @auth.rfAuthRequired + def rf_registries_patch(sys_path): + rdata = json.loads(request.data,object_pairs_hook=OrderedDict) + print("rdata:{}".format(rdata)) + obj = patch_path(root.components['Registries'], sys_path) + rc, status_code, err_string, resp = obj.patch_resource(rdata) if rc == 0: return "", status_code else: return err_string, status_code @app.route("/redfish/v1/Managers/", methods=['PATCH']) - @app.route("/redfish/v1/Managers//", methods=['PATCH']) + #@app.route("/redfish/v1/Managers//", methods=['PATCH']) @auth.rfAuthRequired def rf_patch_manager_entity(manager_id): - rdata = request.get_json(cache=True) + rdata = json.loads(request.data,object_pairs_hook=OrderedDict) # print("RRrdata:{}".format(rdata)) rc, status_code, err_string, resp = root.managers.get_element(manager_id).patch_resource(rdata) if rc == 0: - return "", status_code + return resp, status_code else: return err_string, status_code # rest/v1/Managers/1 @app.route("/redfish/v1/Managers//Actions/Manager.Reset", methods=['POST']) - @app.route("/redfish/v1/Managers//Actions/Manager.Reset/", methods=['POST']) + #@app.route("/redfish/v1/Managers//Actions/Manager.Reset/", methods=['POST']) @auth.rfAuthRequired def rf_reset_manager(manager_id): - rdata = request.get_json(cache=True) + rdata = json.loads(request.data,object_pairs_hook=OrderedDict) # print("rdata:{}".format(rdata)) rc, status_code, err_string, resp = root.managers.get_element(manager_id).reset_resource(rdata) if rc == 0: - return "", status_code + return resp, status_code else: return err_string, status_code @app.route("/redfish/v1/Managers//EthernetInterfaces/", methods=['PATCH']) - @app.route("/redfish/v1/Managers//EthernetInterfaces//", methods=['PATCH']) + #@app.route("/redfish/v1/Managers//EthernetInterfaces//", methods=['PATCH']) @auth.rfAuthRequired def rf_patch_manager_nic_entity(manager_id, eth_id): resp = root.managers.get_element(manager_id).ethernetColl.get_interface(eth_id).get_resource() - rdata = request.get_json(cache=True) + rdata = json.loads(request.data,object_pairs_hook=OrderedDict) # print("RRrdata:{}".format(rdata)) ethernet_coll = root.managers.get_element(manager_id).ethernetColl rc, status_code, err_string, resp = ethernet_coll.get_interface(eth_id).patch_resource(rdata) if rc == 0: - return "", status_code + return resp, status_code else: return err_string, status_code + @app.route("/redfish/v1/SessionService", methods=['GET']) + def rf_get_session_service(): + return root.components['SessionService'].get_resource() + @app.route("/redfish/v1/SessionService", methods=['PATCH']) - @app.route("/redfish/v1/SessionService/", methods=['PATCH']) - @auth.rfAuthRequired + #@app.route("/redfish/v1/SessionService/", methods=['PATCH']) def rf_patch_session_service(): - rdata = request.get_json(cache=True) + rdata = json.loads(request.data,object_pairs_hook=OrderedDict) # print("RRrdata:{}".format(rdata)) rc, status_code, err_string, resp = root.sessionService.patch_resource(rdata) if rc == 0: - return "", status_code + return resp, status_code else: return err_string, status_code @@ -215,7 +276,7 @@ def rfApi_SimpleServer(root, versions, host="127.0.0.1", port=5000): @app.route("/redfish/v1/SessionService/Sessions", methods=['POST']) def rf_login(): print("login") - rdata = request.get_json(cache=True) + rdata = json.loads(request.data,object_pairs_hook=OrderedDict) print("rdata:{}".format(rdata)) if rdata["UserName"] == "root" and rdata["Password"] == "password123456": x = {"Id": "SESSION123456"} @@ -233,17 +294,17 @@ def rfApi_SimpleServer(root, versions, host="127.0.0.1", port=5000): @auth.rfAuthRequired def rf_session_logout(session_id): print("session logout %s" % session_id) - # rdata=request.get_json(cache=True) + # rdata = json.loads(request.data,object_pairs_hook=OrderedDict) # print("rdata:{}".format(rdata)) return "", 204 @app.route("/redfish/v1/AccountService", methods=['PATCH']) @auth.rfAuthRequired def rf_patch_account_service(): - rdata = request.get_json(cache=True) + rdata = json.loads(request.data,object_pairs_hook=OrderedDict) rc, status_code, err_string, resp = root.accountService.patch_resource(rdata) if rc == 0: - return "", status_code + return resp, status_code else: return err_string, status_code @@ -293,12 +354,14 @@ def rfApi_SimpleServer(root, versions, host="127.0.0.1", port=5000): ''' # END file redfishURIs - # start Flask REST engine running - app.run(host=host, port=port) - # never returns + if key != "" and cert != "": + app.run(host=host, port=port, ssl_context=(cert, key)) + else: + app.run(host=host, port=port) + # never returns ''' reference source links: diff --git a/RedfishClientPkg/Tools/Redfish-Profile-Simulator/v1sim/registry.py b/RedfishClientPkg/Tools/Redfish-Profile-Simulator/v1sim/registry.py new file mode 100644 index 0000000000..9cfbb30cde --- /dev/null +++ b/RedfishClientPkg/Tools/Redfish-Profile-Simulator/v1sim/registry.py @@ -0,0 +1,14 @@ +# +# Copyright (c) 2019, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +# + +from .resource import RfResource, RfCollection + +class RfRegistryCollection(RfCollection): + def element_type(self): + return RfRegistry + +#subclass Bios +class RfRegistry(RfResource): + pass diff --git a/RedfishClientPkg/Tools/Redfish-Profile-Simulator/v1sim/resource.py b/RedfishClientPkg/Tools/Redfish-Profile-Simulator/v1sim/resource.py index 6fee348064..ca7541f172 100644 --- a/RedfishClientPkg/Tools/Redfish-Profile-Simulator/v1sim/resource.py +++ b/RedfishClientPkg/Tools/Redfish-Profile-Simulator/v1sim/resource.py @@ -1,6 +1,13 @@ +# +# Copyright Notice: +# +# Copyright (c) 2019, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +# # Copyright Notice: # Copyright 2016 Distributed Management Task Force, Inc. All rights reserved. # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Profile-Simulator/blob/master/LICENSE.md +# import json import os @@ -23,7 +30,7 @@ class RfResource: if os.path.exists(indx_file_path): res_file = open(indx_file_path, "r") res_rawdata = res_file.read() - self.res_data = json.loads(res_rawdata) + self.res_data = json.loads(res_rawdata,object_pairs_hook=OrderedDict) self.create_sub_objects(base_path, rel_path) self.final_init_processing(base_path, rel_path) else: @@ -36,7 +43,15 @@ class RfResource: pass def get_resource(self): - return flask.jsonify(self.res_data) + self.response=json.dumps(self.res_data,indent=4) + try: + # SHA1 should generate well-behaved etags + response = flask.make_response(self.response) + etag = hashlib.sha1(self.response.encode('utf-8')).hexdigest() + response.set_etag(etag) + return response + except KeyError: + flask.abort(404) def get_attribute(self, attribute): return flask.jsonify(self.res_data[attribute]) @@ -54,6 +69,14 @@ class RfResource: else: raise Exception("attribute %s not found" % key) + resp = flask.Response(json.dumps(self.res_data,indent=4)) + return 0, 200, None, resp + + def post_resource(self, post_data): + pass + + def delete_resource(self): + pass class RfResourceRaw: def __init__(self, base_path, rel_path, parent=None): diff --git a/RedfishClientPkg/Tools/Redfish-Profile-Simulator/v1sim/systems.py b/RedfishClientPkg/Tools/Redfish-Profile-Simulator/v1sim/systems.py index b107f035db..b8b3788054 100644 --- a/RedfishClientPkg/Tools/Redfish-Profile-Simulator/v1sim/systems.py +++ b/RedfishClientPkg/Tools/Redfish-Profile-Simulator/v1sim/systems.py @@ -1,6 +1,13 @@ +# +# Copyright Notice: +# +# Copyright (c) 2019, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +# # Copyright Notice: # Copyright 2016 Distributed Management Task Force, Inc. All rights reserved. # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Profile-Simulator/blob/master/LICENSE.md +# import os @@ -8,7 +15,9 @@ from .common_services import RfLogServiceCollection from .network import RfEthernetCollection, RfNetworkInterfaceCollection from .resource import RfResource, RfCollection from .storage import RfSimpleStorageCollection, RfSmartStorage - +import flask +import json +from collections import OrderedDict class RfSystemsCollection(RfCollection): def element_type(self): @@ -48,15 +57,17 @@ class RfSystemObj(RfResource): self.components[item] = RfUSBDeviceCollection(base_path, os.path.join(rel_path, item), parent=self) elif item == "USBPorts": self.components[item] = RfUSBPortCollection(base_path, os.path.join(rel_path, item), parent=self) + elif item == "BootOptions": + self.components[item] = RfBootOptionCollection(base_path, os.path.join(rel_path, item), parent=self) def patch_resource(self, patch_data): # first verify client didn't send us a property we cant patch for key in patch_data.keys(): - if key != "AssetTag" and key != "IndicatorLED" and key != "Boot": + if key != "AssetTag" and key != "IndicatorLED" and key != "Boot" and key != "BiosVersion": return 4, 400, "Invalid Patch Property Sent", "" elif key == "Boot": for prop2 in patch_data["Boot"].keys(): - if prop2 != "BootSourceOverrideEnabled" and prop2 != "BootSourceOverrideTarget": + if prop2 != "BootSourceOverrideEnabled" and prop2 != "BootSourceOverrideTarget" and prop2 != "BootNext" and prop2 != "BootOrder": return 4, 400, "Invalid Patch Property Sent", "" # now patch the valid properties sent if "AssetTag" in patch_data: @@ -64,6 +75,8 @@ class RfSystemObj(RfResource): self.res_data['AssetTag'] = patch_data['AssetTag'] if "IndicatorLED" in patch_data: self.res_data['IndicatorLED'] = patch_data['IndicatorLED'] + if "BiosVersion" in patch_data: + self.res_data['BiosVersion'] = patch_data['BiosVersion'] if "Boot" in patch_data: boot_data = patch_data["Boot"] if "BootSourceOverrideEnabled" in boot_data: @@ -80,7 +93,13 @@ class RfSystemObj(RfResource): self.res_data['Boot']['BootSourceOverrideTarget'] = value else: return 4, 400, "Invalid_Value_Specified: BootSourceOverrideTarget", "" - return 0, 204, None, None + if "BootNext" in boot_data: + self.res_data['Boot']['BootNext'] = boot_data['BootNext'] + if "BootOrder" in boot_data: + self.res_data['Boot']['BootOrder'] = boot_data['BootOrder'] + + resp = flask.Response(json.dumps(self.res_data,indent=4)) + return 0, 200, None, resp def reset_resource(self, reset_data): if "ResetType" in reset_data: @@ -145,13 +164,17 @@ class RfBiosSettings(RfResource): def patch_resource(self, patch_data): if "Attributes" not in patch_data: return 4, 400, "Invalid Payload. No Attributes found", "" + self.res_data["Attributes"] = OrderedDict() for key in patch_data["Attributes"].keys(): + print("Check key in patch_data:{}".format(key)) # verify client didn't send us a property we cant patch - if key not in self.res_data["Attributes"]: + if key not in self.parent.res_data["Attributes"]: + print("Invalid Patch Property Sent") return 4, 400, "Invalid Patch Property Sent", "" else: - self.parent.res_data["Attributes"][key] = patch_data["Attributes"][key] - return 0, 204, None, None + self.res_data["Attributes"][key] = patch_data["Attributes"][key] + resp = flask.Response(json.dumps(self.res_data,indent=4)) + return 0, 200, None, resp class RfPCIeDeviceCollection(RfCollection): @@ -196,3 +219,51 @@ class RfUSBPortCollection(RfCollection): class RfUSBPort(RfResource): pass + +class RfBootOptionCollection(RfCollection): + def final_init_processing(self, base_path, rel_path): + self.maxIdx = 0 + self.bootOptions = {} + + def element_type(self): + return RfBootOption + + def post_resource(self, post_data): + print("Members@odata.count:{}".format(self.res_data["Members@odata.count"])) + print("Members:{}".format(self.res_data["Members"])) + print("post_data:{}".format(post_data)) + + self.res_data["Members@odata.count"] = self.res_data["Members@odata.count"] + 1 + self.maxIdx = self.maxIdx + 1 + newBootOptIdx = self.maxIdx + newBootOptUrl = self.res_data["@odata.id"] + "/" + str(newBootOptIdx) + self.res_data["Members"].append({"@odata.id":newBootOptUrl}) + + post_data["@odata.id"] = newBootOptUrl + self.bootOptions[str(newBootOptIdx)] = post_data + + resp = flask.Response(json.dumps(post_data,indent=4)) + resp.headers["Location"] = newBootOptUrl + return 0, 200, None, resp + + def patch_bootOpt(self, Idx, patch_data): + self.bootOptions[str(Idx)] = {**self.bootOptions[str(Idx)], **patch_data} + resp = flask.Response(json.dumps(self.bootOptions[str(Idx)],indent=4)) + return 0, 200, None, resp + + def get_bootOpt(self, Idx): + return json.dumps(self.bootOptions[Idx],indent=4) + + def delete_bootOpt(self, Idx): + print("in delete_bootOpt") + + resp = flask.Response(json.dumps(self.bootOptions[Idx],indent=4)) + + self.bootOptions.pop(Idx) + self.res_data["Members@odata.count"] = self.res_data["Members@odata.count"] - 1 + + bootOptUrl = self.res_data["@odata.id"] + "/" + str(Idx) + self.res_data["Members"].remove({"@odata.id":bootOptUrl}) + return 0, 200, None, resp + +class RfBootOption(RfResource): -- 2.17.1