From nobody Mon Feb 9 10:48:30 2026 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of groups.io designates 66.175.222.108 as permitted sender) client-ip=66.175.222.108; envelope-from=bounce+27952+78064+1787277+3901457@groups.io; helo=mail02.groups.io; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of groups.io designates 66.175.222.108 as permitted sender) smtp.mailfrom=bounce+27952+78064+1787277+3901457@groups.io; dmarc=fail(p=none dis=none) header.from=hpe.com ARC-Seal: i=1; a=rsa-sha256; t=1626937416; cv=none; d=zohomail.com; s=zohoarc; b=c83IjtvqQ0AXhhE/6nxNMDQ5l8ceKfVPj01DrMzBVmXkFFFd3w1UnL6Y2xbS5BraIk0s+WHxz6uBshV6xYB5WylnVILZ6E6DiFIf4q8pIvYlnkrrTPd0j6sNMmpecVdB6F0GeiMbKqYiVxiRdEa4/8Y7Z+ztXUoJ9sAfm9Ux+Lw= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1626937416; h=Cc:Date:From:In-Reply-To:List-Subscribe:List-Id:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Reply-To:References:Sender:Subject:To; bh=kahZ4wmytYMFwzGeuTBda5THfe/O6+Aaq8AQnWW7UoQ=; b=kxlH11Vf1f0bU9ZRGHy3Db2zWTVWOwF8GWkWxOxbJ8XqO32W5Yo1XS0Kz4UgARWNKfiIBD+Z8ULLQz9svyjGTA4M9t8XDZeG14zZqd9xJLmRCBO40nfX+RvhlzCLIpZbW01UHoOsaDYcb4nezjeTyvgP8334dVXv0wVyuL0tTyA= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of groups.io designates 66.175.222.108 as permitted sender) smtp.mailfrom=bounce+27952+78064+1787277+3901457@groups.io; dmarc=fail header.from= (p=none dis=none) Received: from mail02.groups.io (mail02.groups.io [66.175.222.108]) by mx.zohomail.com with SMTPS id 1626937416124490.8925478534153; Thu, 22 Jul 2021 00:03:36 -0700 (PDT) Return-Path: X-Received: by 127.0.0.2 with SMTP id K8uIYY1788612xLNOzqhlO9I; Thu, 22 Jul 2021 00:03:35 -0700 X-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 X-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 X-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 X-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) X-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: [edk2-devel] [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> 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 Precedence: Bulk List-Unsubscribe: List-Subscribe: List-Help: Sender: devel@edk2.groups.io List-Id: Mailing-List: list devel@edk2.groups.io; contact devel+owner@edk2.groups.io Reply-To: devel@edk2.groups.io,abner.chang@hpe.com X-Gm-Message-State: zMmrFwvhrdP500KqN0n8Yrlbx1787277AA= DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=groups.io; q=dns/txt; s=20140610; t=1626937415; bh=0HvxIJGkIw7xbrDoz0j+cqHDEFAvO8CenG3D4BST9NQ=; h=Cc:Date:From:Reply-To:Subject:To; b=uXYsPBKAlfQB5yKv0qdH/fQJw3d1rzTNQAMP4qRL8otbTHhFmL8MUVvKmDgAK5CTfrn epKZ+4MPiSBw1byBnNUcCsTKou8VeZHAnvyG7w/SvXTEZi6QX4drHZoOUTS6fXPY81lAc R2xzYt9OGND16YH+YsEmkV3Wg8T2FOEM3RQ= X-ZohoMail-DKIM: pass (identity @groups.io) X-ZM-MESSAGEID: 1626937418298100010 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" - 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 Reviewed-by: Nickle Wang --- .../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/redfishProfil= eSimulator.py b/RedfishClientPkg/Tools/Redfish-Profile-Simulator/redfishPro= fileSimulator.py index 24be52bafc..91c792a2b7 100644 --- a/RedfishClientPkg/Tools/Redfish-Profile-Simulator/redfishProfileSimula= tor.py +++ b/RedfishClientPkg/Tools/Redfish-Profile-Simulator/redfishProfileSimula= tor.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 reserv= ed. # License: BSD 3-Clause License. For full text see link: https://github.co= m/DMTF/Redfish-Profile-Simulator/blob/master/LICENSE.md =20 @@ -9,13 +14,16 @@ import sys import getopt import os +import functools +import flask +import werkzeug =20 rfVersion =3D "0.9.6" rfProgram1 =3D "redfishProfileSimulator" rfProgram2 =3D " " rfUsage1 =3D "[-Vh] [--Version][--help]" -rfUsage2 =3D "[-H] [-P] [-p]" -rfUsage3 =3D "[--Host=3D] [--Port=3D] [--profile_path=3D]" +rfUsage2 =3D "[-H] [-P] [-C] [-K] [-p]" +rfUsage3 =3D "[--Host=3D] [--Port=3D] [--Cert=3D] [--K= ey=3D] [--profile_path=3D]" =20 =20 def rf_usage(): @@ -27,18 +35,19 @@ def rf_usage(): =20 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 dy= namic 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 Re= dfish service.") print(" Both Basic and Redfish Session/Token authentication is sup= ported (for a single user/passwd and token") print(" the user/passwd is: root/password123456. The authT= oken 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 al= l 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 dictionari= es are updated with patches, posts, deletes.") print(" The program can be extended to support other mockup \"prof= iles\".") 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=3D --host=3D") + print(" By default, the simulation runs over http, on localhost (1= 27.0.0.1), on port 5000.") + print(" These can be changed with CLI options: -P -C -= K -H | --port=3D --Cert=3D --Key=3D --host= =3D") + print(" -C -K | --Cert=3D --Key=3D options m= ust 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, --- he= lp") print(" -H, --Host=3D --- = host IP address. dflt=3D127.0.0.1") print(" -P, --Port=3D --- = the port to use. dflt=3D5000") + print(" -C, --Cert=3D --- = Server certificate.") + print(" -K, --Key=3D --- = Server key.") print(" -p, --profile=3D --- = the path to the Redfish profile to use. " "dflt=3D\"./MockupData/SimpleOcpServerV1\" ") =20 +# 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 =3D True + return func(*args, **kwargs) + return wrapper + +class NotModified(werkzeug.exceptions.HTTPException): + code =3D 304 + def get_response(self, environment): + return flask.Response(status=3D304) + +class PreconditionRequired(werkzeug.exceptions.HTTPException): + code =3D 428 + description =3D ('

