├── .gitignore ├── MockupData └── SimpleOcpServerV1 │ └── redfish │ ├── index.json │ └── v1 │ ├── SessionService │ ├── Sessions │ │ ├── SESSION123456 │ │ │ └── index.json │ │ └── index.json │ └── index.json │ ├── Chassis │ ├── index.json │ └── A33 │ │ ├── Power │ │ └── index.json │ │ ├── index.json │ │ └── Thermal │ │ └── index.json │ ├── Managers │ ├── index.json │ └── bmc │ │ ├── EthernetInterfaces │ │ ├── index.json │ │ └── eth0 │ │ │ └── index.json │ │ ├── NetworkProtocol │ │ └── index.json │ │ └── index.json │ ├── AccountService │ ├── Roles │ │ ├── ReadOnlyUser │ │ │ └── index.json │ │ ├── Operator │ │ │ └── index.json │ │ ├── Admin │ │ │ └── index.json │ │ └── index.json │ ├── Accounts │ │ ├── root │ │ │ └── index.json │ │ ├── jane │ │ │ └── index.json │ │ ├── john │ │ │ └── index.json │ │ └── index.json │ └── index.json │ ├── Systems │ ├── index.json │ └── 2M220100SL │ │ ├── LogServices │ │ ├── index.json │ │ └── SEL │ │ │ ├── Entries │ │ │ ├── 1 │ │ │ │ └── index.json │ │ │ ├── 2 │ │ │ │ └── index.json │ │ │ └── index.json │ │ │ └── index.json │ │ └── index.json │ ├── index.json │ ├── odata │ └── index.json │ └── $metadata │ └── index.xml ├── AUTHORS.md ├── v1sim ├── __init__.py ├── serviceVersions.py ├── common_services.py ├── security.py ├── network.py ├── sessionService.py ├── accountService.py ├── updateService.py ├── resource.py ├── serviceRoot.py ├── storage.py ├── chassis.py ├── managers.py ├── systems.py ├── flask_redfish_auth.py └── redfishURIs.py ├── CHANGELOG.md ├── LICENSE.md ├── README.md └── redfishProfileSimulator.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | v1sim/*.pyc 3 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "v1": "/redfish/v1/" 3 | } 4 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Original Contribution: 2 | * Paul Vancil - Dell Inc. -- Dell Extreme Scale Infrastructure (ESI) Architecture Team 3 | -------------------------------------------------------------------------------- /v1sim/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright Notice: 2 | # Copyright 2016 Distributed Management Task Force, Inc. All rights reserved. 3 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Profile-Simulator/blob/main/LICENSE.md 4 | 5 | -------------------------------------------------------------------------------- /v1sim/serviceVersions.py: -------------------------------------------------------------------------------- 1 | # Copyright Notice: 2 | # Copyright 2016 Distributed Management Task Force, Inc. All rights reserved. 3 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Profile-Simulator/blob/main/LICENSE.md 4 | 5 | from .resource import RfResource 6 | 7 | 8 | class RfServiceVersions(RfResource): 9 | pass 10 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/SessionService/Sessions/SESSION123456/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#Session.v1_0_2.Session", 3 | "Id": "SESSION123456", 4 | "Name": "User Session", 5 | "Description": "Manager User Session", 6 | "UserName": "root", 7 | "Oem": {}, 8 | "@odata.context": "/redfish/v1/$metadata#Session.Session", 9 | "@odata.id": "/redfish/v1/SessionService/Sessions/SESSION123456" 10 | } 11 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/SessionService/Sessions/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#SessionCollection.SessionCollection", 3 | "Name": "Session Collection", 4 | "Members@odata.count": 1, 5 | "Members": [ 6 | { 7 | "@odata.id": "/redfish/v1/SessionService/Sessions/SESSION123456" 8 | } 9 | ], 10 | "@odata.context": "/redfish/v1/$metadata#SessionCollection.SessionCollection", 11 | "@odata.id": "/redfish/v1/SessionService/Sessions" 12 | } 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [0.9.6] - 2018-05-25 4 | - Fixed setuptools config to install all necessary Python modules 5 | - Enabled OpenBMC Static OBmcMonolythicPower8 6 | - Made simulator more generic to support other mockups 7 | - Fixed several properties in the Computer System resource 8 | - Fixed Content-Type header when returning XML 9 | 10 | ## [0.9.2] - 2016-12-08 11 | - Fixex code to work with late change in mockup data. It was failing to load 12 | - Fixed incomplete code in systems.py modeling logs 13 | 14 | ## [0.9.1] - 2016-09-06 15 | - Initial Public Release 16 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/SessionService/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#SessionService.v1_0_2.SessionService", 3 | "Id": "SessionService", 4 | "Name": "Session Service", 5 | "Description": "Session Service", 6 | "Status": { 7 | "State": "Enabled", 8 | "Health": "OK" 9 | }, 10 | "ServiceEnabled": true, 11 | "SessionTimeout": 30, 12 | "Sessions": { 13 | "@odata.id": "/redfish/v1/SessionService/Sessions" 14 | }, 15 | "@odata.context": "/redfish/v1/$metadata#SessionService.SessionService", 16 | "@odata.id": "/redfish/v1/SessionService" 17 | } 18 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/Chassis/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#ChassisCollection.ChassisCollection", 3 | "Name": "Chassis Collection", 4 | "Members@odata.count": 1, 5 | "Members": [ 6 | { 7 | "@odata.id": "/redfish/v1/Chassis/A33" 8 | } 9 | ], 10 | "@odata.context": "/redfish/v1/$metadata#ChassisCollection.ChassisCollection", 11 | "@odata.id": "/redfish/v1/Chassis", 12 | "@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 13 | } 14 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/Managers/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#ManagerCollection.ManagerCollection", 3 | "Name": "Manager Collection", 4 | "Members@odata.count": 1, 5 | "Members": [ 6 | { 7 | "@odata.id": "/redfish/v1/Managers/bmc" 8 | } 9 | ], 10 | "@odata.context": "/redfish/v1/$metadata#ManagerCollection.ManagerCollection", 11 | "@odata.id": "/redfish/v1/Managers", 12 | "@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 13 | } 14 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/AccountService/Roles/ReadOnlyUser/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#Role.v1_0_2.Role", 3 | "Id": "ReadOnlyUser", 4 | "Name": "User Role", 5 | "Description": "ReadOnlyUser User Role", 6 | "IsPredefined": true, 7 | "AssignedPrivileges": [ 8 | "Login" 9 | ], 10 | "@odata.context": "/redfish/v1/$metadata#Role.Role", 11 | "@odata.id": "/redfish/v1/AccountService/Roles/ReadOnlyUser", 12 | "@Redfish.Copyright":"Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 13 | } 14 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/Systems/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#ComputerSystemCollection.ComputerSystemCollection", 3 | "Name": "Computer System Collection", 4 | "Members@odata.count": 1, 5 | "Members": [ 6 | { 7 | "@odata.id": "/redfish/v1/Systems/2M220100SL" 8 | } 9 | ], 10 | "@odata.context": "/redfish/v1/$metadata#ComputerSystemCollection.ComputerSystemCollection", 11 | "@odata.id": "/redfish/v1/Systems", 12 | "@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 13 | } 14 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/AccountService/Roles/Operator/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#Role.v1_0_2.Role", 3 | "Id": "Operator", 4 | "Name": "User Role", 5 | "Description": "Operator User Role", 6 | "IsPredefined": true, 7 | "AssignedPrivileges": [ 8 | "Login", 9 | "ConfigureSelf", 10 | "ConfigureComponents" 11 | ], 12 | "@odata.context": "/redfish/v1/$metadata#Role.Role", 13 | "@odata.id": "/redfish/v1/AccountService/Roles/Operator", 14 | "@Redfish.Copyright":"Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 15 | } 16 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/AccountService/Roles/Admin/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#Role.v1_0_2.Role", 3 | "Id": "Admin", 4 | "Name": "User Role", 5 | "Description": "Admin User Role", 6 | "IsPredefined": true, 7 | "AssignedPrivileges": [ 8 | "Login", 9 | "ConfigureManager", 10 | "ConfigureUsers", 11 | "ConfigureSelf", 12 | "ConfigureComponents" 13 | ], 14 | "@odata.context": "/redfish/v1/$metadata#Role.Role", 15 | "@odata.id": "/redfish/v1/AccountService/Roles/Admin", 16 | "@Redfish.Copyright":"Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 17 | } 18 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/Systems/2M220100SL/LogServices/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#LogServiceCollection.LogServiceCollection", 3 | "Name": "Log Service Collection", 4 | "Description": "Collection of Logs for this System", 5 | "Members@odata.count": 1, 6 | "Members": [ 7 | { 8 | "@odata.id": "/redfish/v1/Systems/2M220100SL/LogServices/SEL" 9 | } 10 | ], 11 | "Oem": {}, 12 | "@odata.context": "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection", 13 | "@odata.id": "/redfish/v1/Systems/2M220100SL/LogServices", 14 | "@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 15 | } 16 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/AccountService/Accounts/root/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#ManagerAccount.v1_0_2.ManagerAccount", 3 | "Id": "root", 4 | "Name": "UserAccount", 5 | "Description": "User Account", 6 | "Enabled": true, 7 | "Password": null, 8 | "UserName": "root", 9 | "RoleId": "Admin", 10 | "Locked": false, 11 | "Links": { 12 | "Role": { 13 | "@odata.id": "/redfish/v1/AccountService/Roles/Admin" 14 | } 15 | }, 16 | "@odata.context": "/redfish/v1/$metadata#ManagerAccount.ManagerAccount", 17 | "@odata.id": "/redfish/v1/AccountService/Accounts/root", 18 | "@Redfish.Copyright":"Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 19 | } 20 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/AccountService/Accounts/jane/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#ManagerAccount.v1_0_2.ManagerAccount", 3 | "Id": "jane", 4 | "Name": "UserAccount", 5 | "Description": "User Account", 6 | "Enabled": true, 7 | "Password": null, 8 | "UserName": "jane", 9 | "RoleId": "Operator", 10 | "Locked": false, 11 | "Links": { 12 | "Role": { 13 | "@odata.id": "/redfish/v1/AccountService/Roles/Operator" 14 | } 15 | }, 16 | "@odata.context": "/redfish/v1/$metadata#ManagerAccount.ManagerAccount", 17 | "@odata.id": "/redfish/v1/AccountService/Accounts/jane", 18 | "@Redfish.Copyright":"Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 19 | } 20 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/AccountService/Accounts/john/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#ManagerAccount.v1_0_2.ManagerAccount", 3 | "Id": "john", 4 | "Name": "UserAccount", 5 | "Description": "User Account", 6 | "Enabled": true, 7 | "Password": null, 8 | "UserName": "john", 9 | "RoleId": "ReadOnlyUser", 10 | "Locked": false, 11 | "Links": { 12 | "Role": { 13 | "@odata.id": "/redfish/v1/AccountService/Roles/ReadOnlyUser" 14 | } 15 | }, 16 | "@odata.context": "/redfish/v1/$metadata#ManagerAccount.ManagerAccount", 17 | "@odata.id": "/redfish/v1/AccountService/Accounts/john", 18 | "@Redfish.Copyright":"Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 19 | } 20 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/Managers/bmc/EthernetInterfaces/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#EthernetInterfaceCollection.EthernetInterfaceCollection", 3 | "Name": "Ethernet Network Interface Collection", 4 | "Description": "Collection of EthernetInterfaces for this Manager", 5 | "Members@odata.count": 1, 6 | "Members": [ 7 | { 8 | "@odata.id": "/redfish/v1/Managers/bmc/EthernetInterfaces/eth0" 9 | } 10 | ], 11 | "Oem": {}, 12 | "@odata.context": "/redfish/v1/$metadata#EthernetInterfaceCollection.EthernetInterfaceCollection", 13 | "@odata.id": "/redfish/v1/Managers/bmc/EthernetInterfaces", 14 | "@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 15 | } 16 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/AccountService/Roles/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#RoleCollection.RoleCollection", 3 | "Name": "Roles Collection", 4 | "Members@odata.count": 3, 5 | "Members": [ 6 | { 7 | "@odata.id": "/redfish/v1/AccountService/Roles/Admin" 8 | }, 9 | { 10 | "@odata.id": "/redfish/v1/AccountService/Roles/Operator" 11 | }, 12 | { 13 | "@odata.id": "/redfish/v1/AccountService/Roles/ReadOnlyUser" 14 | } 15 | ], 16 | "@odata.context": "/redfish/v1/$metadata#RoleCollection.RoleCollection", 17 | "@odata.id": "/redfish/v1/AccountService/Roles", 18 | "@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 19 | } 20 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/AccountService/Accounts/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#ManagerAccountCollection.ManagerAccountCollection", 3 | "Name": "Accounts Collection", 4 | "Members@odata.count": 3, 5 | "Members": [ 6 | { 7 | "@odata.id": "/redfish/v1/AccountService/Accounts/root" 8 | }, 9 | { 10 | "@odata.id": "/redfish/v1/AccountService/Accounts/jane" 11 | }, 12 | { 13 | "@odata.id": "/redfish/v1/AccountService/Accounts/john" 14 | } 15 | ], 16 | "@odata.context": "/redfish/v1/$metadata#ManagerAccountCollection.ManagerAccountCollection", 17 | "@odata.id": "/redfish/v1/AccountService/Accounts", 18 | "@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 19 | } 20 | -------------------------------------------------------------------------------- /v1sim/common_services.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from .resource import RfCollection, RfResource 4 | 5 | 6 | class RfLogServiceCollection(RfCollection): 7 | def element_type(self): 8 | return RfLogService 9 | 10 | 11 | class RfLogService(RfResource): 12 | def create_sub_objects(self, base_path, rel_path): 13 | resource_path = os.path.join(base_path, rel_path); 14 | contents = os.listdir(resource_path) 15 | for item in contents: 16 | if item == "Entries": 17 | self.components[item] = RfLogEntriesCollection(base_path, 18 | os.path.join(rel_path, item), 19 | parent=self) 20 | 21 | 22 | class RfLogEntriesCollection(RfCollection): 23 | def element_type(self): 24 | return RfLogEntry 25 | 26 | 27 | class RfLogEntry(RfResource): 28 | pass 29 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/AccountService/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#AccountService.v1_0_2.AccountService", 3 | "Id": "AccountService", 4 | "Name": "Account Service", 5 | "Description": "Account Service", 6 | "Status": { 7 | "State": "Enabled", 8 | "Health": "OK" 9 | }, 10 | "ServiceEnabled": true, 11 | "AuthFailureLoggingThreshold": 3, 12 | "MinPasswordLength": 8, 13 | "AccountLockoutThreshold": 5, 14 | "AccountLockoutDuration": 30, 15 | "AccountLockoutCounterResetAfter": 30, 16 | "Accounts": { 17 | "@odata.id": "/redfish/v1/AccountService/Accounts" 18 | }, 19 | "Roles": { 20 | "@odata.id": "/redfish/v1/AccountService/Roles" 21 | }, 22 | "@odata.context": "/redfish/v1/$metadata#AccountService.AccountService", 23 | "@odata.id": "/redfish/v1/AccountService", 24 | "@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 25 | } 26 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/Chassis/A33/Power/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#Power.v1_1_0.Power", 3 | "Id": "Power", 4 | "Name": "Power", 5 | "PowerControl": [ 6 | { 7 | "@odata.id": "/redfish/v1/Chassis/A33/Power#/PowerControl/0", 8 | "MemberId": "0", 9 | "Name": "System Power Control", 10 | "PowerConsumedWatts": 224, 11 | "PowerCapacityWatts": 600, 12 | "PowerLimit": { 13 | "LimitInWatts": 450, 14 | "LimitException": "LogEventOnly", 15 | "CorrectionInMs": 1000 16 | }, 17 | "Status": { 18 | "State": "Enabled", 19 | "Health": "OK" 20 | }, 21 | "Oem": {} 22 | } 23 | ], 24 | "Oem": {}, 25 | "@odata.context": "/redfish/v1/$metadata#Power.Power", 26 | "@odata.id": "/redfish/v1/Chassis/A33/Power", 27 | "@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 28 | } 29 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/Systems/2M220100SL/LogServices/SEL/Entries/1/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#LogEntry.v1_0_2.LogEntry", 3 | "Id": "1", 4 | "Name": "Log Entry 1", 5 | "EntryType": "SEL", 6 | "OemRecordFormat": "CompanyX", 7 | "Severity": "Critical", 8 | "Created": "2012-03-07T14:44:00Z", 9 | "EntryCode": "Assert", 10 | "SensorType": "Temperature", 11 | "SensorNumber": 1, 12 | "Message": "Message for Event, Description for SEL, OEM depends", 13 | "MessageId": "Event.1.0.TempAssert", 14 | "MessageArgs": [ 15 | "ArrayOfMessageArgs" 16 | ], 17 | "Links": { 18 | "OriginOfCondition": { 19 | "@odata.id": "/redfish/v1/Chassis/1/Thermal" 20 | }, 21 | "Oem": {} 22 | }, 23 | "Oem": {}, 24 | "@odata.context": "/redfish/v1/$metadata#LogEntry.LogEntry", 25 | "@odata.id": "/redfish/v1/Systems/2M220100SL/LogServices/SEL/Entries/1", 26 | "@Redfish.Copyright":"Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 27 | } 28 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/Systems/2M220100SL/LogServices/SEL/Entries/2/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#LogEntry.v1_0_2.LogEntry", 3 | "Id": "2", 4 | "Name": "Log Entry 2", 5 | "EntryType": "SEL", 6 | "OemRecordFormat": "CompanyX", 7 | "Severity": "Critical", 8 | "Created": "2012-03-07T14:45:00Z", 9 | "EntryCode": "Assert", 10 | "SensorType": "Temperature", 11 | "SensorNumber": 2, 12 | "Message": "Message for Event, Description for SEL, OEM depends", 13 | "MessageId": "Event.1.0.TempAssert", 14 | "MessageArgs": [ 15 | "ArrayOfMessageArgs" 16 | ], 17 | "Links": { 18 | "OriginOfCondition": { 19 | "@odata.id": "/redfish/v1/Chassis/1/Thermal" 20 | }, 21 | "Oem": {} 22 | }, 23 | "Oem": {}, 24 | "@odata.context": "/redfish/v1/$metadata#LogEntry.LogEntry", 25 | "@odata.id": "/redfish/v1/Systems/2M220100SL/LogServices/SEL/Entries/2", 26 | "@Redfish.Copyright":"Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 27 | } 28 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#ServiceRoot.v1_0_2.ServiceRoot", 3 | "Id": "RootService", 4 | "Name": "Root Service", 5 | "RedfishVersion": "1.0.2", 6 | "UUID": "92384634-2938-2342-8820-489239905423", 7 | "Systems": { 8 | "@odata.id": "/redfish/v1/Systems" 9 | }, 10 | "Chassis": { 11 | "@odata.id": "/redfish/v1/Chassis" 12 | }, 13 | "Managers": { 14 | "@odata.id": "/redfish/v1/Managers" 15 | }, 16 | "SessionService": { 17 | "@odata.id": "/redfish/v1/SessionService" 18 | }, 19 | "AccountService": { 20 | "@odata.id": "/redfish/v1/AccountService" 21 | }, 22 | "Links": { 23 | "Sessions": { 24 | "@odata.id": "/redfish/v1/SessionService/Sessions" 25 | } 26 | }, 27 | "Oem": {}, 28 | "@odata.context": "/redfish/v1/$metadata#ServiceRoot.ServiceRoot", 29 | "@odata.id": "/redfish/v1/", 30 | "@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 31 | } 32 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/Systems/2M220100SL/LogServices/SEL/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#LogService.v1_0_2.LogService", 3 | "Id": "SEL", 4 | "Name": "System Log Service", 5 | "MaxNumberOfRecords": 1000, 6 | "OverWritePolicy": "WrapsWhenFull", 7 | "DateTime": "2015-03-13T04:14:33+06:00", 8 | "DateTimeLocalOffset": "+06:00", 9 | "ServiceEnabled": true, 10 | "Status": { 11 | "State": "Enabled", 12 | "Health": "OK" 13 | }, 14 | "Oem": {}, 15 | "Actions": { 16 | "#LogService.ClearLog": { 17 | "target": "/redfish/v1/Systems/2M220100SL/LogServices/SEL/Actions/LogService.Reset" 18 | }, 19 | "Oem": {} 20 | }, 21 | "Entries": { 22 | "@odata.id": "/redfish/v1/Systems/2M220100SL/LogServices/SEL/Entries" 23 | }, 24 | "@odata.context": "/redfish/v1/$metadata#LogService.LogService", 25 | "@odata.id": "/redfish/v1/Systems/2M220100SL/LogServices/SEL", 26 | "@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 27 | } 28 | -------------------------------------------------------------------------------- /v1sim/security.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from .resource import RfResource 4 | 5 | 6 | class RfSecurityService(RfResource): 7 | def create_sub_objects(self, base_path, rel_path): 8 | resource_path = os.path.join(base_path, rel_path); 9 | contents = os.listdir(resource_path) 10 | for item in contents: 11 | if item == "ESKM": 12 | self.components[item] = RfESKM(base_path, os.path.join(rel_path, item), parent=self) 13 | if item == "HttpsCert": 14 | self.components[item] = RfHttpsCert(base_path, os.path.join(rel_path, item), parent=self) 15 | if item == "SSO": 16 | self.components[item] = RfSSO(base_path, os.path.join(rel_path, item), parent=self) 17 | if item == "CertificateAuthentication": 18 | self.components[item] = RfCertificateAuthentication(base_path, os.path.join(rel_path, item), 19 | parent=self) 20 | 21 | 22 | class RfESKM(RfResource): 23 | pass 24 | 25 | 26 | class RfHttpsCert(RfResource): 27 | pass 28 | 29 | 30 | class RfSSO(RfResource): 31 | pass 32 | 33 | 34 | class RfCertificateAuthentication(RfResource): 35 | pass 36 | -------------------------------------------------------------------------------- /v1sim/network.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from .resource import RfCollection, RfResource 4 | 5 | 6 | class RfNetworkService(RfResource): 7 | pass 8 | 9 | 10 | class RfEthernetCollection(RfCollection): 11 | def element_type(self): 12 | return RfEthernet 13 | 14 | def create_sub_objects(self, base_path, rel_path): 15 | self.elements = {} 16 | resource_path = os.path.join(base_path, rel_path); 17 | contents = os.listdir(resource_path) 18 | for item in contents: 19 | if item == "VLANs": 20 | self.components[item] = RfVLanCollection(base_path, os.path.join(rel_path, item), parent=self) 21 | else: 22 | item_path = os.path.join(resource_path, item) 23 | if os.path.isdir(item_path): 24 | etype = self.element_type() # type: Type[RfEthernetCollection] 25 | self.elements[item] = etype(base_path, 26 | os.path.normpath("%s/%s" % (rel_path, item)), 27 | parent=self) 28 | 29 | class RfEthernet(RfResource): 30 | pass 31 | 32 | 33 | class RfVLanCollection(RfCollection): 34 | def element_type(self): 35 | return RfVLan 36 | 37 | 38 | class RfVLan(RfResource): 39 | pass 40 | 41 | 42 | class RfNetworkInterfaceCollection(RfCollection): 43 | def element_type(self): 44 | return RfNetworkInterface 45 | 46 | 47 | class RfNetworkInterface(RfResource): 48 | pass 49 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/Chassis/A33/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#Chassis.v1_2_0.Chassis", 3 | "Id": "A33", 4 | "Name": "Catfish System Chassis", 5 | "ChassisType": "RackMount", 6 | "Manufacturer": "CatfishManufacturer", 7 | "Model": "YellowCat1000", 8 | "SerialNumber": "2M220100SL", 9 | "SKU": "8675309", 10 | "PartNumber": "224071-J23", 11 | "AssetTag": "CATFISHASSETTAG", 12 | "IndicatorLED": "Lit", 13 | "PowerState": "On", 14 | "Status": { 15 | "State": "Enabled", 16 | "Health": "OK" 17 | }, 18 | "Thermal": { 19 | "@odata.id": "/redfish/v1/Chassis/A33/Thermal" 20 | }, 21 | "Power": { 22 | "@odata.id": "/redfish/v1/Chassis/A33/Power" 23 | }, 24 | "Links": { 25 | "ComputerSystems": [ 26 | { 27 | "@odata.id": "/redfish/v1/Systems/2M220100SL" 28 | } 29 | ], 30 | "ManagedBy": [ 31 | { 32 | "@odata.id": "/redfish/v1/Managers/bmc" 33 | } 34 | ], 35 | "ManagersInChassis": [ 36 | { 37 | "@odata.id": "/redfish/v1/Managers/bmc" 38 | } 39 | ], 40 | "Oem": {} 41 | }, 42 | "Oem": {}, 43 | "@odata.context": "/redfish/v1/$metadata#Chassis.Chassis", 44 | "@odata.id": "/redfish/v1/Chassis/A33", 45 | "@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 46 | } 47 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/Managers/bmc/NetworkProtocol/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#ManagerNetworkProtocol.v1_0_2.ManagerNetworkProtocol", 3 | "Id": "NetworkProtocol", 4 | "Name": "Manager Network Protocol", 5 | "Description": "Manager Network Service Status", 6 | "Status": { 7 | "State": "Enabled", 8 | "Health": "OK" 9 | }, 10 | "HostName": "myBmcHostname", 11 | "FQDN": "mymanager.mydomain.com", 12 | "HTTP": { 13 | "ProtocolEnabled": true, 14 | "Port": 80 15 | }, 16 | "HTTPS": { 17 | "ProtocolEnabled": true, 18 | "Port": 443 19 | }, 20 | "IPMI": { 21 | "ProtocolEnabled": true, 22 | "Port": 623 23 | }, 24 | "SSH": { 25 | "ProtocolEnabled": true, 26 | "Port": 22 27 | }, 28 | "SNMP": { 29 | "ProtocolEnabled": true, 30 | "Port": 161 31 | }, 32 | "SSDP": { 33 | "ProtocolEnabled": true, 34 | "Port": 1900, 35 | "NotifyMulticastIntervalSeconds": 600, 36 | "NotifyTTL": 5, 37 | "NotifyIPv6Scope": "Site" 38 | }, 39 | "Telnet": { 40 | "ProtocolEnabled": true, 41 | "Port": 23 42 | }, 43 | "Oem": {}, 44 | "@odata.context": "/redfish/v1/$metadata#ManagerNetworkProtocol.ManagerNetworkProtocol", 45 | "@odata.id": "/redfish/v1/Managers/bmc/NetworkProtocol", 46 | "@Redfish.Copyright":"Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2016-2024, Contributing Member(s) of Distributed Management Task 4 | Force, Inc.. All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, 7 | are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation and/or 14 | other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its contributors 17 | may be used to endorse or promote products derived from this software without 18 | specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /v1sim/sessionService.py: -------------------------------------------------------------------------------- 1 | # Copyright Notice: 2 | # Copyright 2016 Distributed Management Task Force, Inc. All rights reserved. 3 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Profile-Simulator/blob/main/LICENSE.md 4 | 5 | import os 6 | 7 | from .resource import RfResource, RfCollection 8 | 9 | 10 | class RfSessionServiceObj(RfResource): 11 | # create instance of sessionService 12 | def create_sub_objects(self, base_path, rel_path): 13 | self.components["Sessions"] = RfSessionCollection(base_path, 14 | os.path.normpath("redfish/v1/SessionService/Sessions"), 15 | parent=self) 16 | 17 | def patch_resource(self, patch_data): 18 | # first verify client didn't send us a property we cant patch 19 | for key in patch_data.keys(): 20 | if key != "SessionTimeout": 21 | return 4, 400, "Invalid Patch Property Sent", "" 22 | # now patch the valid properties sent 23 | if "SessionTimeout" in patch_data: 24 | new_val = patch_data['SessionTimeout'] 25 | if new_val < 30 or new_val > 86400: 26 | return 4, 400, "Bad Request-not in correct range", "" 27 | else: 28 | self.res_data['SessionTimeout'] = new_val 29 | return 0, 204, None, None 30 | else: 31 | return 4, 400, "Invalid Patch Property Sent", "" 32 | 33 | 34 | class RfSessionCollection(RfCollection): 35 | def element_type(self): 36 | return RfSessionObj 37 | 38 | 39 | # Service Collection Entries 40 | class RfSessionObj(RfResource): 41 | pass 42 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/odata/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "value": [ 3 | { 4 | "name": "Service", 5 | "kind": "Singleton", 6 | "url": "/redfish/v1/" 7 | }, 8 | { 9 | "name": "Systems", 10 | "kind": "Singleton", 11 | "url": "/redfish/v1/Systems" 12 | }, 13 | { 14 | "name": "Chassis", 15 | "kind": "Singleton", 16 | "url": "/redfish/v1/Chassis" 17 | }, 18 | { 19 | "name": "Managers", 20 | "kind": "Singleton", 21 | "url": "/redfish/v1/Managers" 22 | }, 23 | { 24 | "name": "AccountService", 25 | "kind": "Singleton", 26 | "url": "/redfish/v1/AccountService" 27 | }, 28 | { 29 | "name": "SessionService", 30 | "kind": "Singleton", 31 | "url": "/redfish/v1/SessionService" 32 | }, 33 | { 34 | "name": "EventService", 35 | "kind": "Singleton", 36 | "url": "/redfish/v1/EventService" 37 | }, 38 | { 39 | "name": "JsonSchemas", 40 | "kind": "Singleton", 41 | "url": "/redfish/v1/JsonSchemas" 42 | }, 43 | { 44 | "name": "Registries", 45 | "kind": "Singleton", 46 | "url": "/redfish/v1/Registries" 47 | }, 48 | { 49 | "name": "Sessions", 50 | "kind": "Singleton", 51 | "url": "/redfish/v1/SessionService/Sessions" 52 | } 53 | ], 54 | "@odata.context": "/redfish/v1/$metadata", 55 | "@Redfish.Copyright":"Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 56 | } 57 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/Managers/bmc/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#Manager.v1_1_0.Manager", 3 | "Id": "bmc", 4 | "Name": "Manager", 5 | "ManagerType": "BMC", 6 | "Description": "BMC", 7 | "ServiceEntryPointUUID": "92384634-2938-2342-8820-489239905423", 8 | "UUID": "00000000-0000-0000-0000-000000000000", 9 | "Model": "CatfishBMC", 10 | "DateTime": "2015-03-13T04:14:33+06:00", 11 | "DateTimeLocalOffset": "+06:00", 12 | "Status": { 13 | "State": "Enabled", 14 | "Health": "OK" 15 | }, 16 | "FirmwareVersion": "1.00", 17 | "NetworkProtocol": { 18 | "@odata.id": "/redfish/v1/Managers/bmc/NetworkProtocol" 19 | }, 20 | "EthernetInterfaces": { 21 | "@odata.id": "/redfish/v1/Managers/bmc/EthernetInterfaces" 22 | }, 23 | "Links": { 24 | "ManagerForServers": [ 25 | { 26 | "@odata.id": "/redfish/v1/Systems/2M220100SL" 27 | } 28 | ], 29 | "ManagerForChassis": [ 30 | { 31 | "@odata.id": "/redfish/v1/Chassis/A33" 32 | } 33 | ], 34 | "ManagerInChassis": { 35 | "@odata.id": "/redfish/v1/Chassis/A33" 36 | }, 37 | "Oem": {} 38 | }, 39 | "Actions": { 40 | "#Manager.Reset": { 41 | "target": "/redfish/v1/Managers/bmc/Actions/Manager.Reset", 42 | "ResetType@Redfish.AllowableValues": [ 43 | "ForceRestart", 44 | "GracefulRestart" 45 | ] 46 | }, 47 | "Oem": {} 48 | }, 49 | "Oem": {}, 50 | "@odata.context": "/redfish/v1/$metadata#Manager.Manager", 51 | "@odata.id": "/redfish/v1/Managers/bmc", 52 | "@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 53 | } 54 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/Managers/bmc/EthernetInterfaces/eth0/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#EthernetInterface.v1_0_2.EthernetInterface", 3 | "Id": "eth0", 4 | "Name": "Manager Ethernet Interface", 5 | "Description": "Management Network Interface", 6 | "Status": { 7 | "State": "Enabled", 8 | "Health": "OK" 9 | }, 10 | "InterfaceEnabled": true, 11 | "PermanentMACAddress": "AA:BB:CC:DD:EE:FF", 12 | "MACAddress": "AA:BB:CC:DD:EE:FF", 13 | "SpeedMbps": 100, 14 | "AutoNeg": true, 15 | "FullDuplex": true, 16 | "MTUSize": 1500, 17 | "HostName": "MyHostName", 18 | "FQDN": "MyHostName.MyDomainName.com", 19 | "MaxIPv6StaticAddresses": 1, 20 | "VLAN": { 21 | "VLANEnable": true, 22 | "VLANId": 101 23 | }, 24 | "IPv4Addresses": [ 25 | { 26 | "Address": "192.168.0.10", 27 | "SubnetMask": "255.255.252.0", 28 | "AddressOrigin": "DHCP", 29 | "Gateway": "192.168.0.1", 30 | "Oem": {} 31 | } 32 | ], 33 | "IPv6AddressPolicyTable": [ 34 | { 35 | "Prefix": "::1/128", 36 | "Precedence": 50, 37 | "Label": 0 38 | } 39 | ], 40 | "IPv6StaticAddresses": [ 41 | { 42 | "Address": "fe80::1ec1:deff:fe6f:1e24", 43 | "PrefixLength": 16 44 | } 45 | ], 46 | "IPv6DefaultGateway": "fe80::1ec1:deff:fe6f:1e24", 47 | "IPv6Addresses": [ 48 | { 49 | "Address": "fe80::1ec1:deff:fe6f:1e24", 50 | "PrefixLength": 64, 51 | "AddressOrigin": "SLAAC", 52 | "AddressState": "Preferred", 53 | "Oem": {} 54 | } 55 | ], 56 | "Oem": {}, 57 | "@odata.context": "/redfish/v1/$metadata#EthernetInterface.EthernetInterface", 58 | "@odata.id": "/redfish/v1/Managers/bmc/EthernetInterfaces/eth0", 59 | "@Redfish.Copyright":"Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 60 | } 61 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/Systems/2M220100SL/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#ComputerSystem.v1_1_0.ComputerSystem", 3 | "Id": "2M220100SL", 4 | "Name": "Catfish System", 5 | "SystemType": "Physical", 6 | "AssetTag": "CATFISHASSETTAG", 7 | "Manufacturer": "CatfishManufacturer", 8 | "Model": "YellowCat1000", 9 | "SerialNumber": "2M220100SL", 10 | "SKU": "867530", 11 | "PartNumber": "224071-J23", 12 | "Description": "Catfish Implementation Recipe of simple scale-out monolithic server", 13 | "UUID": "00000000-0000-0000-0000-000000000000", 14 | "HostName": "catfishHostname", 15 | "PowerState": "On", 16 | "BiosVersion": "X00.1.2.3.4(build-23)", 17 | "Status": { 18 | "State": "Enabled", 19 | "Health": "OK" 20 | }, 21 | "IndicatorLED": "Off", 22 | "Boot": { 23 | "BootSourceOverrideEnabled": "Once", 24 | "BootSourceOverrideMode": "UEFI", 25 | "UefiTargetBootSourceOverride": "uefiDevicePath", 26 | "BootSourceOverrideTarget": "Pxe", 27 | "BootSourceOverrideTarget@Redfish.AllowableValues": [ 28 | "None", 29 | "Pxe", 30 | "Usb", 31 | "Hdd", 32 | "BiosSetup", 33 | "UefiTarget", 34 | "UefiHttp" 35 | ] 36 | }, 37 | "LogServices": { 38 | "@odata.id": "/redfish/v1/Systems/2M220100SL/LogServices" 39 | }, 40 | "Links": { 41 | "Chassis": [ 42 | { 43 | "@odata.id": "/redfish/v1/Chassis/A33" 44 | } 45 | ], 46 | "ManagedBy": [ 47 | { 48 | "@odata.id": "/redfish/v1/Managers/bmc" 49 | } 50 | ], 51 | "Oem": {} 52 | }, 53 | "Actions": { 54 | "#ComputerSystem.Reset": { 55 | "target": "/redfish/v1/Systems/2M220100SL/Actions/ComputerSystem.Reset", 56 | "ResetType@Redfish.AllowableValues": [ 57 | "On", 58 | "ForceOff", 59 | "GracefulShutdown", 60 | "ForceRestart", 61 | "Nmi", 62 | "GracefulRestart", 63 | "ForceOn" 64 | ] 65 | } 66 | }, 67 | "@odata.context": "/redfish/v1/$metadata#ComputerSystem.ComputerSystem", 68 | "@odata.id": "/redfish/v1/Systems/2M220100SL", 69 | "@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 70 | } 71 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/Systems/2M220100SL/LogServices/SEL/Entries/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#LogEntryCollection.LogEntryCollection", 3 | "Name": "Log Service Collection", 4 | "Description": "Collection of Logs for this System", 5 | "Members@odata.count": 2, 6 | "Members": [ 7 | { 8 | "@odata.type": "#LogEntry.v1_0_2.LogEntry", 9 | "Id": "1", 10 | "Name": "Log Entry 1", 11 | "EntryType": "SEL", 12 | "OemRecordFormat": "CompanyX", 13 | "Severity": "Critical", 14 | "Created": "2012-03-07T14:44:00Z", 15 | "EntryCode": "Assert", 16 | "SensorType": "Temperature", 17 | "SensorNumber": 1, 18 | "Message": "Message for Event, Description for SEL, OEM depends", 19 | "MessageId": "Event.1.0.TempAssert", 20 | "MessageArgs": [ 21 | "ArrayOfMessageArgs" 22 | ], 23 | "Links": { 24 | "OriginOfCondition": { 25 | "@odata.id": "/redfish/v1/Chassis/1/Thermal" 26 | }, 27 | "Oem": {} 28 | }, 29 | "Oem": {}, 30 | "@odata.id": "/redfish/v1/Systems/2M220100SL/LogServices/SEL/Entries/1" 31 | }, 32 | { 33 | "@odata.type": "#LogEntry.v1_0_2.LogEntry", 34 | "Id": "2", 35 | "Name": "Log Entry 2", 36 | "EntryType": "SEL", 37 | "OemRecordFormat": "CompanyX", 38 | "Severity": "Critical", 39 | "Created": "2012-03-07T14:45:00Z", 40 | "EntryCode": "Assert", 41 | "SensorType": "Temperature", 42 | "SensorNumber": 2, 43 | "Message": "Message for Event, Description for SEL, OEM depends", 44 | "MessageId": "Event.1.0.TempAssert", 45 | "MessageArgs": [ 46 | "ArrayOfMessageArgs" 47 | ], 48 | "Links": { 49 | "OriginOfCondition": { 50 | "@odata.id": "/redfish/v1/Chassis/1/Thermal" 51 | }, 52 | "Oem": {} 53 | }, 54 | "Oem": {}, 55 | "@odata.id": "/redfish/v1/Systems/2M220100SL/LogServices/SEL/Entries/2" 56 | } 57 | ], 58 | "@odata.nextLink": "/redfish/v1/Systems/2M220100SL/LogServices/SEL/Entries?$skiptoken=2", 59 | "@odata.context": "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection", 60 | "@odata.id": "/redfish/v1/Systems/2M220100SL/LogServices/SEL/Entries", 61 | "@Redfish.Copyright":"Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 62 | } 63 | -------------------------------------------------------------------------------- /v1sim/accountService.py: -------------------------------------------------------------------------------- 1 | # Copyright Notice: 2 | # Copyright 2016 Distributed Management Task Force, Inc. All rights reserved. 3 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Profile-Simulator/blob/main/LICENSE.md 4 | 5 | import os 6 | 7 | from .resource import RfResource, RfCollection 8 | 9 | 10 | class RfAccountServiceObj(RfResource): 11 | # create instance of each AccountService 12 | def create_sub_objects(self, base_path, rel_path): 13 | if os.path.isdir(os.path.join(base_path, os.path.normpath("redfish/v1/AccountService/Accounts"))): 14 | self.components["Accounts"] = RfAccountCollection(base_path, 15 | os.path.normpath("redfish/v1/AccountService/Accounts"), 16 | parent=self) 17 | 18 | if os.path.isdir(os.path.join(base_path, os.path.normpath("redfish/v1/AccountService/Roles"))): 19 | self.components["Roles"] = RfRoleCollection(base_path, 20 | os.path.normpath("redfish/v1/AccountService/Roles"), 21 | parent=self) 22 | 23 | def patch_resource(self, patch_data): 24 | # first verify client didn't send us a property we cant patch 25 | patachables = ("MinPasswordLength", "AccountLockoutThreshold", 26 | "AccountLockoutDuration", "AccountLockoutCounterResetAfter") 27 | 28 | for key in patch_data.keys(): 29 | if key not in patachables: 30 | return 4, 400, "Invalid Patch Property Sent", "" 31 | 32 | # now patch the valid properties sent 33 | for key in patch_data.keys(): 34 | new_val = patch_data[key] 35 | print("new_val:{}".format(new_val)) 36 | try: 37 | num_val = round(new_val) 38 | except ValueError: 39 | return 4, 400, "invalid value", "" 40 | else: 41 | patch_data[key] = num_val 42 | 43 | # if here, we know all the patch data is valid properties and properties 44 | new_duration = self.res_data["AccountLockoutDuration"] 45 | new_reset_after = self.res_data["AccountLockoutCounterResetAfter"] 46 | if "AccountLockoutDuration" in patch_data: 47 | new_duration = patch_data["AccountLockoutDuration"] 48 | if "AccountLockoutCounterResetAfter" in patch_data: 49 | new_reset_after = patch_data["AccountLockoutCounterResetAfter"] 50 | if new_duration < new_reset_after: 51 | return 4, 400, "invalid value", "" 52 | 53 | # if here, all values are good. set them 54 | for key in patch_data.keys(): 55 | self.res_data[key] = patch_data[key] 56 | return 0, 204, None, None 57 | 58 | 59 | class RfAccountCollection(RfCollection): 60 | def element_type(self): 61 | return RfAccountObj 62 | 63 | 64 | # Service Collection Entries 65 | class RfAccountObj(RfResource): 66 | pass 67 | 68 | 69 | class RfRoleCollection(RfCollection): 70 | def element_type(self): 71 | return RfRoleObj 72 | 73 | 74 | # Service Collection Entries 75 | class RfRoleObj(RfResource): 76 | pass 77 | -------------------------------------------------------------------------------- /v1sim/updateService.py: -------------------------------------------------------------------------------- 1 | # Copyright Notice: 2 | # Copyright 2016 Distributed Management Task Force, Inc. All rights reserved. 3 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Profile-Simulator/blob/main/LICENSE.md 4 | 5 | import os 6 | 7 | from .resource import RfResource, RfCollection 8 | 9 | 10 | class RfUpdateServiceObj(RfResource): 11 | def create_sub_objects(self, base_path, rel_path): 12 | if os.path.isdir(os.path.join(base_path, os.path.normpath("redfish/v1/UpdateService/ComponentRepository"))): 13 | self.components["ComponentRepository"] \ 14 | = RfComponentRepositoryCollection(base_path, 15 | os.path.normpath("redfish/v1/UpdateService/ComponentRepository"), 16 | parent=self) 17 | if os.path.isdir(os.path.join(base_path, os.path.normpath("redfish/v1/UpdateService/FirmwareInventory"))): 18 | self.components["FirmwareInventory"] \ 19 | = RfFirmwareInventoryCollection(base_path, 20 | os.path.normpath("redfish/v1/UpdateService/FirmwareInventory"), 21 | parent=self) 22 | 23 | if os.path.isdir(os.path.join(base_path, os.path.normpath("redfish/v1/UpdateService/InstallSets"))): 24 | self.components["InstallSets"] \ 25 | = RfInstallSetCollection(base_path, 26 | os.path.normpath("redfish/v1/UpdateService/InstallSets"), 27 | parent=self) 28 | 29 | if os.path.isdir(os.path.join(base_path, os.path.normpath("redfish/v1/UpdateService/SoftwareInventory"))): 30 | self.components["SoftwareInventory"] \ 31 | = RfSoftwareInventoryCollection(base_path, 32 | os.path.normpath("redfish/v1/UpdateService/SoftwareInventory"), 33 | parent=self) 34 | 35 | if os.path.isdir(os.path.join(base_path, os.path.normpath("redfish/v1/UpdateService/UpdateTaskQueue"))): 36 | self.components["UpdateTaskQueue"] \ 37 | = RfUpdateTaskQueueCollection(base_path, 38 | os.path.normpath("redfish/v1/UpdateService/UpdateTaskQueue"), 39 | parent=self) 40 | 41 | 42 | class RfComponentRepositoryCollection(RfCollection): 43 | def element_type(self): 44 | return RfComponentRepository 45 | 46 | 47 | class RfComponentRepository(RfResource): 48 | pass 49 | 50 | 51 | class RfFirmwareInventoryCollection(RfCollection): 52 | def element_type(self): 53 | return RfComponentRepository 54 | 55 | 56 | class RfFirmwareInventory(RfResource): 57 | pass 58 | 59 | 60 | class RfInstallSetCollection(RfCollection): 61 | def element_type(self): 62 | return RfInstallSet 63 | 64 | 65 | class RfInstallSet(RfResource): 66 | pass 67 | 68 | 69 | class RfSoftwareInventoryCollection(RfCollection): 70 | def element_type(self): 71 | return RfSoftwareInventory 72 | 73 | 74 | class RfSoftwareInventory(RfResource): 75 | pass 76 | 77 | 78 | class RfUpdateTaskQueueCollection(RfCollection): 79 | def element_type(self): 80 | return RfUpdateTaskQueue 81 | 82 | 83 | class RfUpdateTaskQueue(RfResource): 84 | pass 85 | -------------------------------------------------------------------------------- /v1sim/resource.py: -------------------------------------------------------------------------------- 1 | # Copyright Notice: 2 | # Copyright 2016 Distributed Management Task Force, Inc. All rights reserved. 3 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Profile-Simulator/blob/main/LICENSE.md 4 | 5 | import json 6 | import os 7 | import sys 8 | 9 | import flask 10 | 11 | if sys.version_info >= (3, 5): 12 | from typing import Type 13 | 14 | 15 | class RfResource: 16 | def __init__(self, base_path, rel_path, parent=None): 17 | self.parent = parent 18 | self.components = {} 19 | 20 | path = os.path.join(base_path, rel_path) 21 | indx_file_path = os.path.join(path, "index.json") 22 | print("*****Loading Mockup json file:{}".format(indx_file_path)) 23 | if os.path.exists(indx_file_path): 24 | res_file = open(indx_file_path, "r") 25 | res_rawdata = res_file.read() 26 | self.res_data = json.loads(res_rawdata) 27 | self.create_sub_objects(base_path, rel_path) 28 | self.final_init_processing(base_path, rel_path) 29 | else: 30 | self.res_data = {} 31 | 32 | def create_sub_objects(self, base_path, rel_path): 33 | pass 34 | 35 | def final_init_processing(self, base_path, rel_path): 36 | pass 37 | 38 | def get_resource(self): 39 | return flask.jsonify(self.res_data) 40 | 41 | def get_attribute(self, attribute): 42 | return flask.jsonify(self.res_data[attribute]) 43 | 44 | def get_component(self, component): 45 | if component in self.components: 46 | return self.components[component] 47 | else: 48 | return None 49 | 50 | def patch_resource(self, patch_data): 51 | for key in patch_data.keys(): 52 | if key in self.res_data: 53 | self.res_data[key] = patch_data[key] 54 | else: 55 | raise Exception("attribute %s not found" % key) 56 | 57 | 58 | class RfResourceRaw: 59 | def __init__(self, base_path, rel_path, parent=None): 60 | self.parent = parent 61 | path = os.path.join(base_path, rel_path) 62 | indx_file_path = os.path.join(path, "index.xml") 63 | print("*****Loading Mockup raw data file:{}".format(indx_file_path)) 64 | res_file = open(indx_file_path, "r") 65 | res_raw_data = res_file.read() 66 | self.res_data = res_raw_data 67 | self.create_subobjects(base_path, rel_path) 68 | self.final_init_processing(base_path, rel_path) 69 | 70 | def create_subobjects(self, base_path, rel_path): 71 | pass 72 | 73 | def final_init_processing(self, base_path, rel_path): 74 | pass 75 | 76 | def get_resource(self): 77 | return flask.Response(response=self.res_data, status=200, mimetype='application/xml') 78 | 79 | 80 | class RfCollection(RfResource): 81 | def create_sub_objects(self, base_path, rel_path): 82 | self.elements = {} 83 | subpath = os.path.join(base_path, rel_path) 84 | contents = os.listdir(subpath) 85 | for item in contents: 86 | item_path = os.path.join(subpath, item) 87 | if os.path.isdir(item_path): 88 | etype = self.element_type() # type: Type[RfResource] 89 | self.elements[item] = etype(base_path, 90 | os.path.normpath("%s/%s" % (rel_path, item)), 91 | parent=self) 92 | 93 | def element_type(self): 94 | pass 95 | 96 | def get_elements(self): 97 | return self.elements 98 | 99 | def get_element(self, element_id): 100 | return self.elements[element_id] 101 | -------------------------------------------------------------------------------- /v1sim/serviceRoot.py: -------------------------------------------------------------------------------- 1 | # Copyright Notice: 2 | # Copyright 2016 Distributed Management Task Force, Inc. All rights reserved. 3 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Profile-Simulator/blob/main/LICENSE.md 4 | 5 | import os 6 | 7 | from .accountService import RfAccountServiceObj 8 | from .chassis import RfChassisCollection 9 | from .managers import RfManagersCollection 10 | from .resource import RfResource, RfCollection 11 | from .resource import RfResourceRaw 12 | from .sessionService import RfSessionServiceObj 13 | from .systems import RfSystemsCollection 14 | from .updateService import RfUpdateServiceObj 15 | 16 | 17 | class RfServiceRoot(RfResource): 18 | def create_sub_objects(self, base_path, rel_path): 19 | resource_path = os.path.join(base_path, rel_path); 20 | contents = os.listdir(resource_path) 21 | for item in contents: 22 | if item == "odata": 23 | self.components[item] = RfOdataServiceDoc(base_path, os.path.join(rel_path, item), parent=self) 24 | elif item == "$metadata": 25 | self.components[item] = RfOdataMetadata(base_path, os.path.join(rel_path, item), parent=self) 26 | elif item == "Systems": 27 | self.components[item] = RfSystemsCollection(base_path, os.path.join(rel_path, item), parent=self) 28 | elif item == "Chassis": 29 | self.components[item] = RfChassisCollection(base_path, os.path.join(rel_path, item), parent=self) 30 | elif item == "Managers": 31 | self.components[item] = RfManagersCollection(base_path, os.path.join(rel_path, item), parent=self) 32 | elif item == "AccountService": 33 | self.components[item] = RfAccountServiceObj(base_path, os.path.join(rel_path, item), parent=self) 34 | elif item == "SessionService": 35 | self.components[item] = RfSessionServiceObj(base_path, os.path.join(rel_path, item), parent=self) 36 | elif item == "ResourceDirectory": 37 | self.components[item] = RfResourceDirectoryObj(base_path, os.path.join(rel_path, item), parent=self) 38 | elif item == "UpdateService": 39 | self.components[item] = RfUpdateServiceObj(base_path, os.path.join(rel_path, item), parent=self) 40 | elif item == "Registries": 41 | self.components[item] = RfRegistryCollection(base_path, os.path.join(rel_path, item), parent=self) 42 | elif item == "EventService": 43 | self.components[item] = RfEventServiceObj(base_path, os.path.join(rel_path, item), parent=self) 44 | 45 | def final_init_processing(self, base_path, rel_path): 46 | print("\n\n{}".format(self.res_data['Name'])) 47 | 48 | 49 | class RfOdataServiceDoc(RfResource): 50 | pass 51 | 52 | 53 | class RfOdataMetadata(RfResourceRaw): 54 | pass 55 | 56 | 57 | class RfResourceDirectoryObj(RfResource): 58 | pass 59 | 60 | 61 | class RfRegistryCollection(RfCollection): 62 | def element_type(self): 63 | return RfRegistry 64 | 65 | 66 | class RfRegistry(RfResource): 67 | pass 68 | 69 | 70 | class RfEventServiceObj(RfResource): 71 | def create_sub_objects(self, base_path, rel_path): 72 | resource_path = os.path.join(base_path, rel_path); 73 | contents = os.listdir(resource_path) 74 | for item in contents: 75 | if item == "EventSubscriptions": 76 | self.components[item] = RfEventSubscriptionCollection(base_path, 77 | os.path.join(rel_path, item), 78 | parent=self) 79 | 80 | 81 | class RfEventSubscriptionCollection(RfCollection): 82 | def element_type(self): 83 | return RfEventSubscription 84 | 85 | 86 | class RfEventSubscription(RfResource): 87 | pass 88 | -------------------------------------------------------------------------------- /v1sim/storage.py: -------------------------------------------------------------------------------- 1 | # Copyright Notice: 2 | # Copyright 2016 Distributed Management Task Force, Inc. All rights reserved. 3 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Profile-Simulator/blob/main/LICENSE.md 4 | 5 | import os 6 | 7 | from .resource import RfResource, RfCollection 8 | 9 | 10 | class RfSimpleStorageCollection(RfCollection): 11 | def element_type(self): 12 | return RfSimpleStorage 13 | 14 | 15 | class RfSimpleStorage(RfResource): 16 | pass 17 | 18 | 19 | class RfSmartStorage(RfResource): 20 | def create_sub_objects(self, base_path, rel_path): 21 | resource_path = os.path.join(base_path, rel_path); 22 | contents = os.listdir(resource_path) 23 | for item in contents: 24 | if item == "ArrayControllers": 25 | self.components[item] = RfArrayControllerCollection(base_path, 26 | os.path.join(rel_path, item), 27 | parent=self) 28 | if item == "HostBusAdapters": 29 | self.components[item] = RfHostBusAdapterCollection(base_path, 30 | os.path.join(rel_path, item), 31 | parent=self) 32 | 33 | 34 | class RfArrayControllerCollection(RfCollection): 35 | def element_type(self): 36 | return RfArrayController 37 | 38 | 39 | class RfArrayController(RfResource): 40 | def create_sub_objects(self, base_path, rel_path): 41 | resource_path = os.path.join(base_path, rel_path); 42 | contents = os.listdir(resource_path) 43 | for item in contents: 44 | if item == "DiskDrives": 45 | self.components[item] = RfDiskDriveCollection(base_path, os.path.join(rel_path, item), 46 | parent=self) 47 | if item == "LogicalDrives": 48 | self.components[item] = RfLogicalDriveCollection(base_path, os.path.join(rel_path, item), 49 | parent=self) 50 | 51 | if item == "StorageEnclosures": 52 | self.components[item] = RfStorageEnclosureCollection(base_path, os.path.join(rel_path, item), 53 | parent=self) 54 | if item == "UnconfiguredDrives": 55 | self.components[item] = RfUnconfiguredDriveCollection(base_path, os.path.join(rel_path, item), 56 | parent=self) 57 | 58 | 59 | class RfHostBusAdapterCollection(RfCollection): 60 | def element_type(self): 61 | return RfHostBusAdapter 62 | 63 | 64 | class RfHostBusAdapter(RfResource): 65 | pass 66 | 67 | 68 | class RfDiskDriveCollection(RfCollection): 69 | def element_type(self): 70 | return RfDiskDrive 71 | 72 | 73 | class RfDiskDrive(RfResource): 74 | pass 75 | 76 | 77 | class RfLogicalDriveCollection(RfCollection): 78 | def element_type(self): 79 | return RfLogicalDrive 80 | 81 | 82 | class RfLogicalDrive(RfResource): 83 | def create_sub_objects(self, base_path, rel_path): 84 | resource_path = os.path.join(base_path, rel_path); 85 | contents = os.listdir(resource_path) 86 | for item in contents: 87 | if item == "DataDrives": 88 | self.components[item] = RfDataDriveCollection(base_path, os.path.join(rel_path, item), 89 | parent=self) 90 | 91 | 92 | class RfDataDriveCollection(RfCollection): 93 | def element_type(self): 94 | return RfDataDrive 95 | 96 | 97 | class RfDataDrive(RfResource): 98 | pass 99 | 100 | 101 | class RfStorageEnclosureCollection(RfCollection): 102 | def element_type(self): 103 | return RfStorageEnclosure 104 | 105 | 106 | class RfStorageEnclosure(RfResource): 107 | pass 108 | 109 | 110 | class RfUnconfiguredDriveCollection(RfCollection): 111 | def element_type(self): 112 | return RfUnconfiguredDrive 113 | 114 | 115 | class RfUnconfiguredDrive(RfResource): 116 | pass 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Copyright 2016-2024 Distributed Management Task Force, Inc. All rights reserved. 2 | 3 | # Redfish Profile Simulator 4 | 5 | [![deprecated](http://badges.github.io/stability-badges/dist/deprecated.svg)](http://github.com/badges/stability-badges) 6 | 7 | ## Deprecated 8 | 9 | This tool is deprecated in favor of other mockup server tools: 10 | 11 | * [Redfish-Mockup-Server](https://github.com/DMTF/Redfish-Mockup-Server): Serve static resources for `GET` requests. 12 | * [Redfish-Interface-Emulator](https://github.com/DMTF/Redfish-Interface-Emulator): A more functional service that implements modification requests and actions. 13 | * [Swordfish-API-Emulator](https://github.com/SNIA/Swordfish-API-Emulator): A SNIA extension to the previous tool with more built-in functionality supported. 14 | 15 | ## About 16 | 17 | ***Redfish Profile Simulator*** is a Python34 real simulator of the "simple monolithic server" feature profile. 18 | 19 | * A simple, minimal Redfish Service 20 | * For a monolithic Server 21 | * Aligned with: OCP Remote Machine Management Spec feature set 22 | 23 | ### Description 24 | 25 | * Based on flask 26 | * Initial resources are loaded from a catfish mockup into python dictionary structures 27 | * After that, data is read/patched... to the dictionaries 28 | * Supports BasicAuth, as well as Redfish Session Auth (for one session, one user) 29 | * Uses: 30 | * easy to add new URIs for testing a client 31 | * easy to tweak behavior or add bad responses to test a client 32 | * allows testing of authentication -- which current mockup servers dont do 33 | * easy to insert print statements in service to see if data coming across good..etc 34 | 35 | ### Current Limitation: 36 | 37 | * supports a single user/passwd and token 38 | * the user/passwd is: root/password123456 39 | * The authToken for Session Auth is: 123456SESSIONauthcode 40 | * Supports only HTTP (not HTTPS) 41 | * with redfishtool, use options: redfishtool.py -r127.0.0.1:5000 -u root -p password123456 -S Never 42 | 43 | ## Usage 44 | 45 | * ` python redfishProfileSimulatorMain.py [options]` 46 | * `[Options]`: 47 | 48 | -V, --Version,--- the program version 49 | -h, --help, --- help 50 | -H, --Host= --- host IP address. dflt=127.0.0.1 51 | -P,--Port= --- the port to use. dflt=5000 52 | -p, --profile= --- the path to the Redfish profile to use. dflt="SimpleOcpServerV1" 53 | 54 | ## Implementation 55 | 56 | * The simulation includes an http server, RestEngine, and dynamic Redfish datamodel. 57 | * You can GET, PATCH,... to the service just like a real Redfish service. 58 | * Both Basic and Redfish Session/Token authentication are supported 59 | * for a single user/passwd and token 60 | * the user/passwd is: root/password123456 61 | * The authToken for Session Auth is: 123456SESSIONauthcode 62 | * these can be changed by editing the redfishURSs.py file---will make dynamic later. 63 | * The http service and Rest engine is built on Flask, and all code is Python 3.4+ 64 | * The data model resources are "initialized" from the SPMF "SimpleOcpServerV1" Mockup. 65 | * and stored as python dictionaries 66 | * then the dictionaries are updated with patches, posts, deletes. 67 | * The program can be extended to support other mockup \"profiles\". 68 | * By default, the simulation runs on localhost (127.0.0.1), on port 5000. 69 | * These can be changed with CLI options: -P -H | --port= --host= 70 | 71 | ## Simple OCP Server V1 Mockup Description 72 | 73 | * A Monolithic server: 74 | * One ComputerSystem 75 | * One Chassis 76 | * One Manager 77 | 78 | * Provides basic management features aligned with OCP Remote Machine Management Spec 1.01: 79 | * Power-on/off/reset 80 | * Boot to PXE, HDD, BIOS setup (boot override) 81 | * 4 temp sensors per DCMI (CPU1, CPU2, Board, Inlet) 82 | * Simple Power Reading, and DCMI Power Limiting 83 | * Fan Monitoring w/ redundancy 84 | * Set asset tag and Indicator LED 85 | * Basic inventory (serial#, model, SKU, Vendor, BIOS ver…) 86 | * User Management 87 | * BMC management: get/set IP, version, enable/disable protocol 88 | 89 | * What it does NOT have -- that the Redfish 1.0 model supports 90 | * No PSUs in model (RMM spec did not include PSUs) 91 | * No ProcessorInfo, MemoryInfo, StorageInfo, System-EthernetInterfaceInfo 92 | * No Tasks 93 | * JsonSchema and Registries collections left out (since that is optional) 94 | * No EventService--Remote Machine Management spec used basic PET alerts 95 | * Uses only the pre-defined privileges and roles 96 | 97 | ## TO DO 98 | 99 | Some limitations to be extended in current implementation 100 | 101 | * Auth supports a single hard-coded username, password, and AuthToken, although the protocol is 100% compliant with respect to testing clients trying to authenticate 102 | * ex with basic auth, you have to use the hard coded user/password 103 | * ex with Session Auth, you just use the hard coded AuthToken 104 | * adding and deleting users not implemented--has 3 or 4 users predefined 105 | * accountService properties can be written, but failed logins, lockouts, etc is not implemented 106 | * system log not implemented yet 107 | -------------------------------------------------------------------------------- /v1sim/chassis.py: -------------------------------------------------------------------------------- 1 | # Copyright Notice: 2 | # Copyright 2016 Distributed Management Task Force, Inc. All rights reserved. 3 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Profile-Simulator/blob/main/LICENSE.md 4 | 5 | import os 6 | 7 | from .resource import RfResource, RfCollection 8 | 9 | 10 | class RfChassisCollection(RfCollection): 11 | def element_type(self): 12 | return RfChassisObj 13 | 14 | 15 | class RfChassisObj(RfResource): 16 | # create the dependent sub-objects that live under the chassis object 17 | def create_sub_objects(self, base_path, rel_path): 18 | resource_path = os.path.join(base_path, rel_path); 19 | contents = os.listdir(resource_path) 20 | for item in contents: 21 | if item == "Thermal": 22 | self.components[item] = RfChassisThermal(base_path, os.path.join(rel_path, item), parent=self) 23 | elif item == "Power": 24 | self.components[item] = RfChassisPower(base_path, os.path.join(rel_path, item), parent=self) 25 | 26 | def patch_resource(self, patch_data): 27 | # first verify client didn't send us a property we cant patch 28 | for key in patch_data.keys(): 29 | if key != "AssetTag" and key != "IndicatorLED": 30 | return 4, 400, "Invalid Patch Property Sent", "" 31 | # now patch the valid properties sent 32 | if "AssetTag" in patch_data: 33 | self.res_data['AssetTag'] = patch_data['AssetTag'] 34 | if "IndicatorLED" in patch_data: 35 | self.res_data['IndicatorLED'] = patch_data['IndicatorLED'] 36 | return 0, 204, None, None 37 | 38 | def reset_resource(self, reset_data): 39 | if "ResetType" in reset_data: 40 | # print("RESETDATA: {}".format(resetData)) 41 | value = reset_data['ResetType'] 42 | valid_values = self.res_data["Actions"]["#Chassis.Reset"]["ResetType@Redfish.AllowableValues"] 43 | if value in valid_values: 44 | # it is a supoported reset action modify other properties appropritely 45 | if value == "On": 46 | self.res_data["PowerState"] = "On" 47 | print("PROFILE_SIM--SERVER WAS RESET. power now ON") 48 | elif value == "ForceOff": 49 | self.res_data["PowerState"] = "Off" 50 | print("PROFILE_SIM--SERVER WAS RESET. Power now Off") 51 | return 0, 204, "Chassis Reset", "" 52 | else: 53 | return 4, 400, "Invalid reset value: ResetType", "" 54 | else: # invalid request 55 | return 4, 400, "Invalid request property", "" 56 | 57 | 58 | # subclass Thermal Metrics 59 | class RfChassisThermal(RfResource): 60 | pass 61 | 62 | 63 | # subclass Power Metrics 64 | class RfChassisPower(RfResource): 65 | def create_sub_objects(self, base_path, rel_path): 66 | resource_path = os.path.join(base_path, rel_path); 67 | contents = os.listdir(resource_path) 68 | for item in contents: 69 | if item == "FastPowerMeter": 70 | self.components[item] = RfFastPowerMeter(base_path, os.path.join(rel_path, item), parent=self) 71 | elif item == "FederatedGroupCapping": 72 | self.components[item] = RfFederatedGroupCapping(base_path, os.path.join(rel_path, item), parent=self) 73 | elif item == "PowerMeter": 74 | self.components[item] = RfPowerMeter(base_path, os.path.join(rel_path, item), parent=self) 75 | 76 | def patch_resource(self, patchData): 77 | # first verify client didn't send us a property we cant patch 78 | for key in patchData.keys(): 79 | if key != "PowerControl": 80 | return 4, 400, "Invalid Patch Property Sent", "" 81 | else: # Powercontrol: 82 | for prop2 in patchData["PowerControl"][0].keys(): 83 | if prop2 != "PowerLimit": 84 | return 4, 400, "Invalid Patch Property Sent", "" 85 | else: # PowerLimit 86 | for prop3 in patchData["PowerControl"][0]["PowerLimit"].keys(): 87 | if prop3 != "LimitInWatts" and prop3 != "LimitException" and prop3 != "CorrectionInMs": 88 | return 4, 400, "Invalid Patch Property Sent", "" 89 | # now patch the valid properties sent 90 | if "PowerControl" in patchData: 91 | if "PowerLimit" in patchData["PowerControl"][0]: 92 | patch_power_limit_dict = patchData["PowerControl"][0]["PowerLimit"] 93 | catfish_power_limit_dict = self.res_data["PowerControl"][0]["PowerLimit"] 94 | if "LimitInWatts" in patch_power_limit_dict: 95 | self.res_data["PowerControl"][0]["PowerLimit"]["LimitInWatts"] = \ 96 | patch_power_limit_dict['LimitInWatts'] 97 | if "LimitException" in patch_power_limit_dict: 98 | self.res_data["PowerControl"][0]["PowerLimit"]['LimitException'] = \ 99 | patch_power_limit_dict['LimitException'] 100 | if "CorrectionInMs" in patch_power_limit_dict: 101 | self.res_data["PowerControl"][0]["PowerLimit"]['CorrectionInMs'] = \ 102 | patch_power_limit_dict['CorrectionInMs'] 103 | return 0, 204, None, None 104 | 105 | 106 | class RfFastPowerMeter(RfResource): 107 | pass 108 | 109 | 110 | class RfFederatedGroupCapping(RfResource): 111 | pass 112 | 113 | 114 | class RfPowerMeter(RfResource): 115 | pass 116 | -------------------------------------------------------------------------------- /redfishProfileSimulator.py: -------------------------------------------------------------------------------- 1 | # Copyright Notice: 2 | # Copyright 2016 Distributed Management Task Force, Inc. All rights reserved. 3 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Profile-Simulator/blob/main/LICENSE.md 4 | 5 | # This program is dependent on the following Python packages that should be installed separately with pip: 6 | # pip install Flask 7 | # 8 | # standard python packages 9 | import sys 10 | import getopt 11 | import os 12 | 13 | rfVersion = "0.9.6" 14 | rfProgram1 = "redfishProfileSimulator" 15 | rfProgram2 = " " 16 | rfUsage1 = "[-Vh] [--Version][--help]" 17 | rfUsage2 = "[-H] [-P] [-p]" 18 | rfUsage3 = "[--Host=] [--Port=] [--profile_path=]" 19 | 20 | 21 | def rf_usage(): 22 | print("Usage:") 23 | print(" ", rfProgram1, " ", rfUsage1) 24 | print(" ", rfProgram1, " ", rfUsage2) 25 | print(" ", rfProgram2, " ", rfUsage3) 26 | 27 | 28 | def rf_help(): 29 | print(rfProgram1,"implements a simulation of a redfish service for the \"Simple OCP Server V1\" Mockup.") 30 | print(" The simulation includes an http server, RestEngine, and dynamic Redfish datamodel.") 31 | print(" You can GET, PATHCH,... to the service just like a real Redfish service.") 32 | print(" Both Basic and Redfish Session/Token authentication is supported (for a single user/passwd and token") 33 | print(" the user/passwd is: root/password123456. The authToken is: 123456SESSIONauthcode") 34 | print(" these can be changed by editing the redfishURIs.py file. will make dynamic later.") 35 | print(" The http service and Rest engine is built on Flask, and all code is Python 3.4+") 36 | print(" The data model resources are \"initialized\" from the SPMF \"SimpleOcpServerV1\" Mockup.") 37 | print(" and stored as python dictionaries--then the dictionaries are updated with patches, posts, deletes.") 38 | print(" The program can be extended to support other mockup \"profiles\".") 39 | print("") 40 | print(" By default, the simulation runs on localhost (127.0.0.1), on port 5000.") 41 | print(" These can be changed with CLI options: -P -H | --port= --host=") 42 | print("") 43 | print("Version: ", rfVersion) 44 | rf_usage() 45 | print("") 46 | print(" -V, --Version, --- the program version") 47 | print(" -h, --help, --- help") 48 | print(" -H, --Host= --- host IP address. dflt=127.0.0.1") 49 | print(" -P, --Port= --- the port to use. dflt=5000") 50 | print(" -p, --profile= --- the path to the Redfish profile to use. " 51 | "dflt=\"./MockupData/SimpleOcpServerV1\" ") 52 | 53 | 54 | def main(argv): 55 | # set default option args 56 | rf_profile_path = os.path.abspath("./MockupData/SimpleOcpServerV1") 57 | rf_host = "127.0.0.1" 58 | rf_port = 5000 59 | 60 | try: 61 | opts, args = getopt.getopt(argv[1:], "VhH:P:p:", 62 | ["Version", "help", "Host=", "Port=", "profile="]) 63 | except getopt.GetoptError: 64 | print(rfProgram1, ": Error parsing options") 65 | rf_usage() 66 | sys.exit(2) 67 | for opt, arg in opts: 68 | if opt in ("-h", "--help"): 69 | rf_help() 70 | sys.exit(0) 71 | elif opt in ("-V", "--Version"): 72 | print("Version:", rfVersion) 73 | sys.exit(0) 74 | elif opt in ("-p", "--profile"): 75 | rf_profile_path = arg 76 | elif opt in "--Host=": 77 | rf_host = arg 78 | elif opt in "--Port=": 79 | rf_port=int(arg) 80 | else: 81 | print(" ", rfProgram1, ": Error: unsupported option") 82 | rf_usage() 83 | sys.exit(2) 84 | 85 | print("{} Version: {}".format(rfProgram1,rfVersion)) 86 | print(" Starting redfishProfileSimulator at: hostIP={}, port={}".format(rf_host, rf_port)) 87 | print(" Using Profile at {}".format(rf_profile_path)) 88 | 89 | if os.path.isdir(rf_profile_path): 90 | # import the classes and code we run from main. 91 | from v1sim.serviceVersions import RfServiceVersions 92 | from v1sim.serviceRoot import RfServiceRoot 93 | # rfApi_SimpleServer is a function in ./RedfishProfileSim/redfishURIs.py. 94 | # It loads the flask APIs (URIs), and starts the flask service 95 | from v1sim.redfishURIs import rfApi_SimpleServer 96 | 97 | # create the root service resource 98 | root_path = os.path.normpath("redfish/v1") 99 | 100 | # create the version resource for GET /redfish 101 | versions = RfServiceVersions(rf_profile_path, "redfish") 102 | root = RfServiceRoot(rf_profile_path, root_path) 103 | 104 | # start the flask REST API service 105 | rfApi_SimpleServer(root, versions, host=rf_host, port=rf_port) 106 | else: 107 | print("invalid profile path") 108 | 109 | 110 | if __name__ == "__main__": 111 | main(sys.argv) 112 | 113 | 114 | 115 | 116 | #http://127.0.0.1:5000/ 117 | 118 | #app.run(host="0.0.0.0") # run on all IPs 119 | #run(host=None, port=None, debug=None, **options) 120 | # host=0.0.0.0 server avail externally -- all IPs 121 | # host=127.0.0.1 is default 122 | # port=5000 default, or port defined in SERVER_NAME config var 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/Chassis/A33/Thermal/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "@odata.type": "#Thermal.v1_1_0.Thermal", 3 | "Id": "Thermal", 4 | "Name": "Thermal", 5 | "Temperatures": [ 6 | { 7 | "@odata.id": "/redfish/v1/Chassis/A33/Thermal#/Temperatures/0", 8 | "MemberId": "0", 9 | "Name": "Inlet Temp", 10 | "SensorNumber": 42, 11 | "Status": { 12 | "State": "Enabled", 13 | "Health": "OK" 14 | }, 15 | "ReadingCelsius": 25, 16 | "UpperThresholdNonCritical": 35, 17 | "UpperThresholdCritical": 40, 18 | "UpperThresholdFatal": 50, 19 | "MinReadingRangeTemp": 0, 20 | "MaxReadingRangeTemp": 200, 21 | "PhysicalContext": "Intake" 22 | }, 23 | { 24 | "@odata.id": "/redfish/v1/Chassis/A33/Thermal#/Temperatures/1", 25 | "MemberId": "1", 26 | "Name": "Board Temp", 27 | "SensorNumber": 43, 28 | "Status": { 29 | "State": "Enabled", 30 | "Health": "OK" 31 | }, 32 | "ReadingCelsius": 35, 33 | "UpperThresholdNonCritical": 30, 34 | "UpperThresholdCritical": 40, 35 | "UpperThresholdFatal": 50, 36 | "MinReadingRangeTemp": 0, 37 | "MaxReadingRangeTemp": 200, 38 | "PhysicalContext": "SystemBoard" 39 | }, 40 | { 41 | "@odata.id": "/redfish/v1/Chassis/A33/Thermal#/Temperatures/2", 42 | "MemberId": "2", 43 | "Name": "CPU1 Temp", 44 | "SensorNumber": 44, 45 | "Status": { 46 | "State": "Enabled", 47 | "Health": "OK" 48 | }, 49 | "ReadingCelsius": 45, 50 | "UpperThresholdNonCritical": 60, 51 | "UpperThresholdCritical": 82, 52 | "MinReadingRangeTemp": 0, 53 | "MaxReadingRangeTemp": 200, 54 | "PhysicalContext": "CPU" 55 | }, 56 | { 57 | "@odata.id": "/redfish/v1/Chassis/A33/Thermal#/Temperatures/3", 58 | "MemberId": "3", 59 | "Name": "CPU2 Temp", 60 | "SensorNumber": 45, 61 | "Status": { 62 | "State": "Enabled", 63 | "Health": "OK" 64 | }, 65 | "ReadingCelsius": 46, 66 | "UpperThresholdNonCritical": 60, 67 | "UpperThresholdCritical": 82, 68 | "MinReadingRangeTemp": 0, 69 | "MaxReadingRangeTemp": 200, 70 | "PhysicalContext": "CPU" 71 | } 72 | ], 73 | "Fans": [ 74 | { 75 | "@odata.id": "/redfish/v1/Chassis/A33/Thermal#/Fans/0", 76 | "MemberId": "0", 77 | "Name": "BaseBoard System Fan 1", 78 | "PhysicalContext": "Backplane", 79 | "Status": { 80 | "State": "Enabled", 81 | "Health": "OK" 82 | }, 83 | "Reading": 2100, 84 | "ReadingUnits": "RPM", 85 | "UpperThresholdNonCritical": 42, 86 | "UpperThresholdCritical": 4200, 87 | "UpperThresholdFatal": 42, 88 | "LowerThresholdNonCritical": 42, 89 | "LowerThresholdCritical": 5, 90 | "LowerThresholdFatal": 42, 91 | "MinReadingRange": 0, 92 | "MaxReadingRange": 5000, 93 | "Redundancy": [ 94 | { 95 | "@odata.id": "/redfish/v1/Chassis/A33/Thermal#/Redundancy/0" 96 | } 97 | ] 98 | }, 99 | { 100 | "@odata.id": "/redfish/v1/Chassis/A33/Thermal#/Fans/1", 101 | "MemberId": "1", 102 | "Name": "BaseBoard System Fan 2", 103 | "PhysicalContext": "Backplane", 104 | "Status": { 105 | "State": "Enabled", 106 | "Health": "OK" 107 | }, 108 | "Reading": 2100, 109 | "ReadingUnits": "RPM", 110 | "UpperThresholdNonCritical": 42, 111 | "UpperThresholdCritical": 4200, 112 | "UpperThresholdFatal": 42, 113 | "LowerThresholdNonCritical": 42, 114 | "LowerThresholdCritical": 5, 115 | "LowerThresholdFatal": 42, 116 | "MinReadingRange": 0, 117 | "MaxReadingRange": 5000, 118 | "Redundancy": [ 119 | { 120 | "@odata.id": "/redfish/v1/Chassis/A33/Thermal#/Redundancy/0" 121 | } 122 | ] 123 | } 124 | ], 125 | "Redundancy": [ 126 | { 127 | "@odata.id": "/redfish/v1/Chassis/A33/Thermal#/Redundancy/0", 128 | "MemberId": "0", 129 | "Name": "BaseBoard System Fans", 130 | "RedundancySet": [ 131 | { 132 | "@odata.id": "/redfish/v1/Chassis/A33/Thermal#/Fans/0" 133 | }, 134 | { 135 | "@odata.id": "/redfish/v1/Chassis/A33/Thermal#/Fans/1" 136 | } 137 | ], 138 | "Mode": "N+m", 139 | "Status": { 140 | "State": "Enabled", 141 | "Health": "OK" 142 | }, 143 | "MinNumNeeded": 1, 144 | "MaxNumSupported": 2 145 | } 146 | ], 147 | "@odata.context": "/redfish/v1/$metadata#Thermal.Thermal", 148 | "@odata.id": "/redfish/v1/Chassis/A33/Thermal", 149 | "@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright." 150 | } 151 | -------------------------------------------------------------------------------- /MockupData/SimpleOcpServerV1/redfish/v1/$metadata/index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /v1sim/managers.py: -------------------------------------------------------------------------------- 1 | # Copyright Notice: 2 | # Copyright 2016 Distributed Management Task Force, Inc. All rights reserved. 3 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Profile-Simulator/blob/main/LICENSE.md 4 | 5 | import os 6 | 7 | from .common_services import RfLogServiceCollection 8 | from .network import RfNetworkService 9 | from .resource import RfResource, RfCollection 10 | from .security import RfSecurityService 11 | 12 | 13 | class RfManagersCollection(RfCollection): 14 | def element_type(self): 15 | return RfManagerObj 16 | 17 | 18 | class RfManagerObj(RfResource): 19 | """ 20 | create the dependent sub-objects that live under the Manager object 21 | """ 22 | 23 | def create_sub_objects(self, base_path, rel_path): 24 | resource_path = os.path.join(base_path, rel_path); 25 | contents = os.listdir(resource_path) 26 | for item in contents: 27 | if item == "EthernetInterfaces": 28 | self.components[item] = RfManagerEthernetColl(base_path, os.path.join(rel_path, item), parent=self) 29 | elif item == "NetworkProtocol": 30 | self.components[item] = RfManagerNetworkProtocol(base_path, os.path.join(rel_path, item), parent=self) 31 | elif item == "SerialInterfaces": 32 | self.components[item] = RfSerialInterfaceCollection(base_path, os.path.join(rel_path, item), 33 | parent=self) 34 | elif item == "VirutalMedia": 35 | self.components[item] = RfVirtualMediaCollection(base_path, os.path.join(rel_path, item), parent=self) 36 | elif item == "NICs": 37 | self.components[item] = RfNics(base_path, os.path.join(rel_path, item), parent=self) 38 | elif item == "LogServices": 39 | self.components[item] = RfLogServiceCollection(base_path, os.path.join(rel_path, item), parent=self) 40 | elif item == "ActiveHealthSystem": 41 | self.components[item] = RfActiveHealthSystem(base_path, os.path.join(rel_path, item), parent=self) 42 | elif item == "DateTime": 43 | self.components[item] = RfDateTime(base_path, os.path.join(rel_path, item), parent=self) 44 | elif item == "EmbeddedMedia": 45 | self.components[item] = RfEmbeddedMedia(base_path, os.path.join(rel_path, item), parent=self) 46 | elif item == "FederationGroups": 47 | self.components[item] = RfFederationGroupCollection(base_path, os.path.join(rel_path, item), 48 | parent=self) 49 | elif item == "FederationPeers": 50 | self.components[item] = RfFederationPeerCollection(base_path, os.path.join(rel_path, item), parent=self) 51 | elif item == "LicenseService": 52 | self.components[item] = RfLicenseServiceCollection(base_path, os.path.join(rel_path, item), parent=self) 53 | elif item == "SecurityService": 54 | self.components[item] = RfSecurityService(base_path, os.path.join(rel_path, item), parent=self) 55 | elif item == "UpdateService": 56 | self.components[item] = RfManagerUpdateService(base_path, os.path.join(rel_path, item), parent=self) 57 | elif item == "NetworkService": 58 | self.components[item] = RfNetworkService(base_path, os.path.join(rel_path, item), parent=self) 59 | 60 | def patch_resource(self, patch_data): 61 | # first verify client didn't send us a property we cant patch 62 | for key in patch_data.keys(): 63 | if key != "DateTime" and key != "DateTimeLocalOffset": 64 | return 4, 400, "Invalid Patch Property Sent", "" 65 | 66 | date_time = None 67 | date_time_offset = None 68 | local_offset = None 69 | 70 | # now patch the valid properties sent 71 | if "DateTime" in patch_data: 72 | date_time = patch_data['DateTime'] 73 | date_time_offset = date_time[-6:] # get last 6 chars ....+00:00 or -00:00 74 | if "DateTimeLocalOffset" in patch_data: 75 | local_offset = patch_data['DateTimeLocalOffset'] 76 | 77 | # verify that if both DateTime and DateTimeLocalOffset were sent, thant 78 | # the offsets are the same. (no reason to send both though) 79 | if date_time_offset is not None and local_offset is not None: 80 | if date_time_offset != local_offset: 81 | return 4, 409, "Offsets in DateTime and DateTimeLocalOffset conflict", None # 409 Conflict 82 | 83 | # reconcile localOffset and the offset in DateTime to write back 84 | # if only DateTime was updated, also update dateTimeLocalOffset 85 | if local_offset is None: 86 | local_offset = date_time_offset 87 | # if only DateTimeLocalOffset was updated (timezone change), also update DateTime 88 | if date_time is None: 89 | date_time = self.res_data['DateTime'] # read current value to get time 90 | date_time = date_time[:-6] # strip the offset 91 | date_time = date_time + local_offset # add back the offset sent in in DateTimeLocalOFfset 92 | 93 | # TODO: issue 1545 in SPMF is ambiguity of what patching DateTimeLocalOffset should actually do. 94 | # this may need to be updated once issue is resolved 95 | 96 | # now write the valid properties with updated values 97 | self.res_data['DateTime'] = date_time 98 | self.res_data['DateTimeLocalOffset'] = local_offset 99 | return 0, 204, None, None 100 | 101 | def reset_resource(self, reset_data): 102 | if "ResetType" in reset_data: 103 | value = reset_data['ResetType'] 104 | valid_values = self.res_data["Actions"]["#Manager.Reset"]["ResetType@Redfish.AllowableValues"] 105 | if value in valid_values: 106 | # it is a supoported reset action modify other properties appropritely 107 | # nothing to do--manager always on in this profile 108 | return 0, 204, "System Reset", "" 109 | else: 110 | return 4, 400, "Invalid reset value: ResetType", "" 111 | else: # invalid request 112 | return 4, 400, "Invalid request property", "" 113 | 114 | 115 | class RfManagerNetworkProtocol(RfResource): 116 | pass 117 | 118 | 119 | # the Manager Ethernet Collection 120 | class RfManagerEthernetColl(RfCollection): 121 | def element_type(self): 122 | return RfManagerEthernet 123 | 124 | 125 | # the Manager Ethernet Instance 126 | class RfManagerEthernet(RfResource): 127 | def patch_resource(self, patch_data): 128 | # TODO: check and save the data 129 | # for now, just return ok w/ 204 no content 130 | return 0, 204, None, None 131 | 132 | 133 | class RfSerialInterfaceCollection(RfCollection): 134 | def element_type(self): 135 | return RfSerialInterface 136 | 137 | 138 | class RfSerialInterface(RfResource): 139 | pass 140 | 141 | 142 | class RfVirtualMediaCollection(RfCollection): 143 | def element_type(self): 144 | return RfVirtualMedia 145 | 146 | 147 | class RfVirtualMedia(RfResource): 148 | pass 149 | 150 | 151 | class RfNics(RfResource): 152 | def create_sub_objects(self, base_path, rel_path): 153 | resource_path = os.path.join(base_path, rel_path); 154 | contents = os.listdir(resource_path) 155 | for item in contents: 156 | if item == "Dedicated": 157 | self.components[item] = RfDedicatedNicCollection(base_path, 158 | os.path.join(rel_path, item), 159 | parent=self) 160 | 161 | 162 | class RfDedicatedNicCollection(RfCollection): 163 | def element_type(self): 164 | return RfNic 165 | 166 | 167 | class RfNic(RfResource): 168 | pass 169 | 170 | 171 | class RfActiveHealthSystem(RfResource): 172 | pass 173 | 174 | 175 | class RfDateTime(RfResource): 176 | pass 177 | 178 | 179 | class RfEmbeddedMedia(RfResource): 180 | pass 181 | 182 | 183 | class RfLicenseServiceCollection(RfCollection): 184 | def element_type(self): 185 | return RfLicenseService 186 | 187 | 188 | class RfLicenseService(RfResource): 189 | pass 190 | 191 | 192 | class RfFederationGroupCollection(RfCollection): 193 | def element_type(self): 194 | return RfFederationGroup 195 | 196 | 197 | class RfFederationGroup(RfResource): 198 | pass 199 | 200 | 201 | class RfFederationPeerCollection(RfCollection): 202 | def element_type(self): 203 | return RfFederationPeer 204 | 205 | 206 | class RfFederationPeer(RfResource): 207 | pass 208 | 209 | 210 | class RfManagerUpdateService(RfResource): 211 | pass 212 | -------------------------------------------------------------------------------- /v1sim/systems.py: -------------------------------------------------------------------------------- 1 | # Copyright Notice: 2 | # Copyright 2016 Distributed Management Task Force, Inc. All rights reserved. 3 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Profile-Simulator/blob/main/LICENSE.md 4 | 5 | import os 6 | 7 | from .common_services import RfLogServiceCollection 8 | from .network import RfEthernetCollection, RfNetworkInterfaceCollection 9 | from .resource import RfResource, RfCollection 10 | from .storage import RfSimpleStorageCollection, RfSmartStorage 11 | 12 | 13 | class RfSystemsCollection(RfCollection): 14 | def element_type(self): 15 | return RfSystemObj 16 | 17 | 18 | class RfSystemObj(RfResource): 19 | def create_sub_objects(self, base_path, rel_path): 20 | resource_path = os.path.join(base_path, rel_path) 21 | contents = os.listdir(resource_path) 22 | for item in contents: 23 | if item == "Bios": 24 | self.components[item] = RfBios(base_path, os.path.join(rel_path, item), parent=self) 25 | elif item == "EthernetInterfaces": 26 | self.components[item] = RfEthernetCollection(base_path, os.path.join(rel_path, item), parent=self) 27 | elif item == "LogServices": 28 | self.components[item] = RfLogServiceCollection(base_path, os.path.join(rel_path, item), parent=self) 29 | elif item == "Memory": 30 | self.components[item] = RfMemoryCollection(base_path, os.path.join(rel_path, item), parent=self) 31 | elif item == "Processors": 32 | self.components[item] = RfProcessorCollection(base_path, os.path.join(rel_path, item), parent=self) 33 | elif item == "SimpleStorage": 34 | self.components[item] = RfSimpleStorageCollection(base_path, os.path.join(rel_path, item), parent=self) 35 | elif item == "SmartStorage": 36 | self.components[item] = RfSmartStorage(base_path, os.path.join(rel_path, item), parent=self) 37 | elif item == "SecureBoot": 38 | self.components[item] = RfSecureBoot(base_path, os.path.join(rel_path, item), parent=self) 39 | elif item == "NetworkInterfaces": 40 | self.components[item] = RfNetworkInterfaceCollection(base_path, os.path.join(rel_path, item), parent=self) 41 | elif item == "PCIeDevices": 42 | self.components[item] = RfPCIeDeviceCollection(base_path, os.path.join(rel_path, item), parent=self) 43 | elif item == "PCISlots": 44 | self.components[item] = RfPCISlotCollection(base_path, os.path.join(rel_path, item), parent=self) 45 | elif item == "FirmwareInventory": 46 | self.components[item] = RfSystemFirmwareInventory(base_path, os.path.join(rel_path, item), parent=self) 47 | elif item == "USBDevices": 48 | self.components[item] = RfUSBDeviceCollection(base_path, os.path.join(rel_path, item), parent=self) 49 | elif item == "USBPorts": 50 | self.components[item] = RfUSBPortCollection(base_path, os.path.join(rel_path, item), parent=self) 51 | 52 | def patch_resource(self, patch_data): 53 | # first verify client didn't send us a property we cant patch 54 | for key in patch_data.keys(): 55 | if key != "AssetTag" and key != "IndicatorLED" and key != "Boot": 56 | return 4, 400, "Invalid Patch Property Sent", "" 57 | elif key == "Boot": 58 | for prop2 in patch_data["Boot"].keys(): 59 | if prop2 != "BootSourceOverrideEnabled" and prop2 != "BootSourceOverrideTarget": 60 | return 4, 400, "Invalid Patch Property Sent", "" 61 | # now patch the valid properties sent 62 | if "AssetTag" in patch_data: 63 | print("assetTag:{}".format(patch_data["AssetTag"])) 64 | self.res_data['AssetTag'] = patch_data['AssetTag'] 65 | if "IndicatorLED" in patch_data: 66 | self.res_data['IndicatorLED'] = patch_data['IndicatorLED'] 67 | if "Boot" in patch_data: 68 | boot_data = patch_data["Boot"] 69 | if "BootSourceOverrideEnabled" in boot_data: 70 | value = boot_data["BootSourceOverrideEnabled"] 71 | valid_values = ["Once", "Disabled", "Continuous"] 72 | if value in valid_values: 73 | self.res_data['Boot']['BootSourceOverrideEnabled'] = value 74 | else: 75 | return 4, 400, "Invalid_Value_Specified: BootSourceOverrideEnable", "" 76 | if "BootSourceOverrideTarget" in boot_data: 77 | value = boot_data["BootSourceOverrideTarget"] 78 | valid_values = self.res_data['Boot']['BootSourceOverrideTarget@Redfish.AllowableValues'] 79 | if value in valid_values: 80 | self.res_data['Boot']['BootSourceOverrideTarget'] = value 81 | else: 82 | return 4, 400, "Invalid_Value_Specified: BootSourceOverrideTarget", "" 83 | return 0, 204, None, None 84 | 85 | def reset_resource(self, reset_data): 86 | if "ResetType" in reset_data: 87 | # print("RESETDATA: {}".format(resetData)) 88 | value = reset_data['ResetType'] 89 | valid_values = self.res_data["Actions"]["#ComputerSystem.Reset"]["ResetType@Redfish.AllowableValues"] 90 | if value in valid_values: 91 | # it is a supoported reset action modify other properties appropritely 92 | if value == "On" or value == "ForceRestart" or value == "GracefulRestart": 93 | self.res_data["PowerState"] = "On" 94 | print("PROFILE_SIM--SERVER WAS RESET. power now ON") 95 | elif value == "GracefulShutdown" or value == "ForceOff": 96 | self.res_data["PowerState"] = "Off" 97 | print("PROFILE_SIM--SERVER WAS RESET. Power now Off") 98 | return 0, 204, "System Reset", "" 99 | else: 100 | return 4, 400, "Invalid reset value: ResetType", "" 101 | else: # invalid request 102 | return 4, 400, "Invalid request property", "" 103 | 104 | 105 | # subclass Logs Collection 106 | class RfMemoryCollection(RfCollection): 107 | def element_type(self): 108 | return RfMemory 109 | 110 | 111 | class RfMemory(RfResource): 112 | pass 113 | 114 | 115 | class RfProcessorCollection(RfCollection): 116 | def element_type(self): 117 | return RfProcessor 118 | 119 | 120 | class RfProcessor(RfResource): 121 | pass 122 | 123 | 124 | class RfBios(RfResource): 125 | def create_sub_objects(self, base_path, rel_path): 126 | resource_path = os.path.join(base_path, rel_path) 127 | contents = os.listdir(resource_path) 128 | for item in contents: 129 | if item == "Settings": 130 | self.components[item] = RfBiosSettings(base_path, os.path.join(rel_path, item), parent=self) 131 | 132 | def reset_resource(self, req_data): 133 | print("bios was reset") 134 | return 0, 204, "Bios Reset", "" 135 | 136 | def change_password(self, req_data): 137 | if "PasswordName" in req_data and "OldPassword" in req_data and "NewPassword" in req_data: 138 | print("changed password of type %s" % req_data["PasswordName"]) 139 | return 0, 204, "Password Change", "" 140 | else: # invalid request 141 | return 4, 400, "Invalid request property", "" 142 | 143 | 144 | class RfBiosSettings(RfResource): 145 | def patch_resource(self, patch_data): 146 | if "Attributes" not in patch_data: 147 | return 4, 400, "Invalid Payload. No Attributes found", "" 148 | for key in patch_data["Attributes"].keys(): 149 | # verify client didn't send us a property we cant patch 150 | if key not in self.res_data["Attributes"]: 151 | return 4, 400, "Invalid Patch Property Sent", "" 152 | else: 153 | self.parent.res_data["Attributes"][key] = patch_data["Attributes"][key] 154 | return 0, 204, None, None 155 | 156 | 157 | class RfPCIeDeviceCollection(RfCollection): 158 | def element_type(self): 159 | return RfPCIeDevice 160 | 161 | 162 | class RfPCIeDevice(RfResource): 163 | pass 164 | 165 | 166 | class RfPCISlotCollection(RfCollection): 167 | def element_type(self): 168 | return RfPCISlot 169 | 170 | 171 | class RfPCISlot(RfResource): 172 | pass 173 | 174 | 175 | class RfSecureBoot(RfResource): 176 | pass 177 | 178 | 179 | class RfSystemFirmwareInventory(RfResource): 180 | pass 181 | 182 | 183 | class RfUSBDeviceCollection(RfCollection): 184 | def element_type(self): 185 | return RfUSBDevice 186 | 187 | 188 | class RfUSBDevice(RfResource): 189 | pass 190 | 191 | 192 | class RfUSBPortCollection(RfCollection): 193 | def element_type(self): 194 | return RfUSBPort 195 | 196 | 197 | class RfUSBPort(RfResource): 198 | pass 199 | -------------------------------------------------------------------------------- /v1sim/flask_redfish_auth.py: -------------------------------------------------------------------------------- 1 | # Copyright Notice: 2 | # Copyright 2016 Distributed Management Task Force, Inc. All rights reserved. 3 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Profile-Simulator/blob/main/LICENSE.md 4 | 5 | """ 6 | flask_redfish_auth 7 | 8 | adapted from: 9 | flask_httpauth 10 | ================== 11 | This module provides Basic and Digest HTTP authentication for Flask routes. 12 | :copyright: (C) 2014 by Miguel Grinberg. 13 | :license: MIT, see LICENSE for more details, 14 | at https://github.com/miguelgrinberg/Flask-HTTPAuth/blob/main/LICENSE 15 | 16 | see documentation at: http://flask.pocoo.org/snippets/8/ 17 | code and docs at: https://github.com/miguelgrinberg/flask-httpauth/ 18 | 19 | **** modified to implement EITHER Redfish Token Auth or Basic Auth 20 | this file is imported by: catfishURIs.py 21 | 22 | Usage: In RedfishProfileSimulator: see this flow below in redfishURIs.py 23 | ... in redfishURIs.py 24 | from .flask_redfish_auth import RfHTTPBasicOrTokenAuth 25 | ... 26 | #create instance of the modified Basic or Redfish Token auth 27 | # this is what is in this file 28 | auth=RfHTTPBasicOrTokenAuth 29 | 30 | #define basic auth decorator used by flask 31 | @auth.verify_basic_password 32 | def verifyRfPasswd(user,passwd): 33 | ... 34 | 35 | #define Redfish Token/Session auth decorator used by flask 36 | @auth.verify_token 37 | def verifyRfToken(auth_token): 38 | .. 39 | 40 | @app.route("/api", methods=['GET']) 41 | @auth.rfAuthRequired 42 | def api() 43 | ... 44 | """ 45 | 46 | from functools import wraps 47 | 48 | from flask import request, make_response 49 | 50 | 51 | # this is the Base HTTP Auth class that is used to derive the Redfish "Basic or Token Auth" class 52 | class HTTPAuth(object): 53 | def __init__(self, scheme=None, realm=None): 54 | def default_get_password(userx): 55 | return None 56 | 57 | def default_basic_auth_error(): 58 | return "Unauthorized Access" 59 | 60 | def default_token_auth_error(): 61 | return "Unauthorized Access. Invalid authentication token" 62 | 63 | self.scheme = scheme 64 | self.realm = realm or "Authentication Required" 65 | self.get_password(default_get_password) 66 | self.basic_error_handler(default_basic_auth_error) 67 | self.token_error_handler(default_token_auth_error) 68 | 69 | def get_password(self, f): 70 | self.get_password_callback = f 71 | return f 72 | 73 | def token_error_handler(self, f): 74 | @wraps(f) 75 | def decorated(*args, **kwargs): 76 | res = f(*args, **kwargs) 77 | if type(res) == str: 78 | res = make_response(res) 79 | res.status_code = 401 80 | return res 81 | 82 | self.auth_token_error_callback = decorated 83 | return decorated 84 | 85 | def basic_error_handler(self, f): 86 | @wraps(f) 87 | def decorated(*args, **kwargs): 88 | res = f(*args, **kwargs) 89 | if type(res) == str: 90 | res = make_response(res) 91 | res.status_code = 401 92 | if 'WWW-Authenticate' not in res.headers.keys(): 93 | res.headers['WWW-Authenticate'] = self.authenticate_header() 94 | return res 95 | 96 | self.auth_basic_error_callback = decorated 97 | return decorated 98 | 99 | # for redfish, we need to hook this to check if its token auth before trying basic auth 100 | def rfAuthRequired(self, f): 101 | @wraps(f) 102 | def decorated(*args, **kwargs): 103 | auth = request.authorization 104 | print("in rfAuthRequired") 105 | print("headers: {}".format(request.headers)) 106 | # We need to ignore authentication headers for OPTIONS to avoid 107 | # unwanted interactions with CORS. 108 | # Chrome and Firefox issue a preflight OPTIONS request to check 109 | # Access-Control-* headers, and will fail if it returns 401. 110 | if request.method != 'OPTIONS': 111 | # auth is None if the Basic auth header didn't come in the request 112 | found_token = False 113 | if (auth is None): 114 | ###print("auth is None") 115 | # check if we have a redfish auth token 116 | hdr_token_key = "X-Auth-Token" 117 | auth_token = request.headers.get(hdr_token_key) 118 | ###print("token={}".format(auth_token)) 119 | if (auth_token is not None): 120 | found_token = True 121 | # yeah! we have an auth token in the headers 122 | authOk = self.verify_token_callback(auth_token) 123 | ###print("verify_token={}".format(authOk)) 124 | if (authOk is not True): 125 | # we had an auth token, but it didn't validate 126 | return (self.auth_token_error_callback()) 127 | 128 | # now continue with normal Basic Auth validation 129 | if (found_token is not True): 130 | ###print("try basic") 131 | if auth: 132 | password = self.get_password_callback(auth.username) 133 | else: 134 | password = None 135 | ###print("basic auth: auth={}, pwd={}".format(auth,password)) 136 | if (not self.authenticate(auth, password)): 137 | return (self.auth_basic_error_callback()) 138 | ###print("now execute the function") 139 | return (f(*args, **kwargs)) 140 | 141 | return (decorated) 142 | 143 | def username(self): 144 | if not request.authorization: 145 | return "" 146 | return request.authorization.username 147 | 148 | 149 | # this class is derived from HTTPAuth above 150 | class RfHTTPBasicOrTokenAuth(HTTPAuth): 151 | def __init__(self, scheme=None, realm=None): 152 | super(RfHTTPBasicOrTokenAuth, self).__init__(scheme, realm) 153 | self.hash_password(None) 154 | self.verify_basic_password(None) 155 | self.verify_token(None) 156 | 157 | def hash_password(self, f): 158 | self.hash_password_callback = f 159 | return f 160 | 161 | def verify_basic_password(self, f): 162 | self.verify_password_callback = f 163 | return f 164 | 165 | def verify_token(self, f): 166 | self.verify_token_callback = f 167 | return f 168 | 169 | def authenticate_header(self): 170 | return '{0} realm="{1}"'.format(self.scheme or 'Basic', self.realm) 171 | 172 | def authenticate(self, auth, stored_password): 173 | if auth: 174 | username = auth.username 175 | client_password = auth.password 176 | else: 177 | username = "" 178 | client_password = "" 179 | if self.verify_password_callback: 180 | return self.verify_password_callback(username, client_password) 181 | if not auth: 182 | return False 183 | if self.hash_password_callback: 184 | try: 185 | client_password = self.hash_password_callback(client_password) 186 | except TypeError: 187 | client_password = self.hash_password_callback(username, 188 | client_password) 189 | return client_password == stored_password 190 | 191 | 192 | ''' 193 | class HTTPDigestAuth(HTTPAuth): 194 | def __init__(self, scheme=None, realm=None, use_ha1_pw=False): 195 | super(HTTPDigestAuth, self).__init__(scheme, realm) 196 | self.use_ha1_pw = use_ha1_pw 197 | self.random = SystemRandom() 198 | try: 199 | self.random.random() 200 | except NotImplementedError: 201 | self.random = Random() 202 | 203 | def _generate_random(): 204 | return md5(str(self.random.random()).encode('utf-8')).hexdigest() 205 | 206 | def default_generate_nonce(): 207 | session["auth_nonce"] = _generate_random() 208 | return session["auth_nonce"] 209 | 210 | def default_verify_nonce(nonce): 211 | return nonce == session.get("auth_nonce") 212 | 213 | def default_generate_opaque(): 214 | session["auth_opaque"] = _generate_random() 215 | return session["auth_opaque"] 216 | 217 | def default_verify_opaque(opaque): 218 | return opaque == session.get("auth_opaque") 219 | 220 | self.generate_nonce(default_generate_nonce) 221 | self.generate_opaque(default_generate_opaque) 222 | self.verify_nonce(default_verify_nonce) 223 | self.verify_opaque(default_verify_opaque) 224 | 225 | def generate_nonce(self, f): 226 | self.generate_nonce_callback = f 227 | return f 228 | 229 | def verify_nonce(self, f): 230 | self.verify_nonce_callback = f 231 | return f 232 | 233 | def generate_opaque(self, f): 234 | self.generate_opaque_callback = f 235 | return f 236 | 237 | def verify_opaque(self, f): 238 | self.verify_opaque_callback = f 239 | return f 240 | 241 | def get_nonce(self): 242 | return self.generate_nonce_callback() 243 | 244 | def get_opaque(self): 245 | return self.generate_opaque_callback() 246 | 247 | def generate_ha1(self, username, password): 248 | a1 = username + ":" + self.realm + ":" + password 249 | a1 = a1.encode('utf-8') 250 | return md5(a1).hexdigest() 251 | 252 | def authenticate_header(self): 253 | session["auth_nonce"] = self.get_nonce() 254 | session["auth_opaque"] = self.get_opaque() 255 | return '{0} realm="{1}",nonce="{49}",opaque="{3}"'.format( 256 | self.scheme or 'Digest', self.realm, session["auth_nonce"], 257 | session["auth_opaque"]) 258 | 259 | def authenticate(self, auth, stored_password_or_ha1): 260 | if not auth or not auth.username or not auth.realm or not auth.uri \ 261 | or not auth.nonce or not auth.response \ 262 | or not stored_password_or_ha1: 263 | return False 264 | if not(self.verify_nonce_callback(auth.nonce)) or \ 265 | not(self.verify_opaque_callback(auth.opaque)): 266 | return False 267 | if self.use_ha1_pw: 268 | ha1 = stored_password_or_ha1 269 | else: 270 | a1 = auth.username + ":" + auth.realm + ":" + \ 271 | stored_password_or_ha1 272 | ha1 = md5(a1.encode('utf-8')).hexdigest() 273 | a2 = request.method + ":" + auth.uri 274 | ha2 = md5(a2.encode('utf-8')).hexdigest() 275 | a3 = ha1 + ":" + auth.nonce + ":" + ha2 276 | response = md5(a3.encode('utf-8')).hexdigest() 277 | return response == auth.response 278 | ''' 279 | -------------------------------------------------------------------------------- /v1sim/redfishURIs.py: -------------------------------------------------------------------------------- 1 | # Copyright Notice: 2 | # Copyright 2016 Distributed Management Task Force, Inc. All rights reserved. 3 | # License: BSD 3-Clause License. For full text see link: https://github.com/DMTF/Redfish-Profile-Simulator/blob/main/LICENSE.md 4 | 5 | import json 6 | 7 | from flask import Flask 8 | from flask import request 9 | 10 | from .flask_redfish_auth import RfHTTPBasicOrTokenAuth 11 | from .resource import RfResource, RfResourceRaw, RfCollection 12 | 13 | 14 | def rfApi_SimpleServer(root, versions, host="127.0.0.1", port=5000): 15 | app = Flask(__name__) 16 | 17 | # create auth class that does basic or redifish session auth 18 | auth = RfHTTPBasicOrTokenAuth() 19 | 20 | # define basic auth decorator used by flask 21 | # for basic auth, we only support user=catfish, passwd=hunter 22 | @auth.verify_basic_password 23 | def verify_rf_passwd(user, passwd): 24 | if user == "root": 25 | if passwd == "password123456": 26 | return True 27 | return False 28 | 29 | # define Redfish Token/Session auth decorator used by flask 30 | # for session token auth, only support toden: 123456CATFISHauthcode 31 | @auth.verify_token 32 | def verify_rf_token(auth_token): 33 | # lookup the user for this token 34 | # lookup the privileges for this user 35 | # check privilege 36 | # print("at verify_rf_token. auth_token={}".format(auth_token)) 37 | if auth_token == "123456SESSIONauthcode": # the magic token 38 | return True 39 | else: 40 | return False 41 | 42 | # define redfish URI APIs for flask 43 | 44 | # GET /redfish 45 | @app.route("/redfish", methods=['GET']) 46 | @app.route("/redfish/", methods=['GET']) 47 | def rf_versions(): 48 | return versions.get_resource() 49 | 50 | # GET /redfish/v1 51 | @app.route("/redfish/v1", methods=['GET']) 52 | @app.route("/redfish/v1/", methods=['GET']) 53 | def rf_service_root(): 54 | return root.get_resource() 55 | 56 | # GET /redfish/v1/$metadata 57 | @app.route("/redfish/v1/$metadata", methods=['GET']) 58 | def rf_metadata(rf_path='$metadata'): 59 | return resolve_path(root, rf_path) 60 | 61 | # GET /redfish/v1/odata 62 | @app.route("/redfish/v1/odata", methods=['GET']) 63 | @app.route("/redfish/v1/odata/", methods=['GET']) 64 | def rf_odata(rf_path='odata'): 65 | return resolve_path(root, rf_path) 66 | 67 | @app.route("/redfish/v1/", methods=['GET']) 68 | @app.route("/redfish/v1//", methods=['GET']) 69 | @auth.rfAuthRequired 70 | def rf_subsystems(rf_path): 71 | return resolve_path(root, rf_path) 72 | 73 | # this is a special test API -- an authenticated service root 74 | @app.route("/redfish/v1/A", methods=['GET']) 75 | @auth.rfAuthRequired 76 | def rf_service_root2(): 77 | print("root2") 78 | return root.get_resource() 79 | 80 | @app.route("/redfish/v1/Systems/", methods=['PATCH']) 81 | @app.route("/redfish/v1/Systems//", methods=['PATCH']) 82 | @auth.rfAuthRequired 83 | def rf_computer_systempatch(sys_path): 84 | rdata = request.get_json(cache=True) 85 | print("rdata:{}".format(rdata)) 86 | obj = patch_path(root.systems, sys_path) 87 | rc, status_code, err_string, resp = obj.patch_resource(rdata) 88 | if rc == 0: 89 | return "", status_code 90 | else: 91 | return err_string, status_code 92 | 93 | @app.route("/redfish/v1/Systems//Actions/ComputerSystem.Reset", methods=['POST']) 94 | @app.route("/redfish/v1/Systems//Actions/ComputerSystem.Reset/", methods=['POST']) 95 | @auth.rfAuthRequired 96 | def rf_computer_systemreset(system_id): 97 | # print("in reset") 98 | rdata = request.get_json(cache=True) 99 | # print("rdata:{}".format(rdata)) 100 | rc, status_code, err_string, resp = root.components['Systems'].get_element(system_id).reset_resource(rdata) 101 | if rc == 0: 102 | return "", status_code 103 | else: 104 | return err_string, status_code 105 | 106 | @app.route("/redfish/v1/Systems//bios/Actions/Bios.ResetBios", methods=['POST']) 107 | @app.route("/redfish/v1/Systems//bios/Actions/Bios.ResetBios/", methods=['POST']) 108 | @auth.rfAuthRequired 109 | def rf_computer_biosreset(system_id): 110 | # print("in reset") 111 | rdata = request.get_json(cache=True) 112 | # print("rdata:{}".format(rdata)) 113 | system = root.systems.get_element(system_id) 114 | bios = system.get_component("bios") 115 | rc, status_code, err_string, resp = bios.reset_resource(rdata) 116 | if rc == 0: 117 | return "", status_code 118 | else: 119 | return err_string, status_code 120 | 121 | @app.route("/redfish/v1/Systems//bios/Actions/Bios.ChangePassword", methods=['PATCH']) 122 | @app.route("/redfish/v1/Systems//bios/Actions/Bios.ChangePassword/", methods=['PATCH']) 123 | @auth.rfAuthRequired 124 | def rf_computer_change_pswd(system_id): 125 | # print("in reset") 126 | rdata = request.get_json(cache=True) 127 | # print("rdata:{}".format(rdata)) 128 | system = root.systems.get_element(system_id) 129 | bios = system.get_component("bios") 130 | rc, status_code, err_string, resp = bios.change_password(rdata) 131 | if rc == 0: 132 | return "", status_code 133 | else: 134 | return err_string, status_code 135 | 136 | @app.route("/redfish/v1/Chassis//Actions/Chassis.Reset", methods=['POST']) 137 | @app.route("/redfish/v1/Chassis//Actions/Chassis.Reset/", methods=['POST']) 138 | @auth.rfAuthRequired 139 | def rf_computer_chassisreset(chassis_id): 140 | # print("in reset") 141 | rdata = request.get_json(cache=True) 142 | # print("rdata:{}".format(rdata)) 143 | rc, status_code, err_string, resp = root.chassis.get_element(chassis_id).reset_resource(rdata) 144 | if rc == 0: 145 | return "", status_code 146 | else: 147 | return err_string, status_code 148 | 149 | @app.route("/redfish/v1/Chassis//Power", methods=['PATCH']) 150 | @app.route("/redfish/v1/Chassis//Power/", methods=['PATCH']) 151 | @auth.rfAuthRequired 152 | def rf_chassis_powerpatch(chassis_id): 153 | # rawdata=request.data 154 | rdata = request.get_json(cache=True) 155 | # print("RRrdata:{}".format(rdata)) 156 | rc, status_code, err_string, resp = root.chassis.get_element(chassis_id).power.patch_resource(rdata) 157 | if rc == 0: 158 | return "", status_code 159 | else: 160 | return err_string, status_code 161 | 162 | @app.route("/redfish/v1/Managers/", methods=['PATCH']) 163 | @app.route("/redfish/v1/Managers//", methods=['PATCH']) 164 | @auth.rfAuthRequired 165 | def rf_patch_manager_entity(manager_id): 166 | rdata = request.get_json(cache=True) 167 | # print("RRrdata:{}".format(rdata)) 168 | rc, status_code, err_string, resp = root.managers.get_element(manager_id).patch_resource(rdata) 169 | if rc == 0: 170 | return "", status_code 171 | else: 172 | return err_string, status_code 173 | 174 | # rest/v1/Managers/1 175 | @app.route("/redfish/v1/Managers//Actions/Manager.Reset", methods=['POST']) 176 | @app.route("/redfish/v1/Managers//Actions/Manager.Reset/", methods=['POST']) 177 | @auth.rfAuthRequired 178 | def rf_reset_manager(manager_id): 179 | rdata = request.get_json(cache=True) 180 | # print("rdata:{}".format(rdata)) 181 | rc, status_code, err_string, resp = root.managers.get_element(manager_id).reset_resource(rdata) 182 | if rc == 0: 183 | return "", status_code 184 | else: 185 | return err_string, status_code 186 | 187 | @app.route("/redfish/v1/Managers//EthernetInterfaces/", methods=['PATCH']) 188 | @app.route("/redfish/v1/Managers//EthernetInterfaces//", methods=['PATCH']) 189 | @auth.rfAuthRequired 190 | def rf_patch_manager_nic_entity(manager_id, eth_id): 191 | resp = root.managers.get_element(manager_id).ethernetColl.get_interface(eth_id).get_resource() 192 | rdata = request.get_json(cache=True) 193 | # print("RRrdata:{}".format(rdata)) 194 | ethernet_coll = root.managers.get_element(manager_id).ethernetColl 195 | rc, status_code, err_string, resp = ethernet_coll.get_interface(eth_id).patch_resource(rdata) 196 | if rc == 0: 197 | return "", status_code 198 | else: 199 | return err_string, status_code 200 | 201 | @app.route("/redfish/v1/SessionService", methods=['PATCH']) 202 | @app.route("/redfish/v1/SessionService/", methods=['PATCH']) 203 | @auth.rfAuthRequired 204 | def rf_patch_session_service(): 205 | rdata = request.get_json(cache=True) 206 | # print("RRrdata:{}".format(rdata)) 207 | rc, status_code, err_string, resp = root.sessionService.patch_resource(rdata) 208 | if rc == 0: 209 | return "", status_code 210 | else: 211 | return err_string, status_code 212 | 213 | # TODO: call root.sessionService.sessions.sessionLogin(usr,pwd), return resp, status_code, hdr 214 | # login API, user catfish, password=hunter, authToken=123456CATFISHauthcode 215 | @app.route("/redfish/v1/SessionService/Sessions", methods=['POST']) 216 | def rf_login(): 217 | print("login") 218 | rdata = request.get_json(cache=True) 219 | print("rdata:{}".format(rdata)) 220 | if rdata["UserName"] == "root" and rdata["Password"] == "password123456": 221 | x = {"Id": "SESSION123456"} 222 | resp = json.dumps(x) 223 | print("resp:{}".format(resp)) 224 | hdr = {"X-Auth-Token": "123456SESSIONauthcode", 225 | "Location": "/redfish/v1/SessionService/Sessions/SESSION123456"} 226 | return resp, 201, hdr 227 | else: 228 | return "", 401 229 | 230 | # TODO: call root.sessionService.sessions.delete(sessId), return resp,status,hdr 231 | # logout API 232 | @app.route("/redfish/v1/SessionService/Sessions/", methods=['DELETE']) 233 | @auth.rfAuthRequired 234 | def rf_session_logout(session_id): 235 | print("session logout %s" % session_id) 236 | # rdata=request.get_json(cache=True) 237 | # print("rdata:{}".format(rdata)) 238 | return "", 204 239 | 240 | @app.route("/redfish/v1/AccountService", methods=['PATCH']) 241 | @auth.rfAuthRequired 242 | def rf_patch_account_service(): 243 | rdata = request.get_json(cache=True) 244 | rc, status_code, err_string, resp = root.accountService.patch_resource(rdata) 245 | if rc == 0: 246 | return "", status_code 247 | else: 248 | return err_string, status_code 249 | 250 | def resolve_path(service, path): 251 | parts = path.split('/') 252 | result = service 253 | current_obj = service 254 | for part in parts: 255 | if isinstance(current_obj, RfCollection): 256 | result = current_obj.get_element(part) 257 | current_obj = result 258 | elif isinstance(current_obj, RfResource): 259 | result = current_obj.get_component(part) 260 | if not result: 261 | result = current_obj.get_attribute(part) 262 | break 263 | else: 264 | current_obj = result 265 | 266 | if isinstance(result, (RfResource, RfResourceRaw)): 267 | return result.get_resource() 268 | else: 269 | return result 270 | 271 | def patch_path(service, path): 272 | parts = path.split('/') 273 | result = None 274 | current_obj = service 275 | for part in parts: 276 | if isinstance(current_obj, RfCollection): 277 | result = current_obj.get_element(part) 278 | current_obj = result 279 | elif isinstance(current_obj, RfResource): 280 | result = current_obj.get_component(part) 281 | if not result: 282 | result = current_obj 283 | break 284 | else: 285 | current_obj = result 286 | return result 287 | 288 | ''' 289 | @app.route("/rest/v1/xxx/x", methods=['GET']) 290 | def rfXxxx(): 291 | resp=xxx.getObject() 292 | return(resp) 293 | ''' 294 | 295 | # END file redfishURIs 296 | 297 | # start Flask REST engine running 298 | app.run(host=host, port=port) 299 | 300 | # never returns 301 | 302 | 303 | ''' 304 | reference source links: 305 | https://gist.github.com/lrei/2408383 306 | http://docs.python-requests.org/en/v0.10.6/api/ 307 | http://flask.pocoo.org/docs/0.10/quickstart/ 308 | 309 | ''' 310 | --------------------------------------------------------------------------------