From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mx0a-002e3701.pphosted.com (mx0a-002e3701.pphosted.com [148.163.147.86]) by mx.groups.io with SMTP id smtpd.web11.9579.1628428157909633255 for ; Sun, 08 Aug 2021 06:09:17 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@hpe.com header.s=pps0720 header.b=kDvtouQ/; 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.147.86, mailfrom: prvs=085446bcae=nickle.wang@hpe.com) Received: from pps.filterd (m0150241.ppops.net [127.0.0.1]) by mx0a-002e3701.pphosted.com (8.16.0.43/8.16.0.43) with SMTP id 178D8m5s007787; Sun, 8 Aug 2021 13:09:11 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=hpe.com; h=from : to : cc : subject : date : message-id : references : in-reply-to : content-type : content-transfer-encoding : mime-version; s=pps0720; bh=hfyRbIMakq/v1RWf+UmYBe5CwGniuOmo9DC2pxFU2SM=; b=kDvtouQ/145ZNwZCumXwsV8+rZvrQY031Ej6XQrFqAJXjlAPQwc/whEB3qvitLc2+TaC sM77CUY2A33qyY7MPURMADqPbJDSdqrFc1mnJ0WUagnFUm+XGgT6tIVFSecOGIvuCdJ5 kNY9dL2H/bK70K+QPTW6EMk5cqbjTc+v1Qul2ITTpp0Dx2x1GYQrIGua7/ElHBf4PiIa iYe9Y5MNPHfiGLXINliL2qBGlSVCVdF38IF1uWF8SVKt+c0L/EbXnhOLb/kJ8KuLzfbb Dg4gXWdY5FIUQhb0JA7oF3Es7bfktJLF9GjG0bZWSZmGMcxCaY4ko91PjBApMfLEKzgD mg== Received: from g2t2354.austin.hpe.com (g2t2354.austin.hpe.com [15.233.44.27]) by mx0a-002e3701.pphosted.com with ESMTP id 3a9gqr7eps-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Sun, 08 Aug 2021 13:09:11 +0000 Received: from G4W9119.americas.hpqcorp.net (exchangepmrr1.us.hpecorp.net [16.210.20.214]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by g2t2354.austin.hpe.com (Postfix) with ESMTPS id 3E38383; Sun, 8 Aug 2021 13:09:10 +0000 (UTC) Received: from G9W8453.americas.hpqcorp.net (2002:10d8:a0d3::10d8:a0d3) by G4W9119.americas.hpqcorp.net (2002:10d2:14d6::10d2:14d6) with Microsoft SMTP Server (TLS) id 15.0.1497.18; Sun, 8 Aug 2021 13:09:09 +0000 Received: from NAM12-BN8-obe.outbound.protection.outlook.com (15.241.52.13) by G9W8453.americas.hpqcorp.net (16.216.160.211) with Microsoft SMTP Server (TLS) id 15.0.1497.18 via Frontend Transport; Sun, 8 Aug 2021 13:09:09 +0000 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=k9KR7VAR8jec2hCWbpenI2I2RXLWbKEuG//Uqvd7Iss7ynrBNbpsW+1jKKExSvbrSOsbet4zyFnahTquiT0PZ5B0C1Aqk7jaDZMYCNUouzdJHhzAz6P48vueFSDO890sU2Z00CSdozh1DR8O6le16wZdF0A/zjpZR7+HrGYg2kAoE0qf5UlQnw1hy1cCLF14MYRH3Ge+NRQaHD7GPKnwH3wNF2UmA+H7Rft66m93WEqeRpuJhUA43ONfUvoY9KjUumRew7wXZwvdNX6oHP5k5PgtXEzqtT8kER7jqEs/qB5Yi12TdX2XfWUTpqTiPrdxAwA8f9UfEy7FyZF7mjjEGg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=43YmrH/DG0rjWXtd0JYgu2/s29hpuB1Chf5iv2FPtIA=; b=N7F8wjBgn6pCayIe0ITond98ZWehEMSMcFtPfv1sbUquC0SuRSeJS+to1mbf6Id1MHqi4UVriZDQmVxt9U8hU35iVdxGOK85qtm2gtCCXx5IYzeMQiVp1EWpNrPEjXH3NMsjIT2F7CDvSVFYacdl6NQbW7pUr4LFmi6AyWkV8RNtDdJ7myxwG/uUafCVP556/AT3PBVLL9OO8tj/wAhNDVOny74HX8JHj/pbqibwPzerD6atIKw1UNWHd0pbjbywnQepACQhAWETjimINa3xFKI47MCTvT0ixKY7VWOyhoCGSCmou1cp/ueg6tgQD0H3glw17ZVYX6xCmyoQ0755Ww== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=hpe.com; dmarc=pass action=none header.from=hpe.com; dkim=pass header.d=hpe.com; arc=none Received: from DF4PR8401MB0812.NAMPRD84.PROD.OUTLOOK.COM (2a01:111:e400:760d::7) by DF4PR8401MB0666.NAMPRD84.PROD.OUTLOOK.COM (2a01:111:e400:7607::14) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.4394.19; Sun, 8 Aug 2021 13:09:05 +0000 Received: from DF4PR8401MB0812.NAMPRD84.PROD.OUTLOOK.COM ([fe80::44b6:ec1d:9c03:c520]) by DF4PR8401MB0812.NAMPRD84.PROD.OUTLOOK.COM ([fe80::44b6:ec1d:9c03:c520%5]) with mapi id 15.20.4394.022; Sun, 8 Aug 2021 13:09:05 +0000 From: "Nickle Wang" To: "Chang, Abner (HPS SW/FW Technologist)" , "devel@edk2.groups.io" CC: Liming Gao Subject: Re: [staging/edk2-redfish-client Tools PATCH 3/6] RedfishClientPkg/Redfish-Profile-Simulator: Add more features Thread-Topic: [staging/edk2-redfish-client Tools PATCH 3/6] RedfishClientPkg/Redfish-Profile-Simulator: Add more features Thread-Index: AQHXfse4/a0xTHgsGEim5kKPL8RaGatprrWA Date: Sun, 8 Aug 2021 13:09:04 +0000 Message-ID: References: <20210722060817.18564-1-abner.chang@hpe.com> <20210722060817.18564-4-abner.chang@hpe.com> In-Reply-To: <20210722060817.18564-4-abner.chang@hpe.com> Accept-Language: en-US, zh-TW X-MS-Has-Attach: X-MS-TNEF-Correlator: authentication-results: hpe.com; dkim=none (message not signed) header.d=none;hpe.com; dmarc=none action=none header.from=hpe.com; x-ms-publictraffictype: Email x-ms-office365-filtering-correlation-id: 97f6f482-814a-4d8c-6957-08d95a6db319 x-ms-traffictypediagnostic: DF4PR8401MB0666: x-ms-exchange-transport-forked: True x-microsoft-antispam-prvs: x-ms-oob-tlc-oobclassifiers: OLM:3173; x-ms-exchange-senderadcheck: 1 x-ms-exchange-antispam-relay: 0 x-microsoft-antispam: BCL:0; x-microsoft-antispam-message-info: sTFg+n3hxIR1vaA4PS5cnv+suGPUfbhaiI9Emf2ft6b4VxLZIjA9+mkj92n9e5XE0ETcO/fuovh6pKff86j69usHDSlRnCCphVe4eRFAZ0nfHtWVzVb4QxGzATrukkJyhfPlQon9x5qVYujTy+NyzdIoqKZ07hQrXTJ26chTm/Zcg2KWu2JHiYrU+o59lTOoxkyHoXCI58rsTgE6b4nlWevElYAmCl5UzM2wABI2gXhel8uiMCbkidy43+rR0LjAAMBKzFbqjOoHmY5kIBLeuxraeyKfQmILL+snayJ0eGzRNOrggV+aUdK3KPQx3q802QoHV8atXMpPhenQipViWXoUiKSpqfUrCdqoSutZ/msHRTQdykenonhcAeMEFfczVsRRojXySawLOODPeh1bG/FufE7bdgoFwfP7Vmb3lnHywYyg6PzCAdLQGyjXM9m68RFeqAWgrBqkG+QLF1lsuC0AvtC0ENbpYRy7McxTrpLZC8px2XhC49tb44GiJAXGKDk0ApFnNzqZCAxyJFsXYL+5ho3R8dV8y5OVfzj9LcnsWdPyDvoORznUX2VcZ5ZAWJMNuFyfwsAV7mVGWgXjtIVvTL3MKIZoK/WNXUdBk4NjfsmV9DKALxT32Z+4y5z/tWRzMcW94Xv9XpD4d1CpvxaCkIb4DojltrmHVgfMWOj1yM/uHJFOHYFhRLPePKthxyFnnP1q8H3w4mEUguZ8MRjz74dD4s7oNSLolQ4n+2AldGlJVyPAycufSYLtt6U84uuJcJly/ge70tOqRCZQQ7kXtr51QUBz1LftP1OALo5whWph7tDMljrkpdoSZ1Z8vCmSjLkIbCenOmZdKE7jTw== x-forefront-antispam-report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:DF4PR8401MB0812.NAMPRD84.PROD.OUTLOOK.COM;PTR:;CAT:NONE;SFS:(136003)(376002)(396003)(366004)(346002)(39860400002)(4326008)(7696005)(64756008)(122000001)(66556008)(66446008)(38070700005)(66476007)(52536014)(76116006)(2906002)(66946007)(478600001)(5660300002)(9686003)(38100700002)(53546011)(966005)(33656002)(8936002)(71200400001)(6506007)(186003)(30864003)(86362001)(110136005)(55016002)(316002)(83380400001)(8676002)(579004)(559001);DIR:OUT;SFP:1102; x-ms-exchange-antispam-messagedata-chunkcount: 1 x-ms-exchange-antispam-messagedata-0: =?us-ascii?Q?JVg5MqxNLcs3BEvLYT1nKmXg561LzIqJqKsVcQ3RudbipwqRiUuUz6K7mxoM?= =?us-ascii?Q?L5LOHmHRm/uGJQ8+nsppENZo2NpHo50uRt4L4mc9DSnSw3f7byb46CCKgdfO?= =?us-ascii?Q?HsB+MgoJb6PY5Jv1X2tYd5QvW7lRZ1r1sJ+UDeHAA2+cUIuAY8AiW0tJ8ls3?= =?us-ascii?Q?dZxLvKP3ACNWGeUaD96jf65TCPHMUf4lSSUGQmvcbVkQRfF8X7C0nZtw8UWt?= =?us-ascii?Q?LhTAAhr4D/23zCXVoFMaSK+LTqO7KF9O4Y5wFSmpDb6X8vmqpew3YP8/N8d9?= =?us-ascii?Q?TuKb1Ix9GI9QWXHS/8Z0+r3ePPZ0Xyiho9C3IQ8Lm4t2WIYsopu95e6PtrTb?= =?us-ascii?Q?mlYsazi3inwgEciEZRfieMCap+7juXcnP+CN9el+BXykXZQMNODL9TUp6MAO?= =?us-ascii?Q?CA4yjSGC1l9+mhQaB3wCp5d57T3OcVZarhiKh8gDc+QMvFnQzCet6HuSYBoh?= =?us-ascii?Q?3lUI2NUxR+H5vA+w4qzc0ItIvH+PvwTYN8PTQzohShvFoQyKRCjQ0qFZBKkf?= =?us-ascii?Q?dFjhVYIkXOp8IKrVaF/6v7EmQ/J7tuS/tSyO0jzeBks1R01AI/Zm2CyuYyMI?= =?us-ascii?Q?94IT+XpmdOf2uLzoHMseUVpknOWS314T5wJHL6vF0Quwx8mdvHfOG3tkZxTN?= =?us-ascii?Q?rUqAHibHuF88saTpCy9Lz2ZMRC8MS37eC5NLAzQfQglmOgAethY276QDN0CB?= =?us-ascii?Q?BGrhZjsUMzoNDasoV3Mfc/L/gVa0xhQpUNY6E+mGNduixm/Bsy+XPheGxp/r?= =?us-ascii?Q?BRKWHntUFojVwa10LovqZbo5aDzdxEkAO87XV8xbnW1iDNbQZ4j77blchOIw?= =?us-ascii?Q?/dcsSxh211fXqcMl4Hfrx6r1+VXTSGgu6cxs3PUoZUm0aH9NeTLaADZzk+H/?= =?us-ascii?Q?PDTQ0Blg1rI/HWKU8j8KisgHHA6zHLGXQ9qPdEfbPYN62VKwENzNHColKFze?= =?us-ascii?Q?yZbqpSKWXdM8Fh2wjETtmgXXpkuXLG2DnVUCxK5xch5dQEgX+PG4prwkW4cr?= =?us-ascii?Q?5+KJqtwq0u5zz9onHVd3+MSIznKZq81P/sh1YVgoIYkb9ikOnV343gP6o7Xw?= =?us-ascii?Q?+LT4hxu5+8cy5W5HP5wTb8AYm1REgFj42fcJus/egNQKn/APZOtFruQADLiA?= =?us-ascii?Q?aGZCAIzinJsw1kpK9+CH/Hj7gEOgq4STPhUrQ/bCMAPEqyJS/4v/TnpobjZp?= =?us-ascii?Q?VoNIAgWkEzUCJhoq9o6hq5QQVemEf47j+ziBU6nw5dy4+VwYFLQT8xGD5Iis?= =?us-ascii?Q?g37zl2NFDjZdfaM0mf2vW6UzzztvGIAIfCAtP7g6Zx63MLBcjDfWP2yauE7z?= =?us-ascii?Q?x6jfd4uwr5dounP4GNzmCwXxH/NMFRsbVhR4K9kVfqFT58tShz+YljyTrL/V?= =?us-ascii?Q?GCwKN0iqakdcD0V9wH8f2iGUGT3Y?= X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-AuthSource: DF4PR8401MB0812.NAMPRD84.PROD.OUTLOOK.COM X-MS-Exchange-CrossTenant-Network-Message-Id: 97f6f482-814a-4d8c-6957-08d95a6db319 X-MS-Exchange-CrossTenant-originalarrivaltime: 08 Aug 2021 13:09:05.0079 (UTC) X-MS-Exchange-CrossTenant-fromentityheader: Hosted X-MS-Exchange-CrossTenant-id: 105b2061-b669-4b31-92ac-24d304d195dc X-MS-Exchange-CrossTenant-mailboxtype: HOSTED X-MS-Exchange-CrossTenant-userprincipalname: rbnCsjIjde/tveaW+c3miFvu3IVf43DLF3Tje35xk72Qzc45DxUiUjem1dDcwlgo8pvtV8G7XdqeNmFMvOigPw== X-MS-Exchange-Transport-CrossTenantHeadersStamped: DF4PR8401MB0666 X-OriginatorOrg: hpe.com X-Proofpoint-GUID: 1gRjXADfa4ONKpZ8AjTzzJR5tayJK2dC X-Proofpoint-ORIG-GUID: 1gRjXADfa4ONKpZ8AjTzzJR5tayJK2dC 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-08-08_03:2021-08-06,2021-08-08 signatures=0 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 bulkscore=0 mlxlogscore=999 impostorscore=0 adultscore=0 malwarescore=0 priorityscore=1501 clxscore=1015 mlxscore=0 phishscore=0 suspectscore=0 spamscore=0 lowpriorityscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2107140000 definitions=main-2108080082 Content-Language: en-US Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: quoted-printable Reviewed-by: Nickle Wang Thanks, Nickle -----Original Message----- From: Chang, Abner (HPS SW/FW Technologist) =20 Sent: Thursday, July 22, 2021 2:08 PM To: devel@edk2.groups.io Cc: Wang, Nickle (HPS SW) ; Liming Gao Subject: [staging/edk2-redfish-client Tools PATCH 3/6] RedfishClientPkg/Red= fish-Profile-Simulator: Add more features - 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 Re= dfishClientPkg/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/redfishProfileSim +++ ulator.py @@ -1,4 +1,9 @@ # Copyright Notice: +# +# Copyright (c) 2019, Intel Corporation. All rights reserved.
#=20 +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,=20 + 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=20 + 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=20 + 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, --- 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",=20 + "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,= =20 + 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.
#=20 +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 imp= ort 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=3D= 5000): # 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//",=20 + 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=20 + root.components['Systems'].get_element(system_id).components['BootOpti + ons'].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']) +=20=20=20=20 + #@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']) +=20=20=20=20 + #@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']) +=20=20=20=20 + #@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']) +=20=20=20=20 + #@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/",=20 + 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//",=20 + methods=3D['PATCH']) @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']) +=20=20=20=20 + #@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']) +=20=20=20=20 + #@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=20 + json.loads(request.data,object_pairs_hook=3DOrderedDict) # 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.
#=20 +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.
#=20 +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=20 + json.loads(res_rawdata,object_pairs_hook=3DOrderedDict) 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.
#=20 +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 .r= esource import RfResource, RfCollection from .storage import RfSimpleStora= geCollection, 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=20 + RfBootOptionCollection(base_path, os.path.join(rel_path, item),=20 + 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=20 + boot_data['BootOrder'] + + 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=20 + flask.Response(json.dumps(self.bootOptions[Idx],indent=3D4)) + + self.bootOptions.pop(Idx) + self.res_data["Members@odata.count"] =3D=20 + self.res_data["Members@odata.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): -- 2.17.1