This request is required to be ' + 'conditional; try using "If-Match".') + name =3D 'Precondition Required' + def get_response(self, environment): + resp =3D super(PreconditionRequired, + self).get_response(environment) + resp.status =3D str(self.code) + ' ' + self.name.upper() + return resp =20 def main(argv): + #Monkey patch the set_etag() method for conditional request. + _old_set_etag =3D werkzeug.ETagResponseMixin.set_etag + @functools.wraps(werkzeug.ETagResponseMixin.set_etag) + def _new_set_etag(self, etag, weak=3DFalse): + # 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 =3D=3D 'GET' and + flask.request.if_none_match and + etag in flask.request.if_none_match): + raise NotModified + flask.g.condtnl_etags_start =3D False + _old_set_etag(self, etag, weak) + werkzeug.ETagResponseMixin.set_etag =3D _new_set_etag + # set default option args rf_profile_path =3D os.path.abspath("./MockupData/SimpleOcpServerV1") - rf_host =3D "127.0.0.1" + rf_host =3D "0.0.0.0" rf_port =3D 5000 + rf_cert =3D"" + rf_key=3D"" =20 try: - opts, args =3D getopt.getopt(argv[1:], "VhH:P:p:", - ["Version", "help", "Host=3D", "Port=3D= ", "profile=3D"]) + opts, args =3D getopt.getopt(argv[1:], "VhH:P:C:K:p:", + ["Version", "help", "Host=3D", "Port=3D= ", "Cert=3D", "Key=3D", "profile=3D"]) except getopt.GetoptError: print(rfProgram1, ": Error parsing options") rf_usage() @@ -77,11 +136,24 @@ def main(argv): rf_host =3D arg elif opt in "--Port=3D": rf_port=3Dint(arg) + elif opt in "--Cert=3D": + rf_cert=3Darg + elif opt in "--Key=3D": + rf_key=3Darg else: print(" ", rfProgram1, ": Error: unsupported option") rf_usage() sys.exit(2) =20 + if rf_port =3D=3D 443: + if rf_cert =3D=3D "" or rf_key =3D=3D "": + print(" ", rfProgram1, ": Error: port 443 must be used toget= her with -C and -K to enable https session") + sys.exit(2) + else: + if rf_cert !=3D "" or rf_key !=3D "": + print(" ", rfProgram1, ": Error: -C and -K option= s must be used together with port 443 to enable https session") + sys.exit(2) + print("{} Version: {}".format(rfProgram1,rfVersion)) print(" Starting redfishProfileSimulator at: hostIP=3D{}, port=3D{= }".format(rf_host, rf_port)) print(" Using Profile at {}".format(rf_profile_path)) @@ -102,7 +174,7 @@ def main(argv): root =3D RfServiceRoot(rf_profile_path, root_path) =20 # start the flask REST API service - rfApi_SimpleServer(root, versions, host=3Drf_host, port=3Drf_port) + rfApi_SimpleServer(root, versions, host=3Drf_host, port=3Drf_port,= cert=3Drf_cert, key=3Drf_key) else: print("invalid profile path") =20 diff --git a/RedfishClientPkg/Tools/Redfish-Profile-Simulator/v1sim/redfish= URIs.py b/RedfishClientPkg/Tools/Redfish-Profile-Simulator/v1sim/redfishURI= s.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 reserv= ed. # License: BSD 3-Clause License. For full text see link: https://github.co= m/DMTF/Redfish-Profile-Simulator/blob/master/LICENSE.md =20 import json +from collections import OrderedDict =20 from flask import Flask from flask import request =20 from .flask_redfish_auth import RfHTTPBasicOrTokenAuth -from .resource import RfResource, RfResourceRaw, RfCollection =20 +from werkzeug.serving import WSGIRequestHandler =20 -def rfApi_SimpleServer(root, versions, host=3D"127.0.0.1", port=3D5000): +def rfApi_SimpleServer(root, versions, host=3D"127.0.0.1", port=3D5000, ce= rt=3D"", key=3D""): app =3D Flask(__name__) =20 # create auth class that does basic or redifish session auth @@ -21,8 +27,8 @@ def rfApi_SimpleServer(root, versions, host=3D"127.0.0.1"= , port=3D5000): # for basic auth, we only support user=3Dcatfish, passwd=3Dhunter @auth.verify_basic_password def verify_rf_passwd(user, passwd): - if user =3D=3D "root": - if passwd =3D=3D "password123456": + if user =3D=3D "admin": + if passwd =3D=3D "pwd123456": return True return False =20 @@ -43,13 +49,13 @@ def rfApi_SimpleServer(root, versions, host=3D"127.0.0.= 1", port=3D5000): =20 # GET /redfish @app.route("/redfish", methods=3D['GET']) - @app.route("/redfish/", methods=3D['GET']) + #@app.route("/redfish/", methods=3D['GET']) def rf_versions(): return versions.get_resource() =20 # GET /redfish/v1 @app.route("/redfish/v1", methods=3D['GET']) - @app.route("/redfish/v1/", methods=3D['GET']) + #@app.route("/redfish/v1/", methods=3D['GET']) def rf_service_root(): return root.get_resource() =20 @@ -65,8 +71,9 @@ def rfApi_SimpleServer(root, versions, host=3D"127.0.0.1"= , port=3D5000): return resolve_path(root, rf_path) =20 @app.route("/redfish/v1/", methods=3D['GET']) - @app.route("/redfish/v1//", methods=3D['GET']) + #@app.route("/redfish/v1//", methods=3D['GET']) @auth.rfAuthRequired + @conditional def rf_subsystems(rf_path): return resolve_path(root, rf_path) =20 @@ -78,135 +85,189 @@ def rfApi_SimpleServer(root, versions, host=3D"127.0.= 0.1", port=3D5000): return root.get_resource() =20 @app.route("/redfish/v1/Systems/", methods=3D['PATCH']) - @app.route("/redfish/v1/Systems//", methods=3D['PATCH']) + #@app.route("/redfish/v1/Systems//", methods=3D['PATCH'= ]) @auth.rfAuthRequired def rf_computer_systempatch(sys_path): - rdata =3D request.get_json(cache=3DTrue) + rdata =3D json.loads(request.data,object_pairs_hook=3DOrderedDict) print("rdata:{}".format(rdata)) - obj =3D patch_path(root.systems, sys_path) + obj =3D patch_path(root.components['Systems'], sys_path) rc, status_code, err_string, resp =3D obj.patch_resource(rdata) if rc =3D=3D 0: - return "", status_code + return resp, status_code + else: + return err_string, status_code + + @app.route("/redfish/v1/Systems//BootOptions/", methods=3D['GET']) + @auth.rfAuthRequired + def rf_computer_bootoptions_get(system_id, bootOptIdx): + return root.components['Systems'].get_element(system_id).component= s['BootOptions'].get_bootOpt(bootOptIdx) + + @app.route("/redfish/v1/Systems//BootOptions/", methods=3D['DELETE']) + @auth.rfAuthRequired + def rf_computer_bootoptions_del(system_id, bootOptIdx): + print("in rf_computer_bootoptions_del") + rc, status_code, err_string, resp =3D root.components['Systems'].= get_element(system_id).components['BootOptions'].delete_bootOpt(bootOptIdx) + if rc =3D=3D 0: + return resp, status_code + else: + return err_string, status_code + + @app.route("/redfish/v1/Systems//BootOptions/", methods=3D['PATCH']) + @auth.rfAuthRequired + def rf_computer_bootoption_patch(system_id, bootOptIdx): + print ("in POST boot options") + rdata =3D json.loads(request.data,object_pairs_hook=3DOrderedDict) + print("rdata:{}".format(rdata)) + rc, status_code, err_string, resp =3D root.components['Systems'].g= et_element(system_id).components['BootOptions'].patch_bootOpt(bootOptIdx, r= data) + if rc =3D=3D 0: + return resp, status_code + else: + return err_string, status_code + + @app.route("/redfish/v1/Systems//BootOptions", metho= ds=3D['POST']) + @auth.rfAuthRequired + def rf_computer_bootoptions_post(system_id): + print ("in POST boot options") + rdata =3D json.loads(request.data,object_pairs_hook=3DOrderedDict) + print("rdata:{}".format(rdata)) + rc, status_code, err_string, resp =3D root.components['Systems'].g= et_element(system_id).components['BootOptions'].post_resource(rdata) + if rc =3D=3D 0: + return resp, status_code else: return err_string, status_code =20 @app.route("/redfish/v1/Systems//Actions/ComputerSys= tem.Reset", methods=3D['POST']) - @app.route("/redfish/v1/Systems//Actions/ComputerSys= tem.Reset/", methods=3D['POST']) + #@app.route("/redfish/v1/Systems//Actions/ComputerSy= stem.Reset/", methods=3D['POST']) @auth.rfAuthRequired def rf_computer_systemreset(system_id): # print("in reset") - rdata =3D request.get_json(cache=3DTrue) + rdata =3D json.loads(request.data,object_pairs_hook=3DOrderedDict) # print("rdata:{}".format(rdata)) rc, status_code, err_string, resp =3D root.components['Systems'].g= et_element(system_id).reset_resource(rdata) if rc =3D=3D 0: - return "", status_code + return resp, status_code else: return err_string, status_code =20 - @app.route("/redfish/v1/Systems//bios/Actions/Bios.R= esetBios", methods=3D['POST']) - @app.route("/redfish/v1/Systems//bios/Actions/Bios.R= esetBios/", methods=3D['POST']) + @app.route("/redfish/v1/Systems//Bios/Actions/Bios.R= esetBios", methods=3D['POST']) + #@app.route("/redfish/v1/Systems//Bios/Actions/Bios.= ResetBios/", methods=3D['POST']) @auth.rfAuthRequired def rf_computer_biosreset(system_id): # print("in reset") - rdata =3D request.get_json(cache=3DTrue) + rdata =3D json.loads(request.data,object_pairs_hook=3DOrderedDict) # print("rdata:{}".format(rdata)) system =3D root.systems.get_element(system_id) - bios =3D system.get_component("bios") + bios =3D system.get_component("Bios") rc, status_code, err_string, resp =3D bios.reset_resource(rdata) if rc =3D=3D 0: - return "", status_code + return resp, status_code else: return err_string, status_code =20 - @app.route("/redfish/v1/Systems//bios/Actions/Bios.C= hangePassword", methods=3D['PATCH']) - @app.route("/redfish/v1/Systems//bios/Actions/Bios.C= hangePassword/", methods=3D['PATCH']) + @app.route("/redfish/v1/Systems//Bios/Actions/Bios.C= hangePassword", methods=3D['PATCH']) + #@app.route("/redfish/v1/Systems//Bios/Actions/Bios.= ChangePassword/", methods=3D['PATCH']) @auth.rfAuthRequired def rf_computer_change_pswd(system_id): # print("in reset") - rdata =3D request.get_json(cache=3DTrue) + rdata =3D json.loads(request.data,object_pairs_hook=3DOrderedDict) # print("rdata:{}".format(rdata)) system =3D root.systems.get_element(system_id) - bios =3D system.get_component("bios") + bios =3D system.get_component("Bios") rc, status_code, err_string, resp =3D bios.change_password(rdata) if rc =3D=3D 0: - return "", status_code + return resp, status_code else: return err_string, status_code =20 @app.route("/redfish/v1/Chassis//Actions/Chassis.Re= set", methods=3D['POST']) - @app.route("/redfish/v1/Chassis//Actions/Chassis.Re= set/", methods=3D['POST']) + #@app.route("/redfish/v1/Chassis//Actions/Chassis.R= eset/", methods=3D['POST']) @auth.rfAuthRequired def rf_computer_chassisreset(chassis_id): # print("in reset") - rdata =3D request.get_json(cache=3DTrue) + rdata =3D json.loads(request.data,object_pairs_hook=3DOrderedDict) # print("rdata:{}".format(rdata)) rc, status_code, err_string, resp =3D root.chassis.get_element(cha= ssis_id).reset_resource(rdata) if rc =3D=3D 0: - return "", status_code + return resp, status_code else: return err_string, status_code =20 @app.route("/redfish/v1/Chassis//Power", methods=3D= ['PATCH']) - @app.route("/redfish/v1/Chassis//Power/", methods= =3D['PATCH']) + #@app.route("/redfish/v1/Chassis//Power/", methods= =3D['PATCH']) @auth.rfAuthRequired def rf_chassis_powerpatch(chassis_id): # rawdata=3Drequest.data - rdata =3D request.get_json(cache=3DTrue) + rdata =3D json.loads(request.data,object_pairs_hook=3DOrderedDict) # print("RRrdata:{}".format(rdata)) rc, status_code, err_string, resp =3D root.chassis.get_element(cha= ssis_id).power.patch_resource(rdata) + if rc =3D=3D 0: + return resp, status_code + else: + return err_string, status_code + + @app.route("/redfish/v1/Registries/", methods=3D['PATCH= ']) + @auth.rfAuthRequired + def rf_registries_patch(sys_path): + rdata =3D json.loads(request.data,object_pairs_hook=3DOrderedDict) + print("rdata:{}".format(rdata)) + obj =3D patch_path(root.components['Registries'], sys_path) + rc, status_code, err_string, resp =3D obj.patch_resource(rdata) if rc =3D=3D 0: return "", status_code else: return err_string, status_code =20 @app.route("/redfish/v1/Managers/", methods=3D['PAT= CH']) - @app.route("/redfish/v1/Managers//", methods=3D['PA= TCH']) + #@app.route("/redfish/v1/Managers//", methods=3D['P= ATCH']) @auth.rfAuthRequired def rf_patch_manager_entity(manager_id): - rdata =3D request.get_json(cache=3DTrue) + rdata =3D json.loads(request.data,object_pairs_hook=3DOrderedDict) # print("RRrdata:{}".format(rdata)) rc, status_code, err_string, resp =3D root.managers.get_element(ma= nager_id).patch_resource(rdata) if rc =3D=3D 0: - return "", status_code + return resp, status_code else: return err_string, status_code =20 # rest/v1/Managers/1 @app.route("/redfish/v1/Managers//Actions/Manager.R= eset", methods=3D['POST']) - @app.route("/redfish/v1/Managers//Actions/Manager.R= eset/", methods=3D['POST']) + #@app.route("/redfish/v1/Managers//Actions/Manager.= Reset/", methods=3D['POST']) @auth.rfAuthRequired def rf_reset_manager(manager_id): - rdata =3D request.get_json(cache=3DTrue) + rdata =3D json.loads(request.data,object_pairs_hook=3DOrderedDict) # print("rdata:{}".format(rdata)) rc, status_code, err_string, resp =3D root.managers.get_element(ma= nager_id).reset_resource(rdata) if rc =3D=3D 0: - return "", status_code + return resp, status_code else: return err_string, status_code =20 @app.route("/redfish/v1/Managers//EthernetInterface= s/", methods=3D['PATCH']) - @app.route("/redfish/v1/Managers//EthernetInterface= s//", methods=3D['PATCH']) + #@app.route("/redfish/v1/Managers//EthernetInterfac= es//", methods=3D['PATCH']) @auth.rfAuthRequired def rf_patch_manager_nic_entity(manager_id, eth_id): resp =3D root.managers.get_element(manager_id).ethernetColl.get_in= terface(eth_id).get_resource() - rdata =3D request.get_json(cache=3DTrue) + rdata =3D json.loads(request.data,object_pairs_hook=3DOrderedDict) # print("RRrdata:{}".format(rdata)) ethernet_coll =3D root.managers.get_element(manager_id).ethernetCo= ll rc, status_code, err_string, resp =3D ethernet_coll.get_interface(= eth_id).patch_resource(rdata) if rc =3D=3D 0: - return "", status_code + return resp, status_code else: return err_string, status_code =20 + @app.route("/redfish/v1/SessionService", methods=3D['GET']) + def rf_get_session_service(): + return root.components['SessionService'].get_resource() + @app.route("/redfish/v1/SessionService", methods=3D['PATCH']) - @app.route("/redfish/v1/SessionService/", methods=3D['PATCH']) - @auth.rfAuthRequired + #@app.route("/redfish/v1/SessionService/", methods=3D['PATCH']) def rf_patch_session_service(): - rdata =3D request.get_json(cache=3DTrue) + rdata =3D json.loads(request.data,object_pairs_hook=3DOrderedDict) # print("RRrdata:{}".format(rdata)) rc, status_code, err_string, resp =3D root.sessionService.patch_re= source(rdata) if rc =3D=3D 0: - return "", status_code + return resp, status_code else: return err_string, status_code =20 @@ -215,7 +276,7 @@ def rfApi_SimpleServer(root, versions, host=3D"127.0.0.= 1", port=3D5000): @app.route("/redfish/v1/SessionService/Sessions", methods=3D['POST']) def rf_login(): print("login") - rdata =3D request.get_json(cache=3DTrue) + rdata =3D json.loads(request.data,object_pairs_hook=3DOrderedDict) print("rdata:{}".format(rdata)) if rdata["UserName"] =3D=3D "root" and rdata["Password"] =3D=3D "p= assword123456": x =3D {"Id": "SESSION123456"} @@ -233,17 +294,17 @@ def rfApi_SimpleServer(root, versions, host=3D"127.0.= 0.1", port=3D5000): @auth.rfAuthRequired def rf_session_logout(session_id): print("session logout %s" % session_id) - # rdata=3Drequest.get_json(cache=3DTrue) + # rdata =3D json.loads(request.data,object_pairs_hook=3DOrderedDic= t) # print("rdata:{}".format(rdata)) return "", 204 =20 @app.route("/redfish/v1/AccountService", methods=3D['PATCH']) @auth.rfAuthRequired def rf_patch_account_service(): - rdata =3D request.get_json(cache=3DTrue) + rdata =3D json.loads(request.data,object_pairs_hook=3DOrderedDict) rc, status_code, err_string, resp =3D root.accountService.patch_re= source(rdata) if rc =3D=3D 0: - return "", status_code + return resp, status_code else: return err_string, status_code =20 @@ -293,12 +354,14 @@ def rfApi_SimpleServer(root, versions, host=3D"127.0.= 0.1", port=3D5000): ''' =20 # END file redfishURIs - # start Flask REST engine running - app.run(host=3Dhost, port=3Dport) =20 - # never returns + if key !=3D "" and cert !=3D "": + app.run(host=3Dhost, port=3Dport, ssl_context=3D(cert, key)) + else: + app.run(host=3Dhost, port=3Dport) =20 + # never returns =20 ''' reference source links: diff --git a/RedfishClientPkg/Tools/Redfish-Profile-Simulator/v1sim/registr= y.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/resourc= e.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 reserv= ed. # License: BSD 3-Clause License. For full text see link: https://github.co= m/DMTF/Redfish-Profile-Simulator/blob/master/LICENSE.md +# =20 import json import os @@ -23,7 +30,7 @@ class RfResource: if os.path.exists(indx_file_path): res_file =3D open(indx_file_path, "r") res_rawdata =3D res_file.read() - self.res_data =3D json.loads(res_rawdata) + self.res_data =3D json.loads(res_rawdata,object_pairs_hook=3DO= rderedDict) self.create_sub_objects(base_path, rel_path) self.final_init_processing(base_path, rel_path) else: @@ -36,7 +43,15 @@ class RfResource: pass =20 def get_resource(self): - return flask.jsonify(self.res_data) + self.response=3Djson.dumps(self.res_data,indent=3D4) + try: + # SHA1 should generate well-behaved etags + response =3D flask.make_response(self.response) + etag =3D hashlib.sha1(self.response.encode('utf-8')).hexdigest= () + response.set_etag(etag) + return response + except KeyError: + flask.abort(404) =20 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) =20 + resp =3D flask.Response(json.dumps(self.res_data,indent=3D4)) + return 0, 200, None, resp + + def post_resource(self, post_data): + pass + + def delete_resource(self): + pass =20 class RfResourceRaw: def __init__(self, base_path, rel_path, parent=3DNone): 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 reserv= ed. # License: BSD 3-Clause License. For full text see link: https://github.co= m/DMTF/Redfish-Profile-Simulator/blob/master/LICENSE.md +# =20 import os =20 @@ -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 =20 class RfSystemsCollection(RfCollection): def element_type(self): @@ -48,15 +57,17 @@ class RfSystemObj(RfResource): self.components[item] =3D RfUSBDeviceCollection(base_path,= os.path.join(rel_path, item), parent=3Dself) elif item =3D=3D "USBPorts": self.components[item] =3D RfUSBPortCollection(base_path, o= s.path.join(rel_path, item), parent=3Dself) + elif item =3D=3D "BootOptions": + self.components[item] =3D RfBootOptionCollection(base_path= , os.path.join(rel_path, item), parent=3Dself) =20 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 !=3D "AssetTag" and key !=3D "IndicatorLED" and key != =3D "Boot": + if key !=3D "AssetTag" and key !=3D "IndicatorLED" and key != =3D "Boot" and key !=3D "BiosVersion": return 4, 400, "Invalid Patch Property Sent", "" elif key =3D=3D "Boot": for prop2 in patch_data["Boot"].keys(): - if prop2 !=3D "BootSourceOverrideEnabled" and prop2 != =3D "BootSourceOverrideTarget": + if prop2 !=3D "BootSourceOverrideEnabled" and prop2 != =3D "BootSourceOverrideTarget" and prop2 !=3D "BootNext" and prop2 !=3D "Bo= otOrder": 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'] =3D patch_data['AssetTag'] if "IndicatorLED" in patch_data: self.res_data['IndicatorLED'] =3D patch_data['IndicatorLED'] + if "BiosVersion" in patch_data: + self.res_data['BiosVersion'] =3D patch_data['BiosVersion'] if "Boot" in patch_data: boot_data =3D patch_data["Boot"] if "BootSourceOverrideEnabled" in boot_data: @@ -80,7 +93,13 @@ class RfSystemObj(RfResource): self.res_data['Boot']['BootSourceOverrideTarget'] =3D = value else: return 4, 400, "Invalid_Value_Specified: BootSourceOve= rrideTarget", "" - return 0, 204, None, None + if "BootNext" in boot_data: + self.res_data['Boot']['BootNext'] =3D boot_data['BootNext'] + if "BootOrder" in boot_data: + self.res_data['Boot']['BootOrder'] =3D boot_data['BootOrde= r'] + + resp =3D flask.Response(json.dumps(self.res_data,indent=3D4)) + return 0, 200, None, resp =20 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"] =3D 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] =3D patch_data["At= tributes"][key] - return 0, 204, None, None + self.res_data["Attributes"][key] =3D patch_data["Attribute= s"][key] + resp =3D flask.Response(json.dumps(self.res_data,indent=3D4)) + return 0, 200, None, resp =20 =20 class RfPCIeDeviceCollection(RfCollection): @@ -196,3 +219,51 @@ class RfUSBPortCollection(RfCollection): =20 class RfUSBPort(RfResource): pass + +class RfBootOptionCollection(RfCollection): + def final_init_processing(self, base_path, rel_path): + self.maxIdx =3D 0 + self.bootOptions =3D {} + + 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"] =3D self.res_data["Members@od= ata.count"] + 1 + self.maxIdx =3D self.maxIdx + 1 + newBootOptIdx =3D self.maxIdx + newBootOptUrl =3D self.res_data["@odata.id"] + "/" + str(newBootOp= tIdx) + self.res_data["Members"].append({"@odata.id":newBootOptUrl}) + + post_data["@odata.id"] =3D newBootOptUrl + self.bootOptions[str(newBootOptIdx)] =3D post_data + + resp =3D flask.Response(json.dumps(post_data,indent=3D4)) + resp.headers["Location"] =3D newBootOptUrl + return 0, 200, None, resp + + def patch_bootOpt(self, Idx, patch_data): + self.bootOptions[str(Idx)] =3D {**self.bootOptions[str(Idx)], **pa= tch_data} + resp =3D flask.Response(json.dumps(self.bootOptions[str(Idx)],inde= nt=3D4)) + return 0, 200, None, resp + + def get_bootOpt(self, Idx): + return json.dumps(self.bootOptions[Idx],indent=3D4) + + def delete_bootOpt(self, Idx): + print("in delete_bootOpt") + + resp =3D flask.Response(json.dumps(self.bootOptions[Idx],indent=3D= 4)) + + self.bootOptions.pop(Idx) + self.res_data["Members@odata.count"] =3D self.res_data["Members@od= ata.count"] - 1 + + bootOptUrl =3D self.res_data["@odata.id"] + "/" + str(Idx) + self.res_data["Members"].remove({"@odata.id":bootOptUrl}) + return 0, 200, None, resp + +class RfBootOption(RfResource): --=20 2.17.1 -=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D- Groups.io Links: You receive all messages sent to this group. View/Reply Online (#78064): https://edk2.groups.io/g/devel/message/78064 Mute This Topic: https://groups.io/mt/84374362/1787277 Group Owner: devel+owner@edk2.groups.io Unsubscribe: https://edk2.groups.io/g/devel/unsub [importer@patchew.org] -=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-