blob: 5e8a400ca158a1848b2e5ac5a79a04488ba429a3 [file] [log] [blame]
Justin Thalerb8807ce2018-05-25 19:16:20 -05001#!/usr/bin/python3
Justin Thalere412dc22018-01-12 16:28:24 -06002"""
3 Copyright 2017 IBM Corporation
Justin Thalerf9aee3e2017-12-05 12:11:09 -06004
Justin Thalere412dc22018-01-12 16:28:24 -06005 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16"""
Justin Thalerf9aee3e2017-12-05 12:11:09 -060017import argparse
18import requests
19import getpass
20import json
21import os
22import urllib3
23import time, datetime
Justin Thalerf9aee3e2017-12-05 12:11:09 -060024import binascii
25import subprocess
26import platform
27import zipfile
Justin Thaler22b1bb52018-03-15 13:31:32 -050028import tarfile
29import tempfile
30import hashlib
Justin Thalerf9aee3e2017-12-05 12:11:09 -060031
Justin Thalerf9aee3e2017-12-05 12:11:09 -060032def hilight(textToColor, color, bold):
Justin Thalere412dc22018-01-12 16:28:24 -060033 """
34 Used to add highlights to various text for displaying in a terminal
35
36 @param textToColor: string, the text to be colored
37 @param color: string, used to color the text red or green
38 @param bold: boolean, used to bold the textToColor
39 @return: Buffered reader containing the modified string.
40 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -060041 if(sys.platform.__contains__("win")):
42 if(color == "red"):
43 os.system('color 04')
44 elif(color == "green"):
45 os.system('color 02')
46 else:
47 os.system('color') #reset to default
48 return textToColor
49 else:
50 attr = []
51 if(color == "red"):
52 attr.append('31')
53 elif(color == "green"):
54 attr.append('32')
55 else:
56 attr.append('0')
57 if bold:
58 attr.append('1')
59 else:
60 attr.append('0')
61 return '\x1b[%sm%s\x1b[0m' % (';'.join(attr),textToColor)
62
Justin Thalere412dc22018-01-12 16:28:24 -060063
Justin Thalerf9aee3e2017-12-05 12:11:09 -060064def connectionErrHandler(jsonFormat, errorStr, err):
Justin Thalere412dc22018-01-12 16:28:24 -060065 """
66 Error handler various connection errors to bmcs
67
68 @param jsonFormat: boolean, used to output in json format with an error code.
69 @param errorStr: string, used to color the text red or green
70 @param err: string, the text from the exception
71 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -060072 if errorStr == "Timeout":
73 if not jsonFormat:
74 return("FQPSPIN0000M: Connection timed out. Ensure you have network connectivity to the bmc")
75 else:
Justin Thaler115bca72018-05-25 19:29:08 -050076 conerror = {}
77 conerror['CommonEventID'] = 'FQPSPIN0000M'
78 conerror['sensor']="N/A"
79 conerror['state']="N/A"
80 conerror['additionalDetails'] = "N/A"
81 conerror['Message']="Connection timed out. Ensure you have network connectivity to the BMC"
82 conerror['LengthyDescription'] = "While trying to establish a connection with the specified BMC, the BMC failed to respond in adequate time. Verify the BMC is functioning properly, and the network connectivity to the BMC is stable."
83 conerror['Serviceable']="Yes"
84 conerror['CallHomeCandidate']= "No"
85 conerror['Severity'] = "Critical"
86 conerror['EventType'] = "Communication Failure/Timeout"
87 conerror['VMMigrationFlag'] = "Yes"
88 conerror["AffectedSubsystem"] = "Interconnect (Networking)"
89 conerror["timestamp"] = str(int(time.time()))
90 conerror["UserAction"] = "Verify network connectivity between the two systems and the bmc is functional."
91 eventdict = {}
92 eventdict['event0'] = conerror
93 eventdict['numAlerts'] = '1'
94
95 errorMessageStr = errorMessageStr = json.dumps(eventdict, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False)
Justin Thalerf9aee3e2017-12-05 12:11:09 -060096 return(errorMessageStr)
97 elif errorStr == "ConnectionError":
98 if not jsonFormat:
99 return("FQPSPIN0001M: " + str(err))
100 else:
Justin Thaler115bca72018-05-25 19:29:08 -0500101 conerror = {}
102 conerror['CommonEventID'] = 'FQPSPIN0001M'
103 conerror['sensor']="N/A"
104 conerror['state']="N/A"
105 conerror['additionalDetails'] = str(err)
106 conerror['Message']="Connection Error. View additional details for more information"
107 conerror['LengthyDescription'] = "A connection error to the specified BMC occurred and additional details are provided. Review these details to resolve the issue."
108 conerror['Serviceable']="Yes"
109 conerror['CallHomeCandidate']= "No"
110 conerror['Severity'] = "Critical"
111 conerror['EventType'] = "Communication Failure/Timeout"
112 conerror['VMMigrationFlag'] = "Yes"
113 conerror["AffectedSubsystem"] = "Interconnect (Networking)"
114 conerror["timestamp"] = str(int(time.time()))
115 conerror["UserAction"] = "Correct the issue highlighted in additional details and try again"
116 eventdict = {}
117 eventdict['event0'] = conerror
118 eventdict['numAlerts'] = '1'
119
120 errorMessageStr = json.dumps(eventdict, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False)
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600121 return(errorMessageStr)
Justin Thaler115bca72018-05-25 19:29:08 -0500122
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600123 else:
124 return("Unknown Error: "+ str(err))
125
Justin Thalere412dc22018-01-12 16:28:24 -0600126
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600127def setColWidth(keylist, numCols, dictForOutput, colNames):
Justin Thalere412dc22018-01-12 16:28:24 -0600128 """
129 Sets the output width of the columns to display
130
131 @param keylist: list, list of strings representing the keys for the dictForOutput
132 @param numcols: the total number of columns in the final output
133 @param dictForOutput: dictionary, contains the information to print to the screen
134 @param colNames: list, The strings to use for the column headings, in order of the keylist
135 @return: A list of the column widths for each respective column.
136 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600137 colWidths = []
138 for x in range(0, numCols):
139 colWidths.append(0)
140 for key in dictForOutput:
141 for x in range(0, numCols):
142 colWidths[x] = max(colWidths[x], len(str(dictForOutput[key][keylist[x]])))
143
144 for x in range(0, numCols):
145 colWidths[x] = max(colWidths[x], len(colNames[x])) +2
146
147 return colWidths
148
149def loadPolicyTable(pathToPolicyTable):
Justin Thalere412dc22018-01-12 16:28:24 -0600150 """
151 loads a json based policy table into a dictionary
152
153 @param value: boolean, the value to convert
154 @return: A string of "Yes" or "No"
155 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600156 policyTable = {}
157 if(os.path.exists(pathToPolicyTable)):
158 with open(pathToPolicyTable, 'r') as stream:
159 try:
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600160 contents =json.load(stream)
161 policyTable = contents['events']
Justin Thalere412dc22018-01-12 16:28:24 -0600162 except Exception as err:
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600163 print(err)
164 return policyTable
165
Justin Thalere412dc22018-01-12 16:28:24 -0600166
167def boolToString(value):
168 """
169 converts a boolean value to a human readable string value
170
171 @param value: boolean, the value to convert
172 @return: A string of "Yes" or "No"
173 """
174 if(value):
175 return "Yes"
176 else:
177 return "No"
178
179
180def tableDisplay(keylist, colNames, output):
181 """
182 Logs into the BMC and creates a session
183
184 @param keylist: list, keys for the output dictionary, ordered by colNames
185 @param colNames: Names for the Table of the columns
186 @param output: The dictionary of data to display
187 @return: Session object
188 """
189 colWidth = setColWidth(keylist, len(colNames), output, colNames)
190 row = ""
191 outputText = ""
192 for i in range(len(colNames)):
193 if (i != 0): row = row + "| "
194 row = row + colNames[i].ljust(colWidth[i])
195 outputText += row + "\n"
196
197 for key in sorted(output.keys()):
198 row = ""
199 for i in range(len(output[key])):
200 if (i != 0): row = row + "| "
201 row = row + output[key][keylist[i]].ljust(colWidth[i])
202 outputText += row + "\n"
203
204 return outputText
205
Justin Thaler22b1bb52018-03-15 13:31:32 -0500206def checkFWactivation(host, args, session):
207 """
208 Checks the software inventory for an image that is being activated.
209
210 @return: True if an image is being activated, false is no activations are happening
211 """
212 url="https://"+host+"/xyz/openbmc_project/software/enumerate"
213 httpHeader = {'Content-Type':'application/json'}
214 try:
215 resp = session.get(url, headers=httpHeader, verify=False, timeout=30)
216 except(requests.exceptions.Timeout):
217 print(connectionErrHandler(args.json, "Timeout", None))
218 return(True)
219 except(requests.exceptions.ConnectionError) as err:
220 print( connectionErrHandler(args.json, "ConnectionError", err))
221 return True
222 fwInfo = json.loads(resp.text)['data']
223 for key in fwInfo:
224 if 'Activation' in fwInfo[key]:
225 if 'Activating' in fwInfo[key]['Activation'] or 'Activating' in fwInfo[key]['RequestedActivation']:
226 return True
227 return False
228
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600229def login(host, username, pw,jsonFormat):
Justin Thalere412dc22018-01-12 16:28:24 -0600230 """
231 Logs into the BMC and creates a session
232
233 @param host: string, the hostname or IP address of the bmc to log into
234 @param username: The user name for the bmc to log into
235 @param pw: The password for the BMC to log into
236 @param jsonFormat: boolean, flag that will only allow relevant data from user command to be display. This function becomes silent when set to true.
237 @return: Session object
238 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600239 if(jsonFormat==False):
240 print("Attempting login...")
241 httpHeader = {'Content-Type':'application/json'}
242 mysess = requests.session()
243 try:
244 r = mysess.post('https://'+host+'/login', headers=httpHeader, json = {"data": [username, pw]}, verify=False, timeout=30)
245 loginMessage = json.loads(r.text)
246 if (loginMessage['status'] != "ok"):
247 print(loginMessage["data"]["description"].encode('utf-8'))
248 sys.exit(1)
249# if(sys.version_info < (3,0)):
250# urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
251# if sys.version_info >= (3,0):
252# requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
253 return mysess
254 except(requests.exceptions.Timeout):
Justin Thaler115bca72018-05-25 19:29:08 -0500255 return (connectionErrHandler(jsonFormat, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600256 except(requests.exceptions.ConnectionError) as err:
Justin Thaler115bca72018-05-25 19:29:08 -0500257 return (connectionErrHandler(jsonFormat, "ConnectionError", err))
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600258
Justin Thalere412dc22018-01-12 16:28:24 -0600259
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600260def logout(host, username, pw, session, jsonFormat):
Justin Thalere412dc22018-01-12 16:28:24 -0600261 """
262 Logs out of the bmc and terminates the session
263
264 @param host: string, the hostname or IP address of the bmc to log out of
265 @param username: The user name for the bmc to log out of
266 @param pw: The password for the BMC to log out of
267 @param session: the active session to use
268 @param jsonFormat: boolean, flag that will only allow relevant data from user command to be display. This function becomes silent when set to true.
269 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600270 httpHeader = {'Content-Type':'application/json'}
Justin Thalere412dc22018-01-12 16:28:24 -0600271 try:
272 r = session.post('https://'+host+'/logout', headers=httpHeader,json = {"data": [username, pw]}, verify=False, timeout=10)
273 except(requests.exceptions.Timeout):
274 print(connectionErrHandler(jsonFormat, "Timeout", None))
275
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600276 if(jsonFormat==False):
277 if('"message": "200 OK"' in r.text):
278 print('User ' +username + ' has been logged out')
279
Justin Thalere412dc22018-01-12 16:28:24 -0600280
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600281def fru(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600282 """
283 prints out the system inventory. deprecated see fruPrint and fruList
284
285 @param host: string, the hostname or IP address of the bmc
286 @param args: contains additional arguments used by the fru sub command
287 @param session: the active session to use
288 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
289 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600290 #url="https://"+host+"/org/openbmc/inventory/system/chassis/enumerate"
291
292 #print(url)
293 #res = session.get(url, headers=httpHeader, verify=False)
294 #print(res.text)
295 #sample = res.text
296
297 #inv_list = json.loads(sample)["data"]
298
299 url="https://"+host+"/xyz/openbmc_project/inventory/enumerate"
300 httpHeader = {'Content-Type':'application/json'}
Justin Thalere412dc22018-01-12 16:28:24 -0600301 try:
302 res = session.get(url, headers=httpHeader, verify=False, timeout=40)
303 except(requests.exceptions.Timeout):
304 return(connectionErrHandler(args.json, "Timeout", None))
305
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600306 sample = res.text
307# inv_list.update(json.loads(sample)["data"])
308#
309# #determine column width's
310# colNames = ["FRU Name", "FRU Type", "Has Fault", "Is FRU", "Present", "Version"]
311# colWidths = setColWidth(["FRU Name", "fru_type", "fault", "is_fru", "present", "version"], 6, inv_list, colNames)
312#
313# print("FRU Name".ljust(colWidths[0])+ "FRU Type".ljust(colWidths[1]) + "Has Fault".ljust(colWidths[2]) + "Is FRU".ljust(colWidths[3])+
314# "Present".ljust(colWidths[4]) + "Version".ljust(colWidths[5]))
315# format the output
316# for key in sorted(inv_list.keys()):
317# keyParts = key.split("/")
318# isFRU = "True" if (inv_list[key]["is_fru"]==1) else "False"
319#
320# fruEntry = (keyParts[len(keyParts) - 1].ljust(colWidths[0]) + inv_list[key]["fru_type"].ljust(colWidths[1])+
321# inv_list[key]["fault"].ljust(colWidths[2])+isFRU.ljust(colWidths[3])+
322# inv_list[key]["present"].ljust(colWidths[4])+ inv_list[key]["version"].ljust(colWidths[5]))
323# if(isTTY):
324# if(inv_list[key]["is_fru"] == 1):
325# color = "green"
326# bold = True
327# else:
328# color='black'
329# bold = False
330# fruEntry = hilight(fruEntry, color, bold)
331# print (fruEntry)
332 return sample
Justin Thalere412dc22018-01-12 16:28:24 -0600333
334def fruPrint(host, args, session):
335 """
336 prints out all inventory
337
338 @param host: string, the hostname or IP address of the bmc
339 @param args: contains additional arguments used by the fru sub command
340 @param session: the active session to use
341 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
342 @return returns the total fru list.
343 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600344 url="https://"+host+"/xyz/openbmc_project/inventory/enumerate"
345 httpHeader = {'Content-Type':'application/json'}
Justin Thalere412dc22018-01-12 16:28:24 -0600346 try:
347 res = session.get(url, headers=httpHeader, verify=False, timeout=40)
348 except(requests.exceptions.Timeout):
349 return(connectionErrHandler(args.json, "Timeout", None))
350
351
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600352# print(res.text)
353 frulist = res.text
354 url="https://"+host+"/xyz/openbmc_project/software/enumerate"
Justin Thalere412dc22018-01-12 16:28:24 -0600355 try:
356 res = session.get(url, headers=httpHeader, verify=False, timeout=40)
357 except(requests.exceptions.Timeout):
358 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600359# print(res.text)
360 frulist = frulist +"\n" + res.text
361
362 return frulist
363
Justin Thalere412dc22018-01-12 16:28:24 -0600364
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600365def fruList(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600366 """
367 prints out all inventory or only a specific specified item
368
369 @param host: string, the hostname or IP address of the bmc
370 @param args: contains additional arguments used by the fru sub command
371 @param session: the active session to use
372 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
373 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600374 if(args.items==True):
375 return fruPrint(host, args, session)
376 else:
Justin Thalere412dc22018-01-12 16:28:24 -0600377 return fruPrint(host, args, session)
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600378
379
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600380
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600381def fruStatus(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600382 """
383 prints out the status of all FRUs
384
385 @param host: string, the hostname or IP address of the bmc
386 @param args: contains additional arguments used by the fru sub command
387 @param session: the active session to use
388 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
389 """
390 url="https://"+host+"/xyz/openbmc_project/inventory/enumerate"
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600391 httpHeader = {'Content-Type':'application/json'}
Justin Thalere412dc22018-01-12 16:28:24 -0600392 try:
393 res = session.get(url, headers=httpHeader, verify=False)
394 except(requests.exceptions.Timeout):
395 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600396# print(res.text)
Justin Thalere412dc22018-01-12 16:28:24 -0600397 frulist = json.loads(res.text)['data']
398 frus = {}
399 for key in frulist:
400 component = frulist[key]
401 isFru = False
402 present = False
403 func = False
404 hasSels = False
405 keyPieces = key.split('/')
406 fruName = keyPieces[-1]
407 if 'core' in fruName: #associate cores to cpus
408 fruName = keyPieces[-2] + '-' + keyPieces[-1]
409 if 'Functional' in component:
410 if('Present' in component):
411
412 if 'FieldReplaceable' in component:
413 if component['FieldReplaceable'] == 1:
414 isFru = True
415 if "fan" in fruName:
416 isFru = True;
417 if component['Present'] == 1:
418 present = True
419 if component['Functional'] == 1:
420 func = True
421 if ((key + "/fault") in frulist):
422 hasSels = True;
423 if args.verbose:
424 if hasSels:
425 loglist = []
426 faults = frulist[key+"/fault"]['endpoints']
427 for item in faults:
428 loglist.append(item.split('/')[-1])
429 frus[fruName] = {"compName": fruName, "Functional": boolToString(func), "Present":boolToString(present), "IsFru": boolToString(isFru), "selList": ', '.join(loglist).strip() }
430 else:
431 frus[fruName] = {"compName": fruName, "Functional": boolToString(func), "Present":boolToString(present), "IsFru": boolToString(isFru), "selList": "None" }
432 else:
433 frus[fruName] = {"compName": fruName, "Functional": boolToString(func), "Present":boolToString(present), "IsFru": boolToString(isFru), "hasSEL": boolToString(hasSels) }
434 elif "power_supply" in fruName:
435 if component['Present'] ==1:
436 present = True
437 isFru = True
438 if ((key + "/fault") in frulist):
439 hasSels = True;
440 if args.verbose:
441 if hasSels:
442 loglist = []
443 faults = frulist[key+"/fault"]['endpoints']
444 for key in faults:
445 loglist.append(faults[key].split('/')[-1])
446 frus[fruName] = {"compName": fruName, "Functional": "No", "Present":boolToString(present), "IsFru": boolToString(isFru), "selList": ', '.join(loglist).strip() }
447 else:
448 frus[fruName] = {"compName": fruName, "Functional": "Yes", "Present":boolToString(present), "IsFru": boolToString(isFru), "selList": "None" }
449 else:
450 frus[fruName] = {"compName": fruName, "Functional": boolToString(not hasSels), "Present":boolToString(present), "IsFru": boolToString(isFru), "hasSEL": boolToString(hasSels) }
451 if not args.json:
452 if not args.verbose:
453 colNames = ["Component", "Is a FRU", "Present", "Functional", "Has Logs"]
454 keylist = ["compName", "IsFru", "Present", "Functional", "hasSEL"]
455 else:
456 colNames = ["Component", "Is a FRU", "Present", "Functional", "Assoc. Log Number(s)"]
457 keylist = ["compName", "IsFru", "Present", "Functional", "selList"]
458 return tableDisplay(keylist, colNames, frus)
459 else:
460 return str(json.dumps(frus, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False))
461
462def sensor(host, args, session):
463 """
464 prints out all sensors
465
466 @param host: string, the hostname or IP address of the bmc
467 @param args: contains additional arguments used by the sensor sub command
468 @param session: the active session to use
469 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
470 """
471 httpHeader = {'Content-Type':'application/json'}
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600472 url="https://"+host+"/xyz/openbmc_project/sensors/enumerate"
Justin Thalere412dc22018-01-12 16:28:24 -0600473 try:
474 res = session.get(url, headers=httpHeader, verify=False, timeout=30)
475 except(requests.exceptions.Timeout):
476 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600477
478 #Get OCC status
479 url="https://"+host+"/org/open_power/control/enumerate"
Justin Thalere412dc22018-01-12 16:28:24 -0600480 try:
481 occres = session.get(url, headers=httpHeader, verify=False, timeout=30)
482 except(requests.exceptions.Timeout):
483 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600484 if not args.json:
485 colNames = ['sensor', 'type', 'units', 'value', 'target']
486 sensors = json.loads(res.text)["data"]
487 output = {}
488 for key in sensors:
489 senDict = {}
490 keyparts = key.split("/")
491 senDict['sensorName'] = keyparts[-1]
492 senDict['type'] = keyparts[-2]
Justin Thalere412dc22018-01-12 16:28:24 -0600493 try:
494 senDict['units'] = sensors[key]['Unit'].split('.')[-1]
495 except KeyError:
Justin Thaler22b1bb52018-03-15 13:31:32 -0500496 senDict['units'] = "N/A"
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600497 if('Scale' in sensors[key]):
498 scale = 10 ** sensors[key]['Scale']
499 else:
500 scale = 1
Justin Thaler22b1bb52018-03-15 13:31:32 -0500501 try:
502 senDict['value'] = str(sensors[key]['Value'] * scale)
503 except KeyError:
504 if 'value' in sensors[key]:
505 senDict['value'] = sensors[key]['value']
506 else:
507 senDict['value'] = "N/A"
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600508 if 'Target' in sensors[key]:
509 senDict['target'] = str(sensors[key]['Target'])
510 else:
511 senDict['target'] = 'N/A'
512 output[senDict['sensorName']] = senDict
513
514 occstatus = json.loads(occres.text)["data"]
515 if '/org/open_power/control/occ0' in occstatus:
516 occ0 = occstatus["/org/open_power/control/occ0"]['OccActive']
517 if occ0 == 1:
518 occ0 = 'Active'
519 else:
520 occ0 = 'Inactive'
521 output['OCC0'] = {'sensorName':'OCC0', 'type': 'Discrete', 'units': 'N/A', 'value': occ0, 'target': 'Active'}
522 occ1 = occstatus["/org/open_power/control/occ1"]['OccActive']
523 if occ1 == 1:
524 occ1 = 'Active'
525 else:
526 occ1 = 'Inactive'
527 output['OCC1'] = {'sensorName':'OCC1', 'type': 'Discrete', 'units': 'N/A', 'value': occ0, 'target': 'Active'}
528 else:
529 output['OCC0'] = {'sensorName':'OCC0', 'type': 'Discrete', 'units': 'N/A', 'value': 'Inactive', 'target': 'Inactive'}
530 output['OCC1'] = {'sensorName':'OCC1', 'type': 'Discrete', 'units': 'N/A', 'value': 'Inactive', 'target': 'Inactive'}
531 keylist = ['sensorName', 'type', 'units', 'value', 'target']
Justin Thalere412dc22018-01-12 16:28:24 -0600532
533 return tableDisplay(keylist, colNames, output)
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600534 else:
535 return res.text + occres.text
Justin Thalere412dc22018-01-12 16:28:24 -0600536
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600537def sel(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600538 """
539 prints out the bmc alerts
540
541 @param host: string, the hostname or IP address of the bmc
542 @param args: contains additional arguments used by the sel sub command
543 @param session: the active session to use
544 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
545 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600546
547 url="https://"+host+"/xyz/openbmc_project/logging/entry/enumerate"
548 httpHeader = {'Content-Type':'application/json'}
Justin Thalere412dc22018-01-12 16:28:24 -0600549 try:
550 res = session.get(url, headers=httpHeader, verify=False, timeout=60)
551 except(requests.exceptions.Timeout):
552 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600553 return res.text
Justin Thalere412dc22018-01-12 16:28:24 -0600554
555
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600556def parseESEL(args, eselRAW):
Justin Thalere412dc22018-01-12 16:28:24 -0600557 """
558 parses the esel data and gets predetermined search terms
559
560 @param eselRAW: string, the raw esel string from the bmc
561 @return: A dictionary containing the quick snapshot data unless args.fullEsel is listed then a full PEL log is returned
562 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600563 eselParts = {}
564 esel_bin = binascii.unhexlify(''.join(eselRAW.split()[16:]))
565 #search terms contains the search term as the key and the return dictionary key as it's value
566 searchTerms = { 'Signature Description':'signatureDescription', 'devdesc':'devdesc',
Justin Thaler22b1bb52018-03-15 13:31:32 -0500567 'Callout type': 'calloutType', 'Procedure':'procedure', 'Sensor Type': 'sensorType'}
Justin Thalercf1deae2018-05-25 19:35:21 -0500568 eselBinPath = tempfile.gettempdir() + os.sep + 'esel.bin'
569 with open(eselBinPath, 'wb') as f:
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600570 f.write(esel_bin)
571 errlPath = ""
572 #use the right errl file for the machine architecture
573 arch = platform.machine()
574 if(arch =='x86_64' or arch =='AMD64'):
575 if os.path.exists('/opt/ibm/ras/bin/x86_64/errl'):
576 errlPath = '/opt/ibm/ras/bin/x86_64/errl'
577 elif os.path.exists('errl/x86_64/errl'):
578 errlPath = 'errl/x86_64/errl'
579 else:
580 errlPath = 'x86_64/errl'
581 elif (platform.machine()=='ppc64le'):
582 if os.path.exists('/opt/ibm/ras/bin/ppc64le/errl'):
583 errlPath = '/opt/ibm/ras/bin/ppc64le/errl'
584 elif os.path.exists('errl/ppc64le/errl'):
585 errlPath = 'errl/ppc64le/errl'
586 else:
587 errlPath = 'ppc64le/errl'
588 else:
589 print("machine architecture not supported for parsing eSELs")
590 return eselParts
591
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600592 if(os.path.exists(errlPath)):
Justin Thalercf1deae2018-05-25 19:35:21 -0500593 output= subprocess.check_output([errlPath, '-d', '--file='+eselBinPath]).decode('utf-8')
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600594# output = proc.communicate()[0]
595 lines = output.split('\n')
596
597 if(hasattr(args, 'fullEsel')):
598 return output
599
600 for i in range(0, len(lines)):
601 lineParts = lines[i].split(':')
602 if(len(lineParts)>1): #ignore multi lines, output formatting lines, and other information
603 for term in searchTerms:
604 if(term in lineParts[0]):
605 temp = lines[i][lines[i].find(':')+1:].strip()[:-1].strip()
606 if lines[i+1].find(':') != -1:
607 if (len(lines[i+1].split(':')[0][1:].strip())==0):
608 while(len(lines[i][:lines[i].find(':')].strip())>2):
609 if((i+1) <= len(lines)):
610 i+=1
611 else:
612 i=i-1
613 break
614 temp = temp + lines[i][lines[i].find(':'):].strip()[:-1].strip()[:-1].strip()
Justin Thaler22b1bb52018-03-15 13:31:32 -0500615 if(searchTerms[term] in eselParts):
616 eselParts[searchTerms[term]] = eselParts[searchTerms[term]] + ", " + temp
617 else:
618 eselParts[searchTerms[term]] = temp
Justin Thalercf1deae2018-05-25 19:35:21 -0500619 os.remove(eselBinPath)
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600620 else:
621 print("errl file cannot be found")
622
623 return eselParts
624
Justin Thalere412dc22018-01-12 16:28:24 -0600625
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600626def sortSELs(events):
Justin Thalere412dc22018-01-12 16:28:24 -0600627 """
628 sorts the sels by timestamp, then log entry number
629
630 @param events: Dictionary containing events
631 @return: list containing a list of the ordered log entries, and dictionary of keys
632 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600633 logNumList = []
634 timestampList = []
635 eventKeyDict = {}
636 eventsWithTimestamp = {}
637 logNum2events = {}
638 for key in events:
639 if key == 'numAlerts': continue
640 if 'callout' in key: continue
641 timestamp = (events[key]['timestamp'])
642 if timestamp not in timestampList:
643 eventsWithTimestamp[timestamp] = [events[key]['logNum']]
644 else:
645 eventsWithTimestamp[timestamp].append(events[key]['logNum'])
646 #map logNumbers to the event dictionary keys
647 eventKeyDict[str(events[key]['logNum'])] = key
648
649 timestampList = list(eventsWithTimestamp.keys())
650 timestampList.sort()
651 for ts in timestampList:
652 if len(eventsWithTimestamp[ts]) > 1:
653 tmplist = eventsWithTimestamp[ts]
654 tmplist.sort()
655 logNumList = logNumList + tmplist
656 else:
657 logNumList = logNumList + eventsWithTimestamp[ts]
658
659 return [logNumList, eventKeyDict]
660
Justin Thalere412dc22018-01-12 16:28:24 -0600661
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600662def parseAlerts(policyTable, selEntries, args):
Justin Thalere412dc22018-01-12 16:28:24 -0600663 """
664 parses alerts in the IBM CER format, using an IBM policy Table
665
666 @param policyTable: dictionary, the policy table entries
667 @param selEntries: dictionary, the alerts retrieved from the bmc
668 @return: A dictionary of the parsed entries, in chronological order
669 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600670 eventDict = {}
671 eventNum =""
672 count = 0
673 esel = ""
674 eselParts = {}
675 i2cdevice= ""
676
677 'prepare and sort the event entries'
678 for key in selEntries:
679 if 'callout' not in key:
680 selEntries[key]['logNum'] = key.split('/')[-1]
681 selEntries[key]['timestamp'] = selEntries[key]['Timestamp']
682 sortedEntries = sortSELs(selEntries)
683 logNumList = sortedEntries[0]
684 eventKeyDict = sortedEntries[1]
685
686 for logNum in logNumList:
687 key = eventKeyDict[logNum]
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600688 hasEsel=False
689 i2creadFail = False
690 if 'callout' in key:
691 continue
692 else:
693 messageID = str(selEntries[key]['Message'])
694 addDataPiece = selEntries[key]['AdditionalData']
695 calloutIndex = 0
696 calloutFound = False
697 for i in range(len(addDataPiece)):
698 if("CALLOUT_INVENTORY_PATH" in addDataPiece[i]):
699 calloutIndex = i
700 calloutFound = True
701 fruCallout = str(addDataPiece[calloutIndex]).split('=')[1]
702 if("CALLOUT_DEVICE_PATH" in addDataPiece[i]):
703 i2creadFail = True
704 i2cdevice = str(addDataPiece[i]).strip().split('=')[1]
705 i2cdevice = '/'.join(i2cdevice.split('/')[-4:])
706 fruCallout = 'I2C'
707 calloutFound = True
708 if("ESEL" in addDataPiece[i]):
709 esel = str(addDataPiece[i]).strip().split('=')[1]
710 if args.devdebug:
711 eselParts = parseESEL(args, esel)
712 hasEsel=True
713 if("GPU" in addDataPiece[i]):
714 fruCallout = '/xyz/openbmc_project/inventory/system/chassis/motherboard/gpu' + str(addDataPiece[i]).strip()[-1]
715 calloutFound = True
716 if("PROCEDURE" in addDataPiece[i]):
717 fruCallout = str(hex(int(str(addDataPiece[i]).split('=')[1])))[2:]
718 calloutFound = True
Justin Thalere412dc22018-01-12 16:28:24 -0600719 if("RAIL_NAME" in addDataPiece[i]):
720 calloutFound=True
721 fruCallout = str(addDataPiece[i]).split('=')[1].strip()
722 if("INPUT_NAME" in addDataPiece[i]):
723 calloutFound=True
724 fruCallout = str(addDataPiece[i]).split('=')[1].strip()
725 if("SENSOR_TYPE" in addDataPiece[i]):
726 calloutFound=True
727 fruCallout = str(addDataPiece[i]).split('=')[1].strip()
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600728
729 if(calloutFound):
Justin Thaler22b1bb52018-03-15 13:31:32 -0500730 if fruCallout != "":
731 policyKey = messageID +"||" + fruCallout
732 else:
733 policyKey = messageID
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600734 else:
735 policyKey = messageID
736 event = {}
737 eventNum = str(count)
738 if policyKey in policyTable:
739 for pkey in policyTable[policyKey]:
740 if(type(policyTable[policyKey][pkey])== bool):
741 event[pkey] = boolToString(policyTable[policyKey][pkey])
742 else:
743 if (i2creadFail and pkey == 'Message'):
744 event[pkey] = policyTable[policyKey][pkey] + ' ' +i2cdevice
745 else:
746 event[pkey] = policyTable[policyKey][pkey]
747 event['timestamp'] = selEntries[key]['Timestamp']
748 event['resolved'] = bool(selEntries[key]['Resolved'])
749 if(hasEsel):
750 if args.devdebug:
751 event['eselParts'] = eselParts
752 event['raweSEL'] = esel
753 event['logNum'] = key.split('/')[-1]
754 eventDict['event' + eventNum] = event
755
756 else:
757 severity = str(selEntries[key]['Severity']).split('.')[-1]
758 if severity == 'Error':
759 severity = 'Critical'
760 eventDict['event'+eventNum] = {}
761 eventDict['event' + eventNum]['error'] = "error: Not found in policy table: " + policyKey
762 eventDict['event' + eventNum]['timestamp'] = selEntries[key]['Timestamp']
763 eventDict['event' + eventNum]['Severity'] = severity
764 if(hasEsel):
765 if args.devdebug:
766 eventDict['event' +eventNum]['eselParts'] = eselParts
767 eventDict['event' +eventNum]['raweSEL'] = esel
768 eventDict['event' +eventNum]['logNum'] = key.split('/')[-1]
769 eventDict['event' +eventNum]['resolved'] = bool(selEntries[key]['Resolved'])
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600770 count += 1
771 return eventDict
772
773
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600774def selDisplay(events, args):
Justin Thalere412dc22018-01-12 16:28:24 -0600775 """
776 displays alerts in human readable format
777
778 @param events: Dictionary containing events
779 @return:
780 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600781 activeAlerts = []
782 historyAlerts = []
783 sortedEntries = sortSELs(events)
784 logNumList = sortedEntries[0]
785 eventKeyDict = sortedEntries[1]
786 keylist = ['Entry', 'ID', 'Timestamp', 'Serviceable', 'Severity','Message']
787 if(args.devdebug):
788 colNames = ['Entry', 'ID', 'Timestamp', 'Serviceable', 'Severity','Message', 'eSEL contents']
789 keylist.append('eSEL')
790 else:
791 colNames = ['Entry', 'ID', 'Timestamp', 'Serviceable', 'Severity', 'Message']
792 for log in logNumList:
793 selDict = {}
794 alert = events[eventKeyDict[str(log)]]
795 if('error' in alert):
796 selDict['Entry'] = alert['logNum']
797 selDict['ID'] = 'Unknown'
798 selDict['Timestamp'] = datetime.datetime.fromtimestamp(int(alert['timestamp']/1000)).strftime("%Y-%m-%d %H:%M:%S")
799 msg = alert['error']
800 polMsg = msg.split("policy table:")[0]
801 msg = msg.split("policy table:")[1]
802 msgPieces = msg.split("||")
803 err = msgPieces[0]
804 if(err.find("org.open_power.")!=-1):
805 err = err.split("org.open_power.")[1]
806 elif(err.find("xyz.openbmc_project.")!=-1):
807 err = err.split("xyz.openbmc_project.")[1]
808 else:
809 err = msgPieces[0]
810 callout = ""
811 if len(msgPieces) >1:
812 callout = msgPieces[1]
813 if(callout.find("/org/open_power/")!=-1):
814 callout = callout.split("/org/open_power/")[1]
815 elif(callout.find("/xyz/openbmc_project/")!=-1):
816 callout = callout.split("/xyz/openbmc_project/")[1]
817 else:
818 callout = msgPieces[1]
819 selDict['Message'] = polMsg +"policy table: "+ err + "||" + callout
820 selDict['Serviceable'] = 'Unknown'
821 selDict['Severity'] = alert['Severity']
822 else:
823 selDict['Entry'] = alert['logNum']
824 selDict['ID'] = alert['CommonEventID']
825 selDict['Timestamp'] = datetime.datetime.fromtimestamp(int(alert['timestamp']/1000)).strftime("%Y-%m-%d %H:%M:%S")
826 selDict['Message'] = alert['Message']
827 selDict['Serviceable'] = alert['Serviceable']
828 selDict['Severity'] = alert['Severity']
829
830
831 eselOrder = ['refCode','signatureDescription', 'eselType', 'devdesc', 'calloutType', 'procedure']
832 if ('eselParts' in alert and args.devdebug):
833 eselOutput = ""
834 for item in eselOrder:
835 if item in alert['eselParts']:
836 eselOutput = eselOutput + item + ": " + alert['eselParts'][item] + " | "
837 selDict['eSEL'] = eselOutput
838 else:
839 if args.devdebug:
840 selDict['eSEL'] = "None"
841
842 if not alert['resolved']:
843 activeAlerts.append(selDict)
844 else:
845 historyAlerts.append(selDict)
846 mergedOutput = activeAlerts + historyAlerts
847 colWidth = setColWidth(keylist, len(colNames), dict(enumerate(mergedOutput)), colNames)
848
849 output = ""
850 if(len(activeAlerts)>0):
851 row = ""
852 output +="----Active Alerts----\n"
853 for i in range(0, len(colNames)):
854 if i!=0: row =row + "| "
855 row = row + colNames[i].ljust(colWidth[i])
856 output += row + "\n"
857
858 for i in range(0,len(activeAlerts)):
859 row = ""
860 for j in range(len(activeAlerts[i])):
861 if (j != 0): row = row + "| "
862 row = row + activeAlerts[i][keylist[j]].ljust(colWidth[j])
863 output += row + "\n"
864
865 if(len(historyAlerts)>0):
866 row = ""
867 output+= "----Historical Alerts----\n"
868 for i in range(len(colNames)):
869 if i!=0: row =row + "| "
870 row = row + colNames[i].ljust(colWidth[i])
871 output += row + "\n"
872
873 for i in range(0, len(historyAlerts)):
874 row = ""
875 for j in range(len(historyAlerts[i])):
876 if (j != 0): row = row + "| "
877 row = row + historyAlerts[i][keylist[j]].ljust(colWidth[j])
878 output += row + "\n"
879# print(events[eventKeyDict[str(log)]])
880 return output
881
Justin Thalere412dc22018-01-12 16:28:24 -0600882
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600883def selPrint(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600884 """
885 prints out all bmc alerts
886
887 @param host: string, the hostname or IP address of the bmc
888 @param args: contains additional arguments used by the fru sub command
889 @param session: the active session to use
890 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
891 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600892 if(args.policyTableLoc is None):
893 if os.path.exists('policyTable.json'):
894 ptableLoc = "policyTable.json"
895 elif os.path.exists('/opt/ibm/ras/lib/policyTable.json'):
896 ptableLoc = '/opt/ibm/ras/lib/policyTable.json'
897 else:
898 ptableLoc = 'lib/policyTable.json'
899 else:
900 ptableLoc = args.policyTableLoc
901 policyTable = loadPolicyTable(ptableLoc)
902 rawselEntries = ""
903 if(hasattr(args, 'fileloc') and args.fileloc is not None):
904 if os.path.exists(args.fileloc):
905 with open(args.fileloc, 'r') as selFile:
906 selLines = selFile.readlines()
907 rawselEntries = ''.join(selLines)
908 else:
909 print("Error: File not found")
910 sys.exit(1)
911 else:
912 rawselEntries = sel(host, args, session)
913 loadFailed = False
914 try:
915 selEntries = json.loads(rawselEntries)
916 except ValueError:
917 loadFailed = True
918 if loadFailed:
919 cleanSels = json.dumps(rawselEntries).replace('\\n', '')
920 #need to load json twice as original content was string escaped a second time
921 selEntries = json.loads(json.loads(cleanSels))
922 selEntries = selEntries['data']
Justin Thalere412dc22018-01-12 16:28:24 -0600923
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600924 if 'description' in selEntries:
925 if(args.json):
926 return("{\n\t\"numAlerts\": 0\n}")
927 else:
928 return("No log entries found")
929
930 else:
931 if(len(policyTable)>0):
932 events = parseAlerts(policyTable, selEntries, args)
933 if(args.json):
934 events["numAlerts"] = len(events)
935 retValue = str(json.dumps(events, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False))
936 return retValue
937 elif(hasattr(args, 'fullSel')):
938 return events
939 else:
940 #get log numbers to order event entries sequentially
941 return selDisplay(events, args)
942 else:
943 if(args.json):
944 return selEntries
945 else:
946 print("error: Policy Table not found.")
947 return selEntries
Justin Thalere412dc22018-01-12 16:28:24 -0600948
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600949def selList(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600950 """
951 prints out all all bmc alerts, or only prints out the specified alerts
952
953 @param host: string, the hostname or IP address of the bmc
954 @param args: contains additional arguments used by the fru sub command
955 @param session: the active session to use
956 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
957 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600958 return(sel(host, args, session))
959
Justin Thalere412dc22018-01-12 16:28:24 -0600960
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600961def selClear(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600962 """
963 clears all alerts
964
965 @param host: string, the hostname or IP address of the bmc
966 @param args: contains additional arguments used by the fru sub command
967 @param session: the active session to use
968 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
969 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600970 url="https://"+host+"/xyz/openbmc_project/logging/action/deleteAll"
971 httpHeader = {'Content-Type':'application/json'}
972 data = "{\"data\": [] }"
Justin Thalere412dc22018-01-12 16:28:24 -0600973
974 try:
975 res = session.post(url, headers=httpHeader, data=data, verify=False, timeout=30)
976 except(requests.exceptions.Timeout):
977 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600978 if res.status_code == 200:
979 return "The Alert Log has been cleared. Please allow a few minutes for the action to complete."
980 else:
981 print("Unable to clear the logs, trying to clear 1 at a time")
982 sels = json.loads(sel(host, args, session))['data']
983 for key in sels:
984 if 'callout' not in key:
985 logNum = key.split('/')[-1]
986 url = "https://"+ host+ "/xyz/openbmc_project/logging/entry/"+logNum+"/action/Delete"
987 try:
988 session.post(url, headers=httpHeader, data=data, verify=False, timeout=30)
989 except(requests.exceptions.Timeout):
990 return connectionErrHandler(args.json, "Timeout", None)
991 sys.exit(1)
992 except(requests.exceptions.ConnectionError) as err:
993 return connectionErrHandler(args.json, "ConnectionError", err)
994 sys.exit(1)
995 return ('Sel clearing complete')
996
997def selSetResolved(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600998 """
999 sets a sel entry to resolved
1000
1001 @param host: string, the hostname or IP address of the bmc
1002 @param args: contains additional arguments used by the fru sub command
1003 @param session: the active session to use
1004 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1005 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001006 url="https://"+host+"/xyz/openbmc_project/logging/entry/" + str(args.selNum) + "/attr/Resolved"
1007 httpHeader = {'Content-Type':'application/json'}
1008 data = "{\"data\": 1 }"
Justin Thalere412dc22018-01-12 16:28:24 -06001009 try:
1010 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1011 except(requests.exceptions.Timeout):
1012 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001013 if res.status_code == 200:
1014 return "Sel entry "+ str(args.selNum) +" is now set to resolved"
1015 else:
1016 return "Unable to set the alert to resolved"
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001017
Justin Thalere412dc22018-01-12 16:28:24 -06001018def selResolveAll(host, args, session):
1019 """
1020 sets a sel entry to resolved
1021
1022 @param host: string, the hostname or IP address of the bmc
1023 @param args: contains additional arguments used by the fru sub command
1024 @param session: the active session to use
1025 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1026 """
1027 rawselEntries = sel(host, args, session)
1028 loadFailed = False
1029 try:
1030 selEntries = json.loads(rawselEntries)
1031 except ValueError:
1032 loadFailed = True
1033 if loadFailed:
1034 cleanSels = json.dumps(rawselEntries).replace('\\n', '')
1035 #need to load json twice as original content was string escaped a second time
1036 selEntries = json.loads(json.loads(cleanSels))
1037 selEntries = selEntries['data']
1038
1039 if 'description' in selEntries:
1040 if(args.json):
1041 return("{\n\t\"selsResolved\": 0\n}")
1042 else:
1043 return("No log entries found")
1044 else:
1045 d = vars(args)
1046 successlist = []
1047 failedlist = []
1048 for key in selEntries:
1049 if 'callout' not in key:
1050 d['selNum'] = key.split('/')[-1]
1051 resolved = selSetResolved(host,args,session)
1052 if 'Sel entry' in resolved:
1053 successlist.append(d['selNum'])
1054 else:
1055 failedlist.append(d['selNum'])
1056 output = ""
1057 successlist.sort()
1058 failedlist.sort()
1059 if len(successlist)>0:
1060 output = "Successfully resolved: " +', '.join(successlist) +"\n"
1061 if len(failedlist)>0:
1062 output += "Failed to resolve: " + ', '.join(failedlist) + "\n"
1063 return output
1064
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001065def chassisPower(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001066 """
1067 called by the chassis function. Controls the power state of the chassis, or gets the status
1068
1069 @param host: string, the hostname or IP address of the bmc
1070 @param args: contains additional arguments used by the fru sub command
1071 @param session: the active session to use
1072 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1073 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001074 if(args.powcmd == 'on'):
Justin Thaler22b1bb52018-03-15 13:31:32 -05001075 if checkFWactivation(host, args, session):
1076 return ("Chassis Power control disabled during firmware activation")
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001077 print("Attempting to Power on...:")
1078 url="https://"+host+"/xyz/openbmc_project/state/host0/attr/RequestedHostTransition"
1079 httpHeader = {'Content-Type':'application/json',}
1080 data = '{"data":"xyz.openbmc_project.State.Host.Transition.On"}'
Justin Thalere412dc22018-01-12 16:28:24 -06001081 try:
1082 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1083 except(requests.exceptions.Timeout):
1084 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001085 return res.text
Justin Thalere412dc22018-01-12 16:28:24 -06001086 elif(args.powcmd == 'softoff'):
Justin Thaler22b1bb52018-03-15 13:31:32 -05001087 if checkFWactivation(host, args, session):
1088 return ("Chassis Power control disabled during firmware activation")
Justin Thalere412dc22018-01-12 16:28:24 -06001089 print("Attempting to Power off gracefully...:")
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001090 url="https://"+host+"/xyz/openbmc_project/state/host0/attr/RequestedHostTransition"
1091 httpHeader = {'Content-Type':'application/json'}
1092 data = '{"data":"xyz.openbmc_project.State.Host.Transition.Off"}'
Justin Thalere412dc22018-01-12 16:28:24 -06001093 try:
1094 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1095 except(requests.exceptions.Timeout):
1096 return(connectionErrHandler(args.json, "Timeout", None))
1097 return res.text
1098 elif(args.powcmd == 'hardoff'):
Justin Thaler22b1bb52018-03-15 13:31:32 -05001099 if checkFWactivation(host, args, session):
1100 return ("Chassis Power control disabled during firmware activation")
Justin Thalere412dc22018-01-12 16:28:24 -06001101 print("Attempting to Power off immediately...:")
1102 url="https://"+host+"/xyz/openbmc_project/state/chassis0/attr/RequestedPowerTransition"
1103 httpHeader = {'Content-Type':'application/json'}
1104 data = '{"data":"xyz.openbmc_project.State.Chassis.Transition.Off"}'
1105 try:
1106 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1107 except(requests.exceptions.Timeout):
1108 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001109 return res.text
1110 elif(args.powcmd == 'status'):
1111 url="https://"+host+"/xyz/openbmc_project/state/chassis0/attr/CurrentPowerState"
1112 httpHeader = {'Content-Type':'application/json'}
1113# print(url)
Justin Thalere412dc22018-01-12 16:28:24 -06001114 try:
1115 res = session.get(url, headers=httpHeader, verify=False, timeout=30)
1116 except(requests.exceptions.Timeout):
1117 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001118 chassisState = json.loads(res.text)['data'].split('.')[-1]
1119 url="https://"+host+"/xyz/openbmc_project/state/host0/attr/CurrentHostState"
Justin Thalere412dc22018-01-12 16:28:24 -06001120 try:
1121 res = session.get(url, headers=httpHeader, verify=False, timeout=30)
1122 except(requests.exceptions.Timeout):
1123 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001124 hostState = json.loads(res.text)['data'].split('.')[-1]
1125 url="https://"+host+"/xyz/openbmc_project/state/bmc0/attr/CurrentBMCState"
Justin Thalere412dc22018-01-12 16:28:24 -06001126 try:
1127 res = session.get(url, headers=httpHeader, verify=False, timeout=30)
1128 except(requests.exceptions.Timeout):
1129 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001130 bmcState = json.loads(res.text)['data'].split('.')[-1]
1131 if(args.json):
1132 outDict = {"Chassis Power State" : chassisState, "Host Power State" : hostState, "BMC Power State":bmcState}
1133 return json.dumps(outDict, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False)
1134 else:
1135 return "Chassis Power State: " +chassisState + "\nHost Power State: " + hostState + "\nBMC Power State: " + bmcState
1136 else:
1137 return "Invalid chassis power command"
1138
Justin Thalere412dc22018-01-12 16:28:24 -06001139
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001140def chassisIdent(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001141 """
1142 called by the chassis function. Controls the identify led of the chassis. Sets or gets the state
1143
1144 @param host: string, the hostname or IP address of the bmc
1145 @param args: contains additional arguments used by the fru sub command
1146 @param session: the active session to use
1147 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1148 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001149 if(args.identcmd == 'on'):
1150 print("Attempting to turn identify light on...:")
1151 url="https://"+host+"/xyz/openbmc_project/led/groups/enclosure_identify/attr/Asserted"
1152 httpHeader = {'Content-Type':'application/json',}
1153 data = '{"data":true}'
Justin Thalere412dc22018-01-12 16:28:24 -06001154 try:
1155 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1156 except(requests.exceptions.Timeout):
1157 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001158 return res.text
1159 elif(args.identcmd == 'off'):
1160 print("Attempting to turn identify light off...:")
1161 url="https://"+host+"/xyz/openbmc_project/led/groups/enclosure_identify/attr/Asserted"
1162 httpHeader = {'Content-Type':'application/json'}
1163 data = '{"data":false}'
Justin Thalere412dc22018-01-12 16:28:24 -06001164 try:
1165 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1166 except(requests.exceptions.Timeout):
1167 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001168 return res.text
1169 elif(args.identcmd == 'status'):
1170 url="https://"+host+"/xyz/openbmc_project/led/groups/enclosure_identify"
1171 httpHeader = {'Content-Type':'application/json'}
Justin Thalere412dc22018-01-12 16:28:24 -06001172 try:
1173 res = session.get(url, headers=httpHeader, verify=False, timeout=30)
1174 except(requests.exceptions.Timeout):
1175 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001176 status = json.loads(res.text)['data']
1177 if(args.json):
1178 return status
1179 else:
1180 if status['Asserted'] == 0:
1181 return "Identify light is off"
1182 else:
1183 return "Identify light is blinking"
1184 else:
1185 return "Invalid chassis identify command"
1186
Justin Thalere412dc22018-01-12 16:28:24 -06001187
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001188def chassis(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001189 """
1190 controls the different chassis commands
1191
1192 @param host: string, the hostname or IP address of the bmc
1193 @param args: contains additional arguments used by the fru sub command
1194 @param session: the active session to use
1195 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1196 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001197 if(hasattr(args, 'powcmd')):
1198 result = chassisPower(host,args,session)
1199 elif(hasattr(args, 'identcmd')):
1200 result = chassisIdent(host, args, session)
1201 else:
Justin Thaler22b1bb52018-03-15 13:31:32 -05001202 return "This feature is not yet implemented"
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001203 return result
Justin Thalere412dc22018-01-12 16:28:24 -06001204
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001205def bmcDumpRetrieve(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001206 """
1207 Downloads a dump file from the bmc
1208
1209 @param host: string, the hostname or IP address of the bmc
1210 @param args: contains additional arguments used by the collectServiceData sub command
1211 @param session: the active session to use
1212 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1213 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001214 httpHeader = {'Content-Type':'application/json'}
1215 dumpNum = args.dumpNum
1216 if (args.dumpSaveLoc is not None):
1217 saveLoc = args.dumpSaveLoc
1218 else:
Justin Thalercf1deae2018-05-25 19:35:21 -05001219 saveLoc = tempfile.gettempdir()
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001220 url ='https://'+host+'/download/dump/' + str(dumpNum)
1221 try:
Justin Thalere412dc22018-01-12 16:28:24 -06001222 r = session.get(url, headers=httpHeader, stream=True, verify=False, timeout=30)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001223 if (args.dumpSaveLoc is not None):
1224 if os.path.exists(saveLoc):
1225 if saveLoc[-1] != os.path.sep:
1226 saveLoc = saveLoc + os.path.sep
1227 filename = saveLoc + host+'-dump' + str(dumpNum) + '.tar.xz'
1228
1229 else:
1230 return 'Invalid save location specified'
1231 else:
Justin Thalercf1deae2018-05-25 19:35:21 -05001232 filename = tempfile.gettempdir()+os.sep + host+'-dump' + str(dumpNum) + '.tar.xz'
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001233
1234 with open(filename, 'wb') as f:
1235 for chunk in r.iter_content(chunk_size =1024):
1236 if chunk:
1237 f.write(chunk)
1238 return 'Saved as ' + filename
1239
1240 except(requests.exceptions.Timeout):
1241 return connectionErrHandler(args.json, "Timeout", None)
Justin Thalere412dc22018-01-12 16:28:24 -06001242
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001243 except(requests.exceptions.ConnectionError) as err:
1244 return connectionErrHandler(args.json, "ConnectionError", err)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001245
Justin Thalere412dc22018-01-12 16:28:24 -06001246def bmcDumpList(host, args, session):
1247 """
1248 Lists the number of dump files on the bmc
1249
1250 @param host: string, the hostname or IP address of the bmc
1251 @param args: contains additional arguments used by the collectServiceData sub command
1252 @param session: the active session to use
1253 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1254 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001255 httpHeader = {'Content-Type':'application/json'}
1256 url ='https://'+host+'/xyz/openbmc_project/dump/list'
1257 try:
1258 r = session.get(url, headers=httpHeader, verify=False, timeout=20)
1259 dumpList = json.loads(r.text)
1260 return r.text
1261 except(requests.exceptions.Timeout):
1262 return connectionErrHandler(args.json, "Timeout", None)
Justin Thalere412dc22018-01-12 16:28:24 -06001263
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001264 except(requests.exceptions.ConnectionError) as err:
Justin Thalere412dc22018-01-12 16:28:24 -06001265 return connectionErrHandler(args.json, "ConnectionError", err)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001266
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001267def bmcDumpDelete(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001268 """
1269 Deletes BMC dump files from the bmc
1270
1271 @param host: string, the hostname or IP address of the bmc
1272 @param args: contains additional arguments used by the collectServiceData sub command
1273 @param session: the active session to use
1274 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1275 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001276 httpHeader = {'Content-Type':'application/json'}
1277 dumpList = []
1278 successList = []
1279 failedList = []
1280 if args.dumpNum is not None:
1281 if isinstance(args.dumpNum, list):
1282 dumpList = args.dumpNum
1283 else:
1284 dumpList.append(args.dumpNum)
1285 for dumpNum in dumpList:
1286 url ='https://'+host+'/xyz/openbmc_project/dump/entry/'+str(dumpNum)+'/action/Delete'
1287 try:
1288 r = session.post(url, headers=httpHeader, json = {"data": []}, verify=False, timeout=30)
1289 if r.status_code == 200:
1290 successList.append(str(dumpNum))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001291 else:
1292 failedList.append(str(dumpNum))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001293 except(requests.exceptions.Timeout):
1294 return connectionErrHandler(args.json, "Timeout", None)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001295 except(requests.exceptions.ConnectionError) as err:
1296 return connectionErrHandler(args.json, "ConnectionError", err)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001297 output = "Successfully deleted dumps: " + ', '.join(successList)
1298 if(len(failedList)>0):
1299 output+= '\nFailed to delete dumps: ' + ', '.join(failedList)
1300 return output
1301 else:
1302 return 'You must specify an entry number to delete'
1303
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001304def bmcDumpDeleteAll(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001305 """
1306 Deletes All BMC dump files from the bmc
1307
1308 @param host: string, the hostname or IP address of the bmc
1309 @param args: contains additional arguments used by the collectServiceData sub command
1310 @param session: the active session to use
1311 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1312 """
1313 dumpResp = bmcDumpList(host, args, session)
1314 if 'FQPSPIN0000M' in dumpResp or 'FQPSPIN0001M'in dumpResp:
1315 return dumpResp
1316 dumpList = json.loads(dumpResp)['data']
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001317 d = vars(args)
1318 dumpNums = []
1319 for dump in dumpList:
1320 if '/xyz/openbmc_project/dump/internal/manager' not in dump:
1321 dumpNums.append(int(dump.strip().split('/')[-1]))
1322 d['dumpNum'] = dumpNums
1323
1324 return bmcDumpDelete(host, args, session)
1325
Justin Thalere412dc22018-01-12 16:28:24 -06001326
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001327def bmcDumpCreate(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001328 """
1329 Creates a bmc dump file
1330
1331 @param host: string, the hostname or IP address of the bmc
1332 @param args: contains additional arguments used by the collectServiceData sub command
1333 @param session: the active session to use
1334 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1335 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001336 httpHeader = {'Content-Type':'application/json'}
1337 url = 'https://'+host+'/xyz/openbmc_project/dump/action/CreateDump'
1338 try:
1339 r = session.post(url, headers=httpHeader, json = {"data": []}, verify=False, timeout=30)
1340 if('"message": "200 OK"' in r.text and not args.json):
1341 return ('Dump successfully created')
1342 else:
1343 return ('Failed to create dump')
1344 except(requests.exceptions.Timeout):
1345 return connectionErrHandler(args.json, "Timeout", None)
1346 except(requests.exceptions.ConnectionError) as err:
1347 return connectionErrHandler(args.json, "ConnectionError", err)
1348
1349
1350
Justin Thalere412dc22018-01-12 16:28:24 -06001351
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001352def collectServiceData(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001353 """
1354 Collects all data needed for service from the BMC
1355
1356 @param host: string, the hostname or IP address of the bmc
1357 @param args: contains additional arguments used by the collectServiceData sub command
1358 @param session: the active session to use
1359 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1360 """
Justin Thaler22b1bb52018-03-15 13:31:32 -05001361
1362 global toolVersion
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001363 #create a bmc dump
1364 dumpcount = len(json.loads(bmcDumpList(host, args, session))['data'])
1365 try:
1366 dumpcreated = bmcDumpCreate(host, args, session)
1367 except Exception as e:
1368 print('failed to create a bmc dump')
1369
1370
1371 #Collect Inventory
1372 try:
1373 args.silent = True
Justin Thalercf1deae2018-05-25 19:35:21 -05001374 myDir = tempfile.gettempdir()+os.sep + host + "--" + datetime.datetime.now().strftime("%Y-%m-%d_%H.%M.%S")
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001375 os.makedirs(myDir)
1376 filelist = []
1377 frulist = fruPrint(host, args, session)
1378 with open(myDir +'/inventory.txt', 'w') as f:
1379 f.write(frulist)
1380 print("Inventory collected and stored in " + myDir + "/inventory.txt")
1381 filelist.append(myDir+'/inventory.txt')
1382 except Exception as e:
1383 print("Failed to collect inventory")
1384
1385 #Read all the sensor and OCC status
1386 try:
1387 sensorReadings = sensor(host, args, session)
1388 with open(myDir +'/sensorReadings.txt', 'w') as f:
1389 f.write(sensorReadings)
1390 print("Sensor readings collected and stored in " +myDir + "/sensorReadings.txt")
1391 filelist.append(myDir+'/sensorReadings.txt')
1392 except Exception as e:
1393 print("Failed to collect sensor readings")
1394
1395 #Collect all of the LEDs status
1396 try:
1397 url="https://"+host+"/xyz/openbmc_project/led/enumerate"
1398 httpHeader = {'Content-Type':'application/json'}
1399 leds = session.get(url, headers=httpHeader, verify=False, timeout=20)
1400 with open(myDir +'/ledStatus.txt', 'w') as f:
1401 f.write(leds.text)
1402 print("System LED status collected and stored in "+myDir +"/ledStatus.txt")
1403 filelist.append(myDir+'/ledStatus.txt')
1404 except Exception as e:
1405 print("Failed to collect LED status")
1406
1407 #Collect the bmc logs
1408 try:
1409 sels = selPrint(host,args,session)
1410 with open(myDir +'/SELshortlist.txt', 'w') as f:
1411 f.write(str(sels))
1412 print("sel short list collected and stored in "+myDir +"/SELshortlist.txt")
1413 filelist.append(myDir+'/SELshortlist.txt')
1414 time.sleep(2)
1415
1416 d = vars(args)
1417 d['json'] = True
1418 d['fullSel'] = True
1419 parsedfullsels = json.loads(selPrint(host, args, session))
1420 d['fullEsel'] = True
1421 sortedSELs = sortSELs(parsedfullsels)
1422 with open(myDir +'/parsedSELs.txt', 'w') as f:
1423 for log in sortedSELs[0]:
1424 esel = ""
1425 parsedfullsels[sortedSELs[1][str(log)]]['timestamp'] = datetime.datetime.fromtimestamp(int(parsedfullsels[sortedSELs[1][str(log)]]['timestamp']/1000)).strftime("%Y-%m-%d %H:%M:%S")
1426 if ('raweSEL' in parsedfullsels[sortedSELs[1][str(log)]] and args.devdebug):
1427 esel = parsedfullsels[sortedSELs[1][str(log)]]['raweSEL']
1428 del parsedfullsels[sortedSELs[1][str(log)]]['raweSEL']
1429 f.write(json.dumps(parsedfullsels[sortedSELs[1][str(log)]],sort_keys=True, indent=4, separators=(',', ': ')))
1430 if(args.devdebug and esel != ""):
1431 f.write(parseESEL(args, esel))
1432 print("fully parsed sels collected and stored in "+myDir +"/parsedSELs.txt")
1433 filelist.append(myDir+'/parsedSELs.txt')
1434 except Exception as e:
1435 print("Failed to collect system event logs")
1436 print(e)
1437
1438 #collect RAW bmc enumeration
1439 try:
1440 url="https://"+host+"/xyz/openbmc_project/enumerate"
1441 print("Attempting to get a full BMC enumeration")
1442 fullDump = session.get(url, headers=httpHeader, verify=False, timeout=120)
1443 with open(myDir +'/bmcFullRaw.txt', 'w') as f:
1444 f.write(fullDump.text)
1445 print("RAW BMC data collected and saved into "+myDir +"/bmcFullRaw.txt")
1446 filelist.append(myDir+'/bmcFullRaw.txt')
1447 except Exception as e:
1448 print("Failed to collect bmc full enumeration")
1449
1450 #collect the dump files
1451 waitingForNewDump = True
1452 count = 0;
1453 while(waitingForNewDump):
1454 dumpList = json.loads(bmcDumpList(host, args, session))['data']
1455 if len(dumpList) > dumpcount:
1456 waitingForNewDump = False
1457 break;
1458 elif(count>30):
1459 print("Timed out waiting for bmc to make a new dump file. Dump space may be full.")
1460 break;
1461 else:
1462 time.sleep(2)
1463 count += 1
1464 try:
1465 print('Collecting bmc dump files')
1466 d['dumpSaveLoc'] = myDir
1467 dumpList = json.loads(bmcDumpList(host, args, session))['data']
1468 for dump in dumpList:
1469 if '/xyz/openbmc_project/dump/internal/manager' not in dump:
1470 d['dumpNum'] = int(dump.strip().split('/')[-1])
1471 print('retrieving dump file ' + str(d['dumpNum']))
1472 filename = bmcDumpRetrieve(host, args, session).split('Saved as ')[-1]
1473 filelist.append(filename)
1474 time.sleep(2)
1475 except Exception as e:
1476 print("Failed to collect bmc dump files")
1477 print(e)
1478
1479 #create the zip file
1480 try:
Justin Thalercf1deae2018-05-25 19:35:21 -05001481 filename = myDir.split(tempfile.gettempdir()+os.sep)[-1] + "_" + toolVersion + '_openbmc.zip'
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001482 zf = zipfile.ZipFile(myDir+'/' + filename, 'w')
1483 for myfile in filelist:
1484 zf.write(myfile, os.path.basename(myfile))
1485 zf.close()
1486 except Exception as e:
1487 print("Failed to create zip file with collected information")
1488 return "data collection complete"
1489
Justin Thalere412dc22018-01-12 16:28:24 -06001490
1491def healthCheck(host, args, session):
1492 """
1493 runs a health check on the platform
1494
1495 @param host: string, the hostname or IP address of the bmc
1496 @param args: contains additional arguments used by the bmc sub command
1497 @param session: the active session to use
1498 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1499 """
1500 #check fru status and get as json to easily work through
1501 d = vars(args)
1502 useJson = d['json']
1503 d['json'] = True
1504 d['verbose']= False
1505
1506 frus = json.loads(fruStatus(host, args, session))
1507
1508 hwStatus= "OK"
1509 performanceStatus = "OK"
1510 for key in frus:
1511 if frus[key]["Functional"] == "No" and frus[key]["Present"] == "Yes":
1512 hwStatus= "Degraded"
1513 if("power_supply" in key):
1514 gpuCount =0;
1515 frulist = json.loads(fruList(host, args, session))
1516 for comp in frulist:
1517 if "gv100card" in comp:
1518 gpuCount +=1
1519 if gpuCount > 4:
1520 hwStatus = "Critical"
1521 performanceStatus="Degraded"
1522 break;
1523 elif("fan" in key):
1524 hwStatus = "Degraded"
1525 else:
1526 performanceStatus = "Degraded"
1527 if useJson:
1528 output = {"Hardware Status": hwStatus, "Performance": performanceStatus}
1529 output = json.dumps(output, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False)
1530 else:
1531 output = ("Hardware Status: " + hwStatus +
1532 "\nPerformance: " +performanceStatus )
1533
1534
1535 #SW407886: Clear the duplicate entries
1536 #collect the dups
1537 d['devdebug'] = False
1538 sels = json.loads(selPrint(host, args, session))
1539 logNums2Clr = []
1540 oldestLogNum={"logNum": "bogus" ,"key" : ""}
1541 count = 0
1542 if sels['numAlerts'] > 0:
1543 for key in sels:
1544 if "numAlerts" in key:
1545 continue
1546 try:
1547 if "slave@00:00/00:00:00:06/sbefifo1-dev0/occ1-dev0" in sels[key]['Message']:
1548 count += 1
1549 if count > 1:
1550 #preserve first occurrence
1551 if sels[key]['timestamp'] < sels[oldestLogNum['key']]['timestamp']:
1552 oldestLogNum['key']=key
1553 oldestLogNum['logNum'] = sels[key]['logNum']
1554 else:
1555 oldestLogNum['key']=key
1556 oldestLogNum['logNum'] = sels[key]['logNum']
1557 logNums2Clr.append(sels[key]['logNum'])
1558 except KeyError:
1559 continue
1560 if(count >0):
1561 logNums2Clr.remove(oldestLogNum['logNum'])
1562 #delete the dups
1563 if count >1:
1564 httpHeader = {'Content-Type':'application/json'}
1565 data = "{\"data\": [] }"
1566 for logNum in logNums2Clr:
1567 url = "https://"+ host+ "/xyz/openbmc_project/logging/entry/"+logNum+"/action/Delete"
1568 try:
1569 session.post(url, headers=httpHeader, data=data, verify=False, timeout=30)
1570 except(requests.exceptions.Timeout):
1571 deleteFailed = True
1572 except(requests.exceptions.ConnectionError) as err:
1573 deleteFailed = True
1574 #End of defect resolve code
1575 d['json'] = useJson
1576 return output
1577
1578
1579
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001580def bmc(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001581 """
1582 handles various bmc level commands, currently bmc rebooting
1583
1584 @param host: string, the hostname or IP address of the bmc
1585 @param args: contains additional arguments used by the bmc sub command
1586 @param session: the active session to use
1587 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1588 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001589 if(args.type is not None):
1590 return bmcReset(host, args, session)
Justin Thalere412dc22018-01-12 16:28:24 -06001591 if(args.info):
1592 return "Not implemented at this time"
1593
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001594
Justin Thalere412dc22018-01-12 16:28:24 -06001595
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001596def bmcReset(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001597 """
1598 controls resetting the bmc. warm reset reboots the bmc, cold reset removes the configuration and reboots.
1599
1600 @param host: string, the hostname or IP address of the bmc
1601 @param args: contains additional arguments used by the bmcReset sub command
1602 @param session: the active session to use
1603 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1604 """
Justin Thaler22b1bb52018-03-15 13:31:32 -05001605 if checkFWactivation(host, args, session):
1606 return ("BMC reset control disabled during firmware activation")
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001607 if(args.type == "warm"):
1608 print("\nAttempting to reboot the BMC...:")
1609 url="https://"+host+"/xyz/openbmc_project/state/bmc0/attr/RequestedBMCTransition"
1610 httpHeader = {'Content-Type':'application/json'}
Justin Thalere412dc22018-01-12 16:28:24 -06001611 data = '{"data":"xyz.openbmc_project.State.BMC.Transition.Reboot"}'
1612 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=20)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001613 return res.text
1614 elif(args.type =="cold"):
Justin Thalere412dc22018-01-12 16:28:24 -06001615 print("\nAttempting to reboot the BMC...:")
1616 url="https://"+host+"/xyz/openbmc_project/state/bmc0/attr/RequestedBMCTransition"
1617 httpHeader = {'Content-Type':'application/json'}
1618 data = '{"data":"xyz.openbmc_project.State.BMC.Transition.Reboot"}'
1619 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=20)
1620 return res.text
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001621 else:
1622 return "invalid command"
Justin Thalere412dc22018-01-12 16:28:24 -06001623
1624def gardClear(host, args, session):
1625 """
1626 clears the gard records from the bmc
1627
1628 @param host: string, the hostname or IP address of the bmc
1629 @param args: contains additional arguments used by the gardClear sub command
1630 @param session: the active session to use
1631 """
1632 url="https://"+host+"/org/open_power/control/gard/action/Reset"
1633 httpHeader = {'Content-Type':'application/json'}
1634 data = '{"data":[]}'
1635 try:
1636
1637 res = session.post(url, headers=httpHeader, data=data, verify=False, timeout=30)
1638 if res.status_code == 404:
1639 return "Command not supported by this firmware version"
1640 else:
1641 return res.text
1642 except(requests.exceptions.Timeout):
1643 return connectionErrHandler(args.json, "Timeout", None)
1644 except(requests.exceptions.ConnectionError) as err:
1645 return connectionErrHandler(args.json, "ConnectionError", err)
1646
1647def activateFWImage(host, args, session):
1648 """
1649 activates a firmware image on the bmc
1650
1651 @param host: string, the hostname or IP address of the bmc
1652 @param args: contains additional arguments used by the fwflash sub command
1653 @param session: the active session to use
1654 @param fwID: the unique ID of the fw image to activate
1655 """
1656 fwID = args.imageID
1657
1658 #determine the existing versions
1659 httpHeader = {'Content-Type':'application/json'}
1660 url="https://"+host+"/xyz/openbmc_project/software/enumerate"
1661 try:
1662 resp = session.get(url, headers=httpHeader, verify=False, timeout=30)
1663 except(requests.exceptions.Timeout):
1664 return connectionErrHandler(args.json, "Timeout", None)
1665 except(requests.exceptions.ConnectionError) as err:
1666 return connectionErrHandler(args.json, "ConnectionError", err)
1667 existingSoftware = json.loads(resp.text)['data']
1668 altVersionID = ''
1669 versionType = ''
1670 imageKey = '/xyz/openbmc_project/software/'+fwID
1671 if imageKey in existingSoftware:
1672 versionType = existingSoftware[imageKey]['Purpose']
1673 for key in existingSoftware:
1674 if imageKey == key:
1675 continue
1676 if 'Purpose' in existingSoftware[key]:
1677 if versionType == existingSoftware[key]['Purpose']:
1678 altVersionID = key.split('/')[-1]
1679
1680
1681
1682
1683 url="https://"+host+"/xyz/openbmc_project/software/"+ fwID + "/attr/Priority"
1684 url1="https://"+host+"/xyz/openbmc_project/software/"+ altVersionID + "/attr/Priority"
1685 data = "{\"data\": 0}"
1686 data1 = "{\"data\": 1 }"
1687 try:
1688 resp = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1689 resp1 = session.put(url1, headers=httpHeader, data=data1, verify=False, timeout=30)
1690 except(requests.exceptions.Timeout):
1691 return connectionErrHandler(args.json, "Timeout", None)
1692 except(requests.exceptions.ConnectionError) as err:
1693 return connectionErrHandler(args.json, "ConnectionError", err)
1694 if(not args.json):
1695 if resp.status_code == 200 and resp1.status_code == 200:
Justin Thaler22b1bb52018-03-15 13:31:32 -05001696 return 'Firmware flash and activation completed. Please reboot the bmc and then boot the host OS for the changes to take effect. '
Justin Thalere412dc22018-01-12 16:28:24 -06001697 else:
1698 return "Firmware activation failed."
1699 else:
1700 return resp.text + resp1.text
Justin Thaler22b1bb52018-03-15 13:31:32 -05001701
1702def activateStatus(host, args, session):
1703 if checkFWactivation(host, args, session):
1704 return("Firmware is currently being activated. Do not reboot the BMC or start the Host OS")
1705 else:
1706 return("No firmware activations are pending")
1707
1708def extractFWimage(path, imageType):
1709 """
1710 extracts the bmc image and returns information about the package
1711
1712 @param path: the path and file name of the firmware image
1713 @param imageType: The type of image the user is trying to flash. Host or BMC
1714 @return: the image id associated with the package. returns an empty string on error.
1715 """
1716 f = tempfile.TemporaryFile()
1717 tmpDir = tempfile.gettempdir()
1718 newImageID = ""
1719 if os.path.exists(path):
1720 try:
1721 imageFile = tarfile.open(path,'r')
1722 contents = imageFile.getmembers()
1723 for tf in contents:
1724 if 'MANIFEST' in tf.name:
1725 imageFile.extract(tf.name, path=tmpDir)
1726 with open(tempfile.gettempdir() +os.sep+ tf.name, 'r') as imageInfo:
1727 for line in imageInfo:
1728 if 'purpose' in line:
1729 purpose = line.split('=')[1]
1730 if imageType not in purpose.split('.')[-1]:
1731 print('The specified image is not for ' + imageType)
1732 print('Please try again with the image for ' + imageType)
1733 return ""
1734 if 'version' == line.split('=')[0]:
1735 version = line.split('=')[1].strip().encode('utf-8')
1736 m = hashlib.sha512()
1737 m.update(version)
1738 newImageID = m.hexdigest()[:8]
1739 break
1740 try:
1741 os.remove(tempfile.gettempdir() +os.sep+ tf.name)
1742 except OSError:
1743 pass
1744 return newImageID
1745 except tarfile.ExtractError as e:
1746 print('Unable to extract information from the firmware file.')
1747 print('Ensure you have write access to the directory: ' + tmpDir)
1748 return newImageID
1749 except tarfile.TarError as e:
1750 print('This is not a valid firmware file.')
1751 return newImageID
1752 print("This is not a valid firmware file.")
1753 return newImageID
1754 else:
1755 print('The filename and path provided are not valid.')
1756 return newImageID
1757
1758def getAllFWImageIDs(fwInvDict):
1759 """
1760 gets a list of all the firmware image IDs
1761
1762 @param fwInvDict: the dictionary to search for FW image IDs
1763 @return: list containing string representation of the found image ids
1764 """
1765 idList = []
1766 for key in fwInvDict:
1767 if 'Version' in fwInvDict[key]:
1768 idList.append(key.split('/')[-1])
1769 return idList
1770
Justin Thalere412dc22018-01-12 16:28:24 -06001771def fwFlash(host, args, session):
1772 """
1773 updates the bmc firmware and pnor firmware
1774
1775 @param host: string, the hostname or IP address of the bmc
1776 @param args: contains additional arguments used by the fwflash sub command
1777 @param session: the active session to use
1778 """
Justin Thaler22b1bb52018-03-15 13:31:32 -05001779 d = vars(args)
Justin Thalere412dc22018-01-12 16:28:24 -06001780 if(args.type == 'bmc'):
1781 purp = 'BMC'
1782 else:
1783 purp = 'Host'
Justin Thaler22b1bb52018-03-15 13:31:32 -05001784
1785 #check power state of the machine. No concurrent FW updates allowed
1786 d['powcmd'] = 'status'
1787 powerstate = chassisPower(host, args, session)
1788 if 'Chassis Power State: On' in powerstate:
1789 return("Aborting firmware update. Host is powered on. Please turn off the host and try again.")
1790
1791 #determine the existing images on the bmc
Justin Thalere412dc22018-01-12 16:28:24 -06001792 httpHeader = {'Content-Type':'application/json'}
1793 url="https://"+host+"/xyz/openbmc_project/software/enumerate"
1794 try:
1795 resp = session.get(url, headers=httpHeader, verify=False, timeout=30)
1796 except(requests.exceptions.Timeout):
1797 return connectionErrHandler(args.json, "Timeout", None)
1798 except(requests.exceptions.ConnectionError) as err:
1799 return connectionErrHandler(args.json, "ConnectionError", err)
1800 oldsoftware = json.loads(resp.text)['data']
1801
Justin Thaler22b1bb52018-03-15 13:31:32 -05001802 #Extract the tar and get information from the manifest file
1803 newversionID = extractFWimage(args.fileloc, purp)
1804 if newversionID == "":
1805 return "Unable to verify FW image."
1806
1807
1808 #check if the new image is already on the bmc
1809 if newversionID not in getAllFWImageIDs(oldsoftware):
1810
1811 #upload the file
1812 httpHeader = {'Content-Type':'application/octet-stream'}
1813 url="https://"+host+"/upload/image"
1814 data=open(args.fileloc,'rb').read()
1815 print("Uploading file to BMC")
Justin Thalere412dc22018-01-12 16:28:24 -06001816 try:
Justin Thaler22b1bb52018-03-15 13:31:32 -05001817 resp = session.post(url, headers=httpHeader, data=data, verify=False)
Justin Thalere412dc22018-01-12 16:28:24 -06001818 except(requests.exceptions.Timeout):
1819 return connectionErrHandler(args.json, "Timeout", None)
1820 except(requests.exceptions.ConnectionError) as err:
1821 return connectionErrHandler(args.json, "ConnectionError", err)
Justin Thaler22b1bb52018-03-15 13:31:32 -05001822 if resp.status_code != 200:
1823 return "Failed to upload the file to the bmc"
Justin Thalere412dc22018-01-12 16:28:24 -06001824 else:
Justin Thaler22b1bb52018-03-15 13:31:32 -05001825 print("Upload complete.")
1826
1827 #verify bmc processed the image
1828 software ={}
1829 for i in range(0, 5):
1830 httpHeader = {'Content-Type':'application/json'}
1831 url="https://"+host+"/xyz/openbmc_project/software/enumerate"
1832 try:
1833 resp = session.get(url, headers=httpHeader, verify=False, timeout=30)
1834 except(requests.exceptions.Timeout):
1835 return connectionErrHandler(args.json, "Timeout", None)
1836 except(requests.exceptions.ConnectionError) as err:
1837 return connectionErrHandler(args.json, "ConnectionError", err)
1838 software = json.loads(resp.text)['data']
1839 #check if bmc is done processing the new image
1840 if (newversionID in getAllFWImageIDs(software)):
Justin Thalere412dc22018-01-12 16:28:24 -06001841 break
Justin Thaler22b1bb52018-03-15 13:31:32 -05001842 else:
1843 time.sleep(15)
1844
1845 #activate the new image
1846 print("Activating new image: "+newversionID)
1847 url="https://"+host+"/xyz/openbmc_project/software/"+ newversionID + "/attr/RequestedActivation"
1848 data = '{"data":"xyz.openbmc_project.Software.Activation.RequestedActivations.Active"}'
1849 try:
1850 resp = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1851 except(requests.exceptions.Timeout):
1852 return connectionErrHandler(args.json, "Timeout", None)
1853 except(requests.exceptions.ConnectionError) as err:
1854 return connectionErrHandler(args.json, "ConnectionError", err)
1855
1856 #wait for the activation to complete, timeout after ~1 hour
1857 i=0
1858 while i < 360:
1859 url="https://"+host+"/xyz/openbmc_project/software/"+ newversionID
1860 data = '{"data":"xyz.openbmc_project.Software.Activation.RequestedActivations.Active"}'
1861 try:
1862 resp = session.get(url, headers=httpHeader, verify=False, timeout=30)
1863 except(requests.exceptions.Timeout):
1864 return connectionErrHandler(args.json, "Timeout", None)
1865 except(requests.exceptions.ConnectionError) as err:
1866 return connectionErrHandler(args.json, "ConnectionError", err)
1867 fwInfo = json.loads(resp.text)['data']
1868 if 'Activating' not in fwInfo['Activation'] and 'Activating' not in fwInfo['RequestedActivation']:
1869 print('')
1870 break
1871 else:
1872 sys.stdout.write('.')
1873 sys.stdout.flush()
1874 time.sleep(10) #check every 10 seconds
1875 return "Firmware flash and activation completed. Please reboot the bmc and then boot the host OS for the changes to take effect. "
1876 else:
1877 print("This image has been found on the bmc. Activating image: " + newversionID)
1878
1879 d['imageID'] = newversionID
1880 return activateFWImage(host, args, session)
Justin Thalere412dc22018-01-12 16:28:24 -06001881
Justin Thaler22b1bb52018-03-15 13:31:32 -05001882
Justin Thalere412dc22018-01-12 16:28:24 -06001883
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001884def createCommandParser():
Justin Thalere412dc22018-01-12 16:28:24 -06001885 """
1886 creates the parser for the command line along with help for each command and subcommand
1887
1888 @return: returns the parser for the command line
1889 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001890 parser = argparse.ArgumentParser(description='Process arguments')
Justin Thalere412dc22018-01-12 16:28:24 -06001891 parser.add_argument("-H", "--host", help='A hostname or IP for the BMC')
1892 parser.add_argument("-U", "--user", help='The username to login with')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001893 group = parser.add_mutually_exclusive_group()
1894 group.add_argument("-A", "--askpw", action='store_true', help='prompt for password')
1895 group.add_argument("-P", "--PW", help='Provide the password in-line')
1896 parser.add_argument('-j', '--json', action='store_true', help='output json data only')
1897 parser.add_argument('-t', '--policyTableLoc', help='The location of the policy table to parse alerts')
1898 parser.add_argument('-c', '--CerFormat', action='store_true', help=argparse.SUPPRESS)
1899 parser.add_argument('-T', '--procTime', action='store_true', help= argparse.SUPPRESS)
Justin Thalere412dc22018-01-12 16:28:24 -06001900 parser.add_argument('-V', '--version', action='store_true', help='Display the version number of the openbmctool')
1901 subparsers = parser.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001902
1903 #fru command
1904 parser_inv = subparsers.add_parser("fru", help='Work with platform inventory')
1905 #fru print
Justin Thalere412dc22018-01-12 16:28:24 -06001906 inv_subparser = parser_inv.add_subparsers(title='subcommands', description='valid inventory actions', help="valid inventory actions", dest='command')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001907 inv_print = inv_subparser.add_parser("print", help="prints out a list of all FRUs")
1908 inv_print.set_defaults(func=fruPrint)
1909 #fru list [0....n]
1910 inv_list = inv_subparser.add_parser("list", help="print out details on selected FRUs. Specifying no items will list the entire inventory")
1911 inv_list.add_argument('items', nargs='?', help="print out details on selected FRUs. Specifying no items will list the entire inventory")
1912 inv_list.set_defaults(func=fruList)
1913 #fru status
1914 inv_status = inv_subparser.add_parser("status", help="prints out the status of all FRUs")
Justin Thalere412dc22018-01-12 16:28:24 -06001915 inv_status.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001916 inv_status.set_defaults(func=fruStatus)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001917
1918 #sensors command
1919 parser_sens = subparsers.add_parser("sensors", help="Work with platform sensors")
Justin Thalere412dc22018-01-12 16:28:24 -06001920 sens_subparser=parser_sens.add_subparsers(title='subcommands', description='valid sensor actions', help='valid sensor actions', dest='command')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001921 #sensor print
1922 sens_print= sens_subparser.add_parser('print', help="prints out a list of all Sensors.")
1923 sens_print.set_defaults(func=sensor)
1924 #sensor list[0...n]
1925 sens_list=sens_subparser.add_parser("list", help="Lists all Sensors in the platform. Specify a sensor for full details. ")
1926 sens_list.add_argument("sensNum", nargs='?', help="The Sensor number to get full details on" )
1927 sens_list.set_defaults(func=sensor)
1928
1929
1930 #sel command
1931 parser_sel = subparsers.add_parser("sel", help="Work with platform alerts")
Justin Thalere412dc22018-01-12 16:28:24 -06001932 sel_subparser = parser_sel.add_subparsers(title='subcommands', description='valid SEL actions', help = 'valid SEL actions', dest='command')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001933
1934 #sel print
1935 sel_print = sel_subparser.add_parser("print", help="prints out a list of all sels in a condensed list")
1936 sel_print.add_argument('-d', '--devdebug', action='store_true', help=argparse.SUPPRESS)
1937 sel_print.add_argument('-v', '--verbose', action='store_true', help="Changes the output to being very verbose")
1938 sel_print.add_argument('-f', '--fileloc', help='Parse a file instead of the BMC output')
1939 sel_print.set_defaults(func=selPrint)
Justin Thaler22b1bb52018-03-15 13:31:32 -05001940
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001941 #sel list
1942 sel_list = sel_subparser.add_parser("list", help="Lists all SELs in the platform. Specifying a specific number will pull all the details for that individual SEL")
1943 sel_list.add_argument("selNum", nargs='?', type=int, help="The SEL entry to get details on")
1944 sel_list.set_defaults(func=selList)
1945
1946 sel_get = sel_subparser.add_parser("get", help="Gets the verbose details of a specified SEL entry")
1947 sel_get.add_argument('selNum', type=int, help="the number of the SEL entry to get")
1948 sel_get.set_defaults(func=selList)
1949
1950 sel_clear = sel_subparser.add_parser("clear", help="Clears all entries from the SEL")
1951 sel_clear.set_defaults(func=selClear)
1952
1953 sel_setResolved = sel_subparser.add_parser("resolve", help="Sets the sel entry to resolved")
Justin Thalere412dc22018-01-12 16:28:24 -06001954 sel_setResolved.add_argument('-n', '--selNum', type=int, help="the number of the SEL entry to resolve")
1955 sel_ResolveAll_sub = sel_setResolved.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
1956 sel_ResolveAll = sel_ResolveAll_sub.add_parser('all', help='Resolve all SEL entries')
1957 sel_ResolveAll.set_defaults(func=selResolveAll)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001958 sel_setResolved.set_defaults(func=selSetResolved)
1959
1960 parser_chassis = subparsers.add_parser("chassis", help="Work with chassis power and status")
Justin Thalere412dc22018-01-12 16:28:24 -06001961 chas_sub = parser_chassis.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001962
1963 parser_chassis.add_argument('status', action='store_true', help='Returns the current status of the platform')
1964 parser_chassis.set_defaults(func=chassis)
1965
1966 parser_chasPower = chas_sub.add_parser("power", help="Turn the chassis on or off, check the power state")
Justin Thalere412dc22018-01-12 16:28:24 -06001967 parser_chasPower.add_argument('powcmd', choices=['on','softoff', 'hardoff', 'status'], help='The value for the power command. on, off, or status')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001968 parser_chasPower.set_defaults(func=chassisPower)
1969
1970 #control the chassis identify led
1971 parser_chasIdent = chas_sub.add_parser("identify", help="Control the chassis identify led")
1972 parser_chasIdent.add_argument('identcmd', choices=['on', 'off', 'status'], help='The control option for the led: on, off, blink, status')
1973 parser_chasIdent.set_defaults(func=chassisIdent)
1974
1975 #collect service data
1976 parser_servData = subparsers.add_parser("collect_service_data", help="Collect all bmc data needed for service")
1977 parser_servData.add_argument('-d', '--devdebug', action='store_true', help=argparse.SUPPRESS)
1978 parser_servData.set_defaults(func=collectServiceData)
1979
Justin Thalere412dc22018-01-12 16:28:24 -06001980 #system quick health check
1981 parser_healthChk = subparsers.add_parser("health_check", help="Work with platform sensors")
1982 parser_healthChk.set_defaults(func=healthCheck)
1983
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001984 #work with bmc dumps
1985 parser_bmcdump = subparsers.add_parser("dump", help="Work with bmc dump files")
Justin Thalere412dc22018-01-12 16:28:24 -06001986 bmcDump_sub = parser_bmcdump.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001987 dump_Create = bmcDump_sub.add_parser('create', help="Create a bmc dump")
1988 dump_Create.set_defaults(func=bmcDumpCreate)
1989
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001990 dump_list = bmcDump_sub.add_parser('list', help="list all bmc dump files")
1991 dump_list.set_defaults(func=bmcDumpList)
1992
1993 parserdumpdelete = bmcDump_sub.add_parser('delete', help="Delete bmc dump files")
1994 parserdumpdelete.add_argument("-n", "--dumpNum", nargs='*', type=int, help="The Dump entry to delete")
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001995 parserdumpdelete.set_defaults(func=bmcDumpDelete)
1996
Justin Thalere412dc22018-01-12 16:28:24 -06001997 bmcDumpDelsub = parserdumpdelete.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001998 deleteAllDumps = bmcDumpDelsub.add_parser('all', help='Delete all bmc dump files')
1999 deleteAllDumps.set_defaults(func=bmcDumpDeleteAll)
2000
2001 parser_dumpretrieve = bmcDump_sub.add_parser('retrieve', help='Retrieve a dump file')
2002 parser_dumpretrieve.add_argument("dumpNum", type=int, help="The Dump entry to delete")
2003 parser_dumpretrieve.add_argument("-s", "--dumpSaveLoc", help="The location to save the bmc dump file")
2004 parser_dumpretrieve.set_defaults(func=bmcDumpRetrieve)
2005
Justin Thaler22b1bb52018-03-15 13:31:32 -05002006 #bmc command for reseting the bmc
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002007 parser_bmc = subparsers.add_parser('bmc', help="Work with the bmc")
Justin Thalere412dc22018-01-12 16:28:24 -06002008 bmc_sub = parser_bmc.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002009 parser_BMCReset = bmc_sub.add_parser('reset', help='Reset the bmc' )
2010 parser_BMCReset.add_argument('type', choices=['warm','cold'], help="Warm: Reboot the BMC, Cold: CLEAR config and reboot bmc")
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002011 parser_bmc.add_argument('info', action='store_true', help="Displays information about the BMC hardware, including device revision, firmware revision, IPMI version supported, manufacturer ID, and information on additional device support.")
2012 parser_bmc.set_defaults(func=bmc)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002013
2014 #add alias to the bmc command
2015 parser_mc = subparsers.add_parser('mc', help="Work with the management controller")
Justin Thalere412dc22018-01-12 16:28:24 -06002016 mc_sub = parser_mc.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002017 parser_MCReset = mc_sub.add_parser('reset', help='Reset the bmc' )
2018 parser_MCReset.add_argument('type', choices=['warm','cold'], help="Reboot the BMC")
2019 #parser_MCReset.add_argument('cold', action='store_true', help="Reboot the BMC and CLEAR the configuration")
2020 parser_mc.add_argument('info', action='store_true', help="Displays information about the BMC hardware, including device revision, firmware revision, IPMI version supported, manufacturer ID, and information on additional device support.")
Justin Thalere412dc22018-01-12 16:28:24 -06002021 parser_MCReset.set_defaults(func=bmcReset)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002022 parser_mc.set_defaults(func=bmc)
Justin Thalere412dc22018-01-12 16:28:24 -06002023
2024 #gard clear
2025 parser_gc = subparsers.add_parser("gardclear", help="Used to clear gard records")
2026 parser_gc.set_defaults(func=gardClear)
2027
2028 #firmware_flash
2029 parser_fw = subparsers.add_parser("firmware", help="Work with the system firmware")
2030 fwflash_subproc = parser_fw.add_subparsers(title='subcommands', description='valid firmware commands', help='sub-command help', dest='command')
2031 fwflash = fwflash_subproc.add_parser('flash', help="Flash the system firmware")
2032 fwflash.add_argument('type', choices=['bmc', 'pnor'], help="image type to flash")
2033 fwflash.add_argument('-f', '--fileloc', required=True, help="The absolute path to the firmware image")
2034 fwflash.set_defaults(func=fwFlash)
2035
Justin Thaler22b1bb52018-03-15 13:31:32 -05002036 fwActivate = fwflash_subproc.add_parser('activate', help="Activate existing image on the bmc")
Justin Thalere412dc22018-01-12 16:28:24 -06002037 fwActivate.add_argument('imageID', help="The image ID to activate from the firmware list. Ex: 63c95399")
2038 fwActivate.set_defaults(func=activateFWImage)
2039
Justin Thaler22b1bb52018-03-15 13:31:32 -05002040 fwActivateStatus = fwflash_subproc.add_parser('activation_status', help="Check Status of activations")
2041 fwActivateStatus.set_defaults(func=activateStatus)
2042
2043
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002044 return parser
2045
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002046def main(argv=None):
Justin Thalere412dc22018-01-12 16:28:24 -06002047 """
2048 main function for running the command line utility as a sub application
2049 """
Justin Thaler22b1bb52018-03-15 13:31:32 -05002050 global toolVersion
2051 toolVersion = "1.03"
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002052 parser = createCommandParser()
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002053 args = parser.parse_args(argv)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002054
2055 totTimeStart = int(round(time.time()*1000))
2056
2057 if(sys.version_info < (3,0)):
2058 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
2059 if sys.version_info >= (3,0):
2060 requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
Justin Thalere412dc22018-01-12 16:28:24 -06002061 if (args.version):
Justin Thaler22b1bb52018-03-15 13:31:32 -05002062 print("Version: "+ toolVersion)
Justin Thalere412dc22018-01-12 16:28:24 -06002063 sys.exit(0)
2064 if (hasattr(args, 'fileloc') and args.fileloc is not None and 'print' in args.command):
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002065 mysess = None
Justin Thalere412dc22018-01-12 16:28:24 -06002066 print(selPrint('N/A', args, mysess))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002067 else:
Justin Thalere412dc22018-01-12 16:28:24 -06002068 if(hasattr(args, 'host') and hasattr(args,'user')):
2069 if (args.askpw):
2070 pw = getpass.getpass()
2071 elif(args.PW is not None):
2072 pw = args.PW
2073 else:
2074 print("You must specify a password")
2075 sys.exit()
2076 logintimeStart = int(round(time.time()*1000))
2077 mysess = login(args.host, args.user, pw, args.json)
2078 logintimeStop = int(round(time.time()*1000))
2079
2080 commandTimeStart = int(round(time.time()*1000))
2081 output = args.func(args.host, args, mysess)
2082 commandTimeStop = int(round(time.time()*1000))
2083 print(output)
2084 if (mysess is not None):
2085 logout(args.host, args.user, pw, mysess, args.json)
2086 if(args.procTime):
2087 print("Total time: " + str(int(round(time.time()*1000))- totTimeStart))
2088 print("loginTime: " + str(logintimeStop - logintimeStart))
2089 print("command Time: " + str(commandTimeStop - commandTimeStart))
2090 else:
2091 print("usage: openbmctool.py [-h] -H HOST -U USER [-A | -P PW] [-j]\n" +
2092 "\t[-t POLICYTABLELOC] [-V]\n" +
2093 "\t{fru,sensors,sel,chassis,collect_service_data,health_check,dump,bmc,mc,gardclear,firmware}\n" +
2094 "\t...\n" +
2095 "openbmctool.py: error: the following arguments are required: -H/--host, -U/--user")
2096 sys.exit()
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002097
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002098if __name__ == '__main__':
Justin Thalere412dc22018-01-12 16:28:24 -06002099 """
2100 main function when called from the command line
2101
2102 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002103 import sys
2104
2105 isTTY = sys.stdout.isatty()
2106 assert sys.version_info >= (2,7)
2107 main()