-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathRedFishBMC.py
243 lines (206 loc) · 10.8 KB
/
RedFishBMC.py
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
import json
#from pprint import pprint
import redfish
from logging import getLogger
#from paramiko.util import log_to_file
log = getLogger(__name__)
def is_hex(param):
import string
return all(c in string.hexdigits for c in param)
def trim_supermicro_key(key):
# Supermicro uses a _XXXX (hex) suffix for the key - trim it off
return key[:-5] if (key[-5] == "_" and is_hex(key[-4])) else key
def trim_supermicro_dict(settings_dict):
new_settings_dict = dict()
for key, value in settings_dict.items():
new_settings_dict[trim_supermicro_key(key)] = value
return new_settings_dict
class RedFishBMC(object):
def __init__(self, hostname, username=None, password=None):
# create the redfish object
self.cdrom_eject_uri = None
self.cdrom_mount_uri = None
self.cdrom_dev = None
self.cdrom_uri = None
self.virtual_media_list = None
self.virtual_media_data = None
self.virtual_media_uri = None
self.redfish = redfish.redfish_client(base_url="https://" + hostname, username=username, password=password,
default_prefix='/redfish/v1')
# login
self.name = hostname
self.username = username
self.password = password
try:
self.redfish.login(auth="session")
except redfish.rest.v1.InvalidCredentialsError:
log.error(f"Error logging into {hostname} - invalid credentials")
raise
except Exception as exc:
log.error(f"Error logging into {hostname}: {exc}")
raise
# get the Vendor ID
self.vendor = next(iter(self.redfish.root.get("Oem", {}).keys()), None)
if self.vendor is None:
self.vendor = self.redfish.root.get("Vendor", None)
# get Systems
self.systems_uri = self.redfish.root['Systems']['@odata.id']
self.systems_response = self.redfish.get(self.systems_uri) # ie: /redfish/v1/Systems
self.systems_members_uri = next(iter(self.systems_response.dict['Members']))['@odata.id']
self.systems_members_response = self.redfish.get(self.systems_members_uri) # ie: /redfish/v1/Systems/1
self.bios_version = self.systems_members_response.dict.get('BiosVersion', None)
self.systems_members_response_actions = self.systems_members_response.dict['Actions']
try:
self.system_reset_types = self.systems_members_response_actions['#ComputerSystem.Reset']['ResetType@Redfish.AllowableValues']
except KeyError:
#self.system_reset_types = None # SMC doesn't have this key...
self.system_reset_action_info = self.redfish.get(
self.systems_members_response_actions['#ComputerSystem.Reset']['@Redfish.ActionInfo'])
self.system_reset_types = self.system_reset_action_info.dict['Parameters'][0]['AllowableValues']
# get Processors
self.proc_uri = self.systems_members_response.dict['Processors']['@odata.id']
self.proc_data = self.redfish.get(self.proc_uri)
self.proc_members_uri = next(iter(self.proc_data.dict['Members']))['@odata.id']
self.proc_members_response = self.redfish.get(self.proc_members_uri) # ie: /redfish/v1/Processors/1
# note the architecture
self.arch = "AMD" if self.proc_members_response.dict.get("Model", None)[0] == 'A' else "Intel"
# fetch the BIOS settings
self.bios_uri = self.systems_members_response.dict['Bios']['@odata.id']
self.bios_data = self.redfish.get(self.bios_uri) # ie: /redfish/v1/Systems/1/Bios
if 'error' in self.bios_data.dict:
#log.error(f"Error fetching BIOS settings for {self.name}: {self.bios_data.dict['error']['@Message.ExtendedInfo']}")
raise Exception(f"Error fetching BIOS settings for {self.name}: {self.bios_data.dict['error']['@Message.ExtendedInfo']}")
self.bios_actions_dict = self.bios_data.dict['Actions']
self.reset_bios_uri = self.bios_actions_dict['#Bios.ResetBios']['target']
self.bios_settings_uri = self.bios_data.dict['@Redfish.Settings']['SettingsObject']['@odata.id']
#self.redfish_settings = self.bios_data.dict['@Redfish.Settings']
if 'SupportedApplyTimes' in self.bios_data.dict['@Redfish.Settings']:
self.supported_apply_times = self.bios_data.dict['@Redfish.Settings']['SupportedApplyTimes']
else:
self.supported_apply_times = None
self.managers_uri = self.redfish.root['Managers']['@odata.id']
self.managers_data = self.redfish.get(self.managers_uri)
self.managers_members_uri = next(iter(self.managers_data.dict['Members']))['@odata.id']
self.managers_members_response = self.redfish.get(self.managers_members_uri) # ie: /redfish/v1/Managers/1
self.managers_members_actions = self.managers_members_response.dict['Actions']
print()
def get_cdrom_info(self):
# get the Virtual CD-ROM
self.virtual_media_uri = self.managers_members_response.dict['VirtualMedia']['@odata.id']
self.virtual_media_data = self.redfish.get(self.virtual_media_uri) # ie: /redfish/v1/Managers/1/VirtualMedia
self.virtual_media_list = list()
for device in self.virtual_media_data.dict['Members']:
vdev = self.redfish.get(device['@odata.id'])
# self.virtual_media_list.append(self.redfish.get(device['@odata.id']))
for mediatype in vdev.obj.MediaTypes:
if mediatype == 'CD' or mediatype == "DVD":
# found it!
self.cdrom_uri = device['@odata.id']
self.cdrom_dev = vdev
self.cdrom_mount_uri = vdev.dict['Actions']['#VirtualMedia.InsertMedia']['target']
self.cdrom_eject_uri = vdev.dict['Actions']['#VirtualMedia.EjectMedia']['target']
break
def mount_cd(self, target):
pass
def eject_cd(self):
pass
def change_settings(self, settings_dict):
if self.vendor == "Supermicro":
settings_dict = self.adjust_supermicro_settings(settings_dict)
if settings_dict is None:
return False
# body = {'Attributes': {bios_property: property_value}}
body = dict()
body['Attributes'] = settings_dict
# We should fetch the SupportedApplyTimes attribute from the settings object to see if we need to set it...
if self.supported_apply_times is not None and "OnReset" in self.supported_apply_times:
body["@Redfish.SettingsApplyTime"] = {"ApplyTime": "OnReset"}
resp = self.redfish.patch(self.bios_settings_uri, body=body)
# If iLO responds with something outside of 200 or 201 then lets check the iLO extended info
# error message to see what went wrong
if resp.status == 400:
try:
print(json.dumps(resp.dict['error']['@Message.ExtendedInfo'], indent=4, sort_keys=True))
except Exception as exc:
log.error(f"A response exception occurred, unable to access Extended information {exc}")
elif resp.status not in [200,201,202]:
log.error("An http response of \'%s\' was returned.\n" % resp.status)
else:
# print("\nSuccess!\n")
log.info(f"Successfully set settings on host {self.name}; System reboot required")
return True
return False
def check_settings(self, settings):
log.info(f"Checking BIOS settings on {self.name}")
if settings is None:
log.info(f"{self.name} There are no settings for this platform in the bios settings configuration file")
return 0
if self.vendor == "Supermicro":
settings = self.adjust_supermicro_settings(settings)
if settings is None:
return 0
count = 0
for key, value in settings.items():
if key not in self.bios_data.obj.Attributes:
log.error(f"desired key ({key}) is not part of {self.name}'s bios!")
else:
if self.bios_data.obj.Attributes[key] != value:
log.info(f"{self.name}: BIOS setting {key} is {self.bios_data.obj.Attributes[key]}, " +
f"but should be {value}")
count += 1
return count
def reset_settings_to_default(self):
resp = self.redfish.post(self.reset_bios_uri, body=None)
if resp.status not in [200,201,202,203,204]:
log.error(f"An http response of '{resp.status}' was returned attempting to reset bios to default on {self.name}.\n")
return False
else:
return True
pass
def print_settings(self):
print(f"{self.name} Current BIOS settings:")
print(json.dumps(self.bios_data.obj.Attributes, indent=4, sort_keys=True))
def reboot(self):
action = self.systems_members_response.obj.Actions['#ComputerSystem.Reset']['target']
# print(json.dumps(self.systems_members_response.obj.Actions['#ComputerSystem.Reset']))
body = dict()
if self.systems_members_response.obj.PowerState != "On":
body['ResetType'] = 'On'
else:
if 'GracefulRestart' in self.system_reset_types:
body['ResetType'] = 'GracefulRestart'
elif 'ForceRestart' in self.system_reset_types:
body['ResetType'] = 'ForceRestart'
else:
body['ResetType'] = 'On'
resp = self.redfish.post(action, body=body)
print(f'reset status: {resp.status}')
if resp.status not in [200,201,202,203,204]:
log.error(f"An http response of '{resp.status}' was returned attempting to reboot {self.name}.\n")
return False
else:
return True
def adjust_supermicro_settings(self, settings_dict):
new_settings_dict = dict()
for key, value in settings_dict.items():
new_key = self.supermicro_find_key(trim_supermicro_key(key))
if new_key is None:
log.error(f"Unable to find key {key} in the Supermicro BIOS settings")
continue
new_settings_dict[new_key] = value
if len(new_settings_dict) == 0:
log.error(f"Unable to find any keys in the Supermicro BIOS settings")
return None
return new_settings_dict
def supermicro_find_key(self, key):
# Supermicro uses a different key for the same setting
# This function will convert the key to the correct one
# for Supermicro
#keys = list(self.bios_data.obj.Attributes.keys())
for server_key in self.bios_data.obj.Attributes.keys():
if server_key[-5] == "_" and is_hex(server_key[-4]) and server_key[:-5] == key:
return server_key
else:
if server_key == key:
return server_key
return None