Skip to content

Commit 40e4764

Browse files
authored
Merge pull request #127 from PranavDarshan/main
Extend Capabilities of Redfish Emulator: Reset Support, Config Updates, Virtual Media, and RAID Volume Management
2 parents bd26464 + a21ac5f commit 40e4764

29 files changed

+1700
-27
lines changed

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,8 +338,11 @@ These dynamic resources are available in the [Redfish Interface Emulator reposit
338338
| ./Systems/{id}/Memory | memory.py
339339
| ./Systems/{id}/Processors/{id} | processor.py
340340
| ./Systems/{id}/SimpleStorage/{id} | simple_storage.py
341+
| ./Systems/{id}/Bios | Bios_api.py
342+
| ./Systems/{id}/Bios/Settings | BiosSettings_api.py
341343
| ./Managers/{id} | Manager_api.py
342-
344+
| ./Managers/{id}/VirtualMedia | VirtualMedia_api.py
345+
| ./Managers/{id}/VirtualMedia/{id} | VirtualMedia_api.py
343346
344347
## SNIA API Emulator
345348
@@ -360,7 +363,7 @@ These dynamic resources are available in the [SNIA API Emulator repository](http
360363
| ./Chassis/{id}/NetworkAdapters/{id}/NetworkDeviceFunctions/{id} | networkdevicefunctions_api.py
361364
| ./Chassis/{id}/NetworkAdapters/{id}/Ports/{id} | nwports_api\.py
362365
| ./Systems/{id} | ComputerSystem_api.py
363-
| ./Systems/{id}/Storage/{id}/Volumes/{id} | volumes_api
366+
| ./Systems/{id}/Storage/{id}/Volumes/{id} | Volume_api.py
364367
| ./Systems/{id}/Storage/{id}/StoragePools/{id} | storagepools_api.py
365368
| ./Systems/{id}/Memory/{id} | memory.py
366369
| ./Systems/{id}/Memory/{id}/MemoryDomains/{id} | memory_domains_api.py
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import g
2+
import sys, traceback
3+
import logging
4+
import copy
5+
from flask import Flask, request, make_response
6+
from flask_restful import reqparse, Api, Resource
7+
8+
# Import BIOS settings template function
9+
from .templates.bios_settings import get_Bios_Settings_instance
10+
11+
# Dictionary to store BIOS settings for multiple systems
12+
bios_setting = {}
13+
INTERNAL_ERROR = 500
14+
15+
ALLOWED_BIOS_ATTRIBUTES = {
16+
"AdminPhone": str,
17+
"BootMode": str,
18+
"EmbeddedSata": str,
19+
"NicBoot1": str,
20+
"NicBoot2": str,
21+
"PowerProfile": str,
22+
"ProcCoreDisable": int,
23+
"ProcHyperthreading": ["Enabled", "Disabled"],
24+
"ProcTurboMode": ["Enabled", "Disabled"],
25+
"UsbControl": str
26+
}
27+
28+
# BIOS Settings Singleton API
29+
class BiosSettingsAPI(Resource):
30+
31+
def __init__(self, **kwargs):
32+
logging.info('BiosSettingsAPI init called')
33+
self.rb = kwargs.get('rb', '') # Get the Redfish base URL
34+
35+
# HTTP GET - Retrieve BIOS settings for a specific system
36+
def get(self, ident):
37+
logging.info(f'BiosSettingsAPI GET called for system {ident}')
38+
try:
39+
global bios_setting
40+
# Check if the system already exists, if not, create it
41+
if ident not in bios_setting:
42+
bios_setting[ident] = get_Bios_Settings_instance({'rb': self.rb, 'id': ident})
43+
44+
return bios_setting[ident], 200
45+
except Exception:
46+
traceback.print_exc()
47+
return {"error": "Internal server error"}, INTERNAL_ERROR
48+
49+
# HTTP PATCH - Update BIOS settings for a specific system
50+
def patch(self, ident):
51+
logging.info(f'BiosSettingsAPI PATCH called for system {ident}')
52+
try:
53+
global bios_setting
54+
if not request.json:
55+
return "Invalid input, expected JSON", 400
56+
57+
# Ensure the system exists
58+
if ident not in bios_setting:
59+
bios_setting[ident] = get_Bios_Settings_instance({'rb': self.rb, 'id': ident})
60+
61+
# Update only the specified attributes
62+
for key, value in request.json.items():
63+
if key not in ALLOWED_BIOS_ATTRIBUTES:
64+
return {"error": f"Invalid BIOS attribute: {key}"}, 400
65+
allowed_type = ALLOWED_BIOS_ATTRIBUTES[key]
66+
67+
if isinstance(allowed_type, list):
68+
if value not in allowed_type:
69+
return {"error": f"Invalid value for {key}. Must be one of {allowed_type}"}, 400
70+
elif not isinstance(value, allowed_type):
71+
return {"error": f"Invalid type for {key}. Expected {allowed_type.__name__}"}, 400
72+
bios_setting[ident]['Attributes'][key] = value
73+
return bios_setting[ident], 200
74+
except Exception:
75+
traceback.print_exc()
76+
return INTERNAL_ERROR
77+
78+
def get_bios_settings(ident):
79+
if ident not in bios_setting:
80+
bios_setting[ident] = get_Bios_Settings_instance({'rb': '/redfish/v1/', 'id': ident})
81+
return bios_setting[ident]

api_emulator/redfish/Bios_api.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import g
2+
import sys, traceback
3+
import logging
4+
import copy
5+
from flask import Flask, request, make_response
6+
from flask_restful import reqparse, Api, Resource
7+
8+
# Resource and SubResource imports
9+
from .templates.Bios import get_Bios_instance
10+
11+
bios_config = {}
12+
INTERNAL_ERROR = 500
13+
14+
# BIOS Singleton API
15+
class BiosAPI(Resource):
16+
def __init__(self, **kwargs):
17+
logging.info('BiosAPI init called')
18+
self.rb = kwargs.get('rb', '') # Get the Redfish base URL
19+
20+
# HTTP GET
21+
def get(self, ident): # Add 'ident' to capture the system ID from the URL
22+
logging.info(f'BiosAPI GET called for system {ident}')
23+
try:
24+
global bios_config
25+
# Pass the extracted 'ident' (system ID) to get_Bios_instance
26+
if ident not in bios_config:
27+
bios_config[ident] = get_Bios_instance({'rb': self.rb, 'id': ident})
28+
return bios_config[ident], 200
29+
except Exception:
30+
traceback.print_exc()
31+
return INTERNAL_ERROR
32+
33+
def get_bios_config(ident):
34+
"""
35+
Returns the current bios_config
36+
"""
37+
try:
38+
global bios_config
39+
# Pass the extracted 'ident' (system ID) to get_Bios_instance
40+
if ident not in bios_config:
41+
bios_config = get_Bios_instance({'rb': '/redfish/v1/', 'id': ident})
42+
return bios_config[ident]
43+
except Exception:
44+
traceback.print_exc()
45+
return bios_config
46+
47+
def set_bios_config(ident, data):
48+
"""
49+
Sets bios_config[ident] by calling get_Bios_instance.
50+
You can call this from any other module.
51+
"""
52+
try:
53+
global bios_config
54+
if ident not in bios_config:
55+
bios_config[ident] = get_Bios_instance({'rb': "/redfish/v1/", 'id': ident})
56+
57+
for key, value in data['Attributes'].items():
58+
bios_config[ident]['Attributes'][key] = value
59+
60+
except Exception as e:
61+
logging.error(f"Error setting BIOS config: {e}")
62+
traceback.print_exc()
63+
64+
class BiosResetDefaultAPI(Resource):
65+
def __init__(self, **kwargs):
66+
logging.info('BiosResetDefaultAPI init called')
67+
logging.info('ResetBiosToDefaultsPending set to true')
68+
self.rb = kwargs.get('rb', '') # Get the Redfish base URL
69+
70+
def post(self, ident):
71+
"""
72+
Sets the ResetBiosToDefaultsPending to true and
73+
waits for a computer reset to reset back to defaults
74+
"""
75+
try:
76+
global bios_config
77+
# Pass the extracted 'ident' (system ID) to get_Bios_instance
78+
if ident not in bios_config:
79+
bios_config[ident] = get_Bios_instance({'rb': self.rb, 'id': ident})
80+
81+
# bios_config[ident]["ResetBiosToDefaultsPending"] is a string
82+
bios_config[ident]["ResetBiosToDefaultsPending"] = "true"
83+
return bios_config[ident], 200
84+
except Exception:
85+
traceback.print_exc()
86+
return INTERNAL_ERROR

api_emulator/redfish/Chassis_api.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
import copy
1717
from flask import Flask, request, make_response, render_template
1818
from flask_restful import reqparse, Api, Resource
19-
19+
from .ResetActionInfo_api import ResetActionInfo_API
20+
from .ResetAction_api import ResetAction_API
21+
from api_emulator.redfish.ComputerSystem.ResetActionInfo_template import get_ResetActionInfo_instance
2022
# Resource and SubResource imports
2123
from .templates.Chassis import get_Chassis_instance
2224
from .thermal_api import ThermalAPI, CreateThermal
@@ -71,6 +73,7 @@ def put(self, ident):
7173
# PATCH commands can then be used to update the new instance.
7274
def post(self, ident):
7375
logging.info('ChassisAPI POST called')
76+
return 'POST is not a supported command for ChassisAPI', 405
7477
try:
7578
global config
7679
global wildcards
@@ -89,6 +92,7 @@ def post(self, ident):
8992
# HTTP PATCH
9093
def patch(self, ident):
9194
logging.info('ChassisAPI PATCH called')
95+
return 'PATCH is not a supported command for ChassisAPI', 405
9296
raw_dict = request.get_json(force=True)
9397
try:
9498
# Update specific portions of the identified object
@@ -103,6 +107,7 @@ def patch(self, ident):
103107
# HTTP DELETE
104108
def delete(self, ident):
105109
logging.info('ChassisAPI DELETE called')
110+
return 'DELETE is not a supported command for ChassisAPI', 405
106111
try:
107112
# Find the entry with the correct value for Id
108113
resp = 404
@@ -156,6 +161,7 @@ def verify(self, config):
156161
# TODO: 'id' should be obtained from the request data.
157162
def post(self):
158163
logging.info('ChassisCollectionAPI POST called')
164+
return 'POST is not a supported command for ChassisCollectionAPI', 405
159165
try:
160166
config = request.get_json(force=True)
161167
ok, msg = self.verify(config)
@@ -213,3 +219,65 @@ def put(self, ident):
213219
resp = INTERNAL_ERROR
214220
logging.info('CreateChassis init exit')
215221
return resp
222+
223+
class ChassisResetAPI(Resource):
224+
def __init__(self, **kwargs):
225+
logging.info('ChassisResetAPI init called')
226+
global wildcards
227+
wildcards = kwargs
228+
229+
230+
def post(self, ident):
231+
logging.info(f'ChassisResetAPI POST called for {ident}')
232+
try:
233+
global wildcards
234+
235+
if ident not in members:
236+
logging.error(f"Chassis {ident} not found in members.")
237+
return {"error": f"Chassis {ident} not found"}, 404
238+
239+
240+
241+
req = request.get_json()
242+
if not req or "ResetType" not in req:
243+
return {"error": "Missing ResetType"}, 400
244+
245+
wildcards['id'] = ident
246+
wildcards['rb'] = "/redfish/v1/"
247+
reset_info = get_ResetActionInfo_instance(wildcards)
248+
valid_reset_types = reset_info["Parameters"][0]["AllowableValues"]
249+
250+
reset_type = req["ResetType"]
251+
if reset_type not in valid_reset_types:
252+
return {"error": f"Invalid ResetType: {reset_type}"}, 400
253+
254+
power_state_map = {
255+
"On": "On",
256+
"ForceOn": "On",
257+
"ForceOff": "Off",
258+
"GracefulShutdown": "Off",
259+
"GracefulRestart": "On",
260+
"ForceRestart": "On",
261+
"Nmi": "Interrupt sent"
262+
}
263+
264+
# Ensure PowerState exists
265+
if "PowerState" not in members[ident]:
266+
logging.warning(f"'PowerState' key missing in {ident}, initializing it.")
267+
members[ident]["PowerState"] = "Unknown"
268+
269+
# Update PowerState
270+
if(reset_type!="PushPowerButton"):
271+
members[ident]["PowerState"] = power_state_map.get(reset_type, "Unknown")
272+
else:
273+
members[ident]["PowerState"]="On" if members[ident]["PowerState"]=="Off" else "Off"
274+
# Prepare response
275+
response = copy.deepcopy(reset_info)
276+
response["Parameters"][0]["Value"] = reset_type
277+
response["Message"] = f"{ident} reset with {reset_type}"
278+
response["PowerState"] = members[ident]["PowerState"]
279+
return response
280+
except Exception as e:
281+
logging.error(f"Error in Reset API: {e}")
282+
traceback.print_exc()
283+
return "Internal Server Error", INTERNAL_ERROR

api_emulator/redfish/ComputerSystem/ResetActionInfo_template.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
{
99
"@Redfish.Copyright": "Copyright 2014-2016 DMTF. All rights reserved.",
1010
"@odata.context": "{rb}$metadata#ActionInfo.ActionInfo",
11-
"@odata.id": "{rb}Systems/{sys_id}/ResetActionInfo",
1211
"@odata.type": "#ActionInfo.v1_0_0.ActionInfo",
1312
"Parameters": [{
1413
"Name": "ResetType",
@@ -31,6 +30,6 @@ def get_ResetActionInfo_instance(wildcards):
3130
c = copy.deepcopy(_TEMPLATE)
3231
print (wildcards)
3332
c['@odata.context']=c['@odata.context'].format(**wildcards)
34-
c['@odata.id']=c['@odata.id'].format(**wildcards)
33+
3534

3635
return c

0 commit comments

Comments
 (0)