blob: 551c43cda072a78f7dce378e8e965afbd1927e37 [file] [log] [blame]
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001#!/usr/bin/python3
2
3'''
4#================================================================================
5#
6# openbmctool.py
7#
8# Copyright IBM Corporation 2015-2017. All Rights Reserved
9#
10# This program is licensed under the terms of the Eclipse Public License
11# v1.0 as published by the Eclipse Foundation and available at
12# http://www.eclipse.org/legal/epl-v10.html
13#
14# U.S. Government Users Restricted Rights: Use, duplication or disclosure
15# restricted by GSA ADP Schedule Contract with IBM Corp.
16#
17#================================================================================
18'''
19import argparse
20import requests
21import getpass
22import json
23import os
24import urllib3
25import time, datetime
26# import yaml
27import binascii
28import subprocess
29import platform
30import zipfile
31
32#isTTY = sys.stdout.isatty()
33
34"""
35 Used to add highlights to various text for displaying in a terminal
36
37 @param textToColor: string, the text to be colored
38 @param color: string, used to color the text red or green
39 @param bold: boolean, used to bold the textToColor
40 @return: Buffered reader containing the modified string.
41"""
42def hilight(textToColor, color, bold):
43 if(sys.platform.__contains__("win")):
44 if(color == "red"):
45 os.system('color 04')
46 elif(color == "green"):
47 os.system('color 02')
48 else:
49 os.system('color') #reset to default
50 return textToColor
51 else:
52 attr = []
53 if(color == "red"):
54 attr.append('31')
55 elif(color == "green"):
56 attr.append('32')
57 else:
58 attr.append('0')
59 if bold:
60 attr.append('1')
61 else:
62 attr.append('0')
63 return '\x1b[%sm%s\x1b[0m' % (';'.join(attr),textToColor)
64
65"""
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"""
72def connectionErrHandler(jsonFormat, errorStr, err):
73 if errorStr == "Timeout":
74 if not jsonFormat:
75 return("FQPSPIN0000M: Connection timed out. Ensure you have network connectivity to the bmc")
76 else:
77 errorMessageStr = ("{\n\t\"event0\":{\n" +
78 "\t\t\"CommonEventID\": \"FQPSPIN0000M\",\n"+
79 "\t\t\"sensor\": \"N/A\",\n"+
80 "\t\t\"state\": \"N/A\",\n" +
81 "\t\t\"additionalDetails\": \"N/A\",\n" +
82 "\t\t\"Message\": \"Connection timed out. Ensure you have network connectivity to the BMC\",\n" +
83 "\t\t\"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.\",\n" +
84 "\t\t\"Serviceable\": \"Yes\",\n" +
85 "\t\t\"CallHomeCandidate\": \"No\",\n" +
86 "\t\t\"Severity\": \"Critical\",\n" +
87 "\t\t\"EventType\": \"Communication Failure/Timeout\",\n" +
88 "\t\t\"VMMigrationFlag\": \"Yes\",\n" +
89 "\t\t\"AffectedSubsystem\": \"Interconnect (Networking)\",\n" +
90 "\t\t\"timestamp\": \""+str(int(time.time()))+"\",\n" +
91 "\t\t\"UserAction\": \"Verify network connectivity between the two systems and the bmc is functional.\"" +
92 "\t\n}, \n" +
93 "\t\"numAlerts\": \"1\" \n}");
94 return(errorMessageStr)
95 elif errorStr == "ConnectionError":
96 if not jsonFormat:
97 return("FQPSPIN0001M: " + str(err))
98 else:
99 errorMessageStr = ("{\n\t\"event0\":{\n" +
100 "\t\t\"CommonEventID\": \"FQPSPIN0001M\",\n"+
101 "\t\t\"sensor\": \"N/A\",\n"+
102 "\t\t\"state\": \"N/A\",\n" +
103 "\t\t\"additionalDetails\": \"" + str(err)+"\",\n" +
104 "\t\t\"Message\": \"Connection Error. View additional details for more information\",\n" +
105 "\t\t\"LengthyDescription\": \"A connection error to the specified BMC occurred and additional details are provided. Review these details to resolve the issue.\",\n" +
106 "\t\t\"Serviceable\": \"Yes\",\n" +
107 "\t\t\"CallHomeCandidate\": \"No\",\n" +
108 "\t\t\"Severity\": \"Critical\",\n" +
109 "\t\t\"EventType\": \"Communication Failure/Timeout\",\n" +
110 "\t\t\"VMMigrationFlag\": \"Yes\",\n" +
111 "\t\t\"AffectedSubsystem\": \"Interconnect (Networking)\",\n" +
112 "\t\t\"timestamp\": \""+str(int(time.time()))+"\",\n" +
113 "\t\t\"UserAction\": \"Correct the issue highlighted in additional details and try again\"" +
114 "\t\n}, \n" +
115 "\t\"numAlerts\": \"1\" \n}");
116 return(errorMessageStr)
117 else:
118 return("Unknown Error: "+ str(err))
119
120"""
121 Sets the output width of the columns to display
122
123 @param keylist: list, list of strings representing the keys for the dictForOutput
124 @param numcols: the total number of columns in the final output
125 @param dictForOutput: dictionary, contains the information to print to the screen
126 @param colNames: list, The strings to use for the column headings, in order of the keylist
127 @return: A list of the column widths for each respective column.
128"""
129def setColWidth(keylist, numCols, dictForOutput, colNames):
130 colWidths = []
131 for x in range(0, numCols):
132 colWidths.append(0)
133 for key in dictForOutput:
134 for x in range(0, numCols):
135 colWidths[x] = max(colWidths[x], len(str(dictForOutput[key][keylist[x]])))
136
137 for x in range(0, numCols):
138 colWidths[x] = max(colWidths[x], len(colNames[x])) +2
139
140 return colWidths
141
142def loadPolicyTable(pathToPolicyTable):
143 policyTable = {}
144 if(os.path.exists(pathToPolicyTable)):
145 with open(pathToPolicyTable, 'r') as stream:
146 try:
147# contents = yaml.load(stream)
148 contents =json.load(stream)
149 policyTable = contents['events']
150 except yaml.YAMLError as err:
151 print(err)
152 return policyTable
153
154"""
155 Logs into the BMC and creates a session
156
157 @param host: string, the hostname or IP address of the bmc to log into
158 @param username: The user name for the bmc to log into
159 @param pw: The password for the BMC to log into
160 @param jsonFormat: boolean, flag that will only allow relevant data from user command to be display. This function becomes silent when set to true.
161 @return: Session object
162"""
163def login(host, username, pw,jsonFormat):
164 if(jsonFormat==False):
165 print("Attempting login...")
166 httpHeader = {'Content-Type':'application/json'}
167 mysess = requests.session()
168 try:
169 r = mysess.post('https://'+host+'/login', headers=httpHeader, json = {"data": [username, pw]}, verify=False, timeout=30)
170 loginMessage = json.loads(r.text)
171 if (loginMessage['status'] != "ok"):
172 print(loginMessage["data"]["description"].encode('utf-8'))
173 sys.exit(1)
174# if(sys.version_info < (3,0)):
175# urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
176# if sys.version_info >= (3,0):
177# requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
178 return mysess
179 except(requests.exceptions.Timeout):
180 print(connectionErrHandler(jsonFormat, "Timeout", None))
181 sys.exit(1)
182 except(requests.exceptions.ConnectionError) as err:
183 print(connectionErrHandler(jsonFormat, "ConnectionError", err))
184 sys.exit(1)
185
186"""
187 Logs out of the bmc and terminates the session
188
189 @param host: string, the hostname or IP address of the bmc to log out of
190 @param username: The user name for the bmc to log out of
191 @param pw: The password for the BMC to log out of
192 @param session: the active session to use
193 @param jsonFormat: boolean, flag that will only allow relevant data from user command to be display. This function becomes silent when set to true.
194"""
195def logout(host, username, pw, session, jsonFormat):
196 httpHeader = {'Content-Type':'application/json'}
197 r = session.post('https://'+host+'/logout', headers=httpHeader,json = {"data": [username, pw]}, verify=False, timeout=10)
198 if(jsonFormat==False):
199 if('"message": "200 OK"' in r.text):
200 print('User ' +username + ' has been logged out')
201
202"""
203 prints out the system inventory. deprecated see fruPrint and fruList
204
205 @param host: string, the hostname or IP address of the bmc
206 @param args: contains additional arguments used by the fru sub command
207 @param session: the active session to use
208 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
209"""
210def fru(host, args, session):
211 #url="https://"+host+"/org/openbmc/inventory/system/chassis/enumerate"
212
213 #print(url)
214 #res = session.get(url, headers=httpHeader, verify=False)
215 #print(res.text)
216 #sample = res.text
217
218 #inv_list = json.loads(sample)["data"]
219
220 url="https://"+host+"/xyz/openbmc_project/inventory/enumerate"
221 httpHeader = {'Content-Type':'application/json'}
222 res = session.get(url, headers=httpHeader, verify=False)
223 sample = res.text
224# inv_list.update(json.loads(sample)["data"])
225#
226# #determine column width's
227# colNames = ["FRU Name", "FRU Type", "Has Fault", "Is FRU", "Present", "Version"]
228# colWidths = setColWidth(["FRU Name", "fru_type", "fault", "is_fru", "present", "version"], 6, inv_list, colNames)
229#
230# print("FRU Name".ljust(colWidths[0])+ "FRU Type".ljust(colWidths[1]) + "Has Fault".ljust(colWidths[2]) + "Is FRU".ljust(colWidths[3])+
231# "Present".ljust(colWidths[4]) + "Version".ljust(colWidths[5]))
232# format the output
233# for key in sorted(inv_list.keys()):
234# keyParts = key.split("/")
235# isFRU = "True" if (inv_list[key]["is_fru"]==1) else "False"
236#
237# fruEntry = (keyParts[len(keyParts) - 1].ljust(colWidths[0]) + inv_list[key]["fru_type"].ljust(colWidths[1])+
238# inv_list[key]["fault"].ljust(colWidths[2])+isFRU.ljust(colWidths[3])+
239# inv_list[key]["present"].ljust(colWidths[4])+ inv_list[key]["version"].ljust(colWidths[5]))
240# if(isTTY):
241# if(inv_list[key]["is_fru"] == 1):
242# color = "green"
243# bold = True
244# else:
245# color='black'
246# bold = False
247# fruEntry = hilight(fruEntry, color, bold)
248# print (fruEntry)
249 return sample
250"""
251 prints out all inventory
252
253 @param host: string, the hostname or IP address of the bmc
254 @param args: contains additional arguments used by the fru sub command
255 @param session: the active session to use
256 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
257 @return returns the total fru list.
258"""
259def fruPrint(host, args, session):
260 url="https://"+host+"/xyz/openbmc_project/inventory/enumerate"
261 httpHeader = {'Content-Type':'application/json'}
262 res = session.get(url, headers=httpHeader, verify=False)
263# print(res.text)
264 frulist = res.text
265 url="https://"+host+"/xyz/openbmc_project/software/enumerate"
266 res = session.get(url, headers=httpHeader, verify=False)
267# print(res.text)
268 frulist = frulist +"\n" + res.text
269
270 return frulist
271
272"""
273 prints out all inventory or only a specific specified item
274
275 @param host: string, the hostname or IP address of the bmc
276 @param args: contains additional arguments used by the fru sub command
277 @param session: the active session to use
278 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
279"""
280def fruList(host, args, session):
281 if(args.items==True):
282 return fruPrint(host, args, session)
283 else:
284 return "not implemented at this time"
285
286
287"""
288 prints out the status of all FRUs
289
290 @param host: string, the hostname or IP address of the bmc
291 @param args: contains additional arguments used by the fru sub command
292 @param session: the active session to use
293 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
294"""
295def fruStatus(host, args, session):
296 print("fru status to be implemented")
297
298"""
299 prints out all sensors
300
301 @param host: string, the hostname or IP address of the bmc
302 @param args: contains additional arguments used by the sensor sub command
303 @param session: the active session to use
304 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
305"""
306def sensor(host, args, session):
307# url="https://"+host+"/org/openbmc/sensors/enumerate"
308 httpHeader = {'Content-Type':'application/json'}
309# print(url)
310# res = session.get(url, headers=httpHeader, verify=False, timeout=20)
311# print(res.text)
312 url="https://"+host+"/xyz/openbmc_project/sensors/enumerate"
313 res = session.get(url, headers=httpHeader, verify=False, timeout=20)
314
315 #Get OCC status
316 url="https://"+host+"/org/open_power/control/enumerate"
317 occres = session.get(url, headers=httpHeader, verify=False, timeout=20)
318 if not args.json:
319 colNames = ['sensor', 'type', 'units', 'value', 'target']
320 sensors = json.loads(res.text)["data"]
321 output = {}
322 for key in sensors:
323 senDict = {}
324 keyparts = key.split("/")
325 senDict['sensorName'] = keyparts[-1]
326 senDict['type'] = keyparts[-2]
327 senDict['units'] = sensors[key]['Unit'].split('.')[-1]
328 if('Scale' in sensors[key]):
329 scale = 10 ** sensors[key]['Scale']
330 else:
331 scale = 1
332 senDict['value'] = str(sensors[key]['Value'] * scale)
333 if 'Target' in sensors[key]:
334 senDict['target'] = str(sensors[key]['Target'])
335 else:
336 senDict['target'] = 'N/A'
337 output[senDict['sensorName']] = senDict
338
339 occstatus = json.loads(occres.text)["data"]
340 if '/org/open_power/control/occ0' in occstatus:
341 occ0 = occstatus["/org/open_power/control/occ0"]['OccActive']
342 if occ0 == 1:
343 occ0 = 'Active'
344 else:
345 occ0 = 'Inactive'
346 output['OCC0'] = {'sensorName':'OCC0', 'type': 'Discrete', 'units': 'N/A', 'value': occ0, 'target': 'Active'}
347 occ1 = occstatus["/org/open_power/control/occ1"]['OccActive']
348 if occ1 == 1:
349 occ1 = 'Active'
350 else:
351 occ1 = 'Inactive'
352 output['OCC1'] = {'sensorName':'OCC1', 'type': 'Discrete', 'units': 'N/A', 'value': occ0, 'target': 'Active'}
353 else:
354 output['OCC0'] = {'sensorName':'OCC0', 'type': 'Discrete', 'units': 'N/A', 'value': 'Inactive', 'target': 'Inactive'}
355 output['OCC1'] = {'sensorName':'OCC1', 'type': 'Discrete', 'units': 'N/A', 'value': 'Inactive', 'target': 'Inactive'}
356 keylist = ['sensorName', 'type', 'units', 'value', 'target']
357 colWidth = setColWidth(keylist, len(colNames), output, colNames)
358 row = ""
359 outputText = ""
360 for i in range(len(colNames)):
361 if (i != 0): row = row + "| "
362 row = row + colNames[i].ljust(colWidth[i])
363 outputText += row + "\n"
364 sortedKeys = list(output.keys()).sort
365 for key in sorted(output.keys()):
366 row = ""
367 for i in range(len(output[key])):
368 if (i != 0): row = row + "| "
369 row = row + output[key][keylist[i]].ljust(colWidth[i])
370 outputText += row + "\n"
371 return outputText
372 else:
373 return res.text + occres.text
374"""
375 prints out the bmc alerts
376
377 @param host: string, the hostname or IP address of the bmc
378 @param args: contains additional arguments used by the sel sub command
379 @param session: the active session to use
380 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
381"""
382def sel(host, args, session):
383
384 url="https://"+host+"/xyz/openbmc_project/logging/entry/enumerate"
385 httpHeader = {'Content-Type':'application/json'}
386 #print(url)
387 res = session.get(url, headers=httpHeader, verify=False, timeout=60)
388 return res.text
389
390
391"""
392 converts a boolean value to a human readable string value
393
394 @param value: boolean, the value to convert
395 @return: A string of "Yes" or "No"
396"""
397def boolToString(value):
398 if(value):
399 return "Yes"
400 else:
401 return "No"
402
403"""
404 parses the esel data and gets predetermined search terms
405
406 @param eselRAW: string, the raw esel string from the bmc
407 @return: A dictionary containing the quick snapshot data unless args.fullEsel is listed then a full PEL log is returned
408"""
409def parseESEL(args, eselRAW):
410 eselParts = {}
411 esel_bin = binascii.unhexlify(''.join(eselRAW.split()[16:]))
412 #search terms contains the search term as the key and the return dictionary key as it's value
413 searchTerms = { 'Signature Description':'signatureDescription', 'devdesc':'devdesc',
414 'Callout type': 'calloutType', 'Procedure':'procedure'}
415
416 with open('/tmp/esel.bin', 'wb') as f:
417 f.write(esel_bin)
418 errlPath = ""
419 #use the right errl file for the machine architecture
420 arch = platform.machine()
421 if(arch =='x86_64' or arch =='AMD64'):
422 if os.path.exists('/opt/ibm/ras/bin/x86_64/errl'):
423 errlPath = '/opt/ibm/ras/bin/x86_64/errl'
424 elif os.path.exists('errl/x86_64/errl'):
425 errlPath = 'errl/x86_64/errl'
426 else:
427 errlPath = 'x86_64/errl'
428 elif (platform.machine()=='ppc64le'):
429 if os.path.exists('/opt/ibm/ras/bin/ppc64le/errl'):
430 errlPath = '/opt/ibm/ras/bin/ppc64le/errl'
431 elif os.path.exists('errl/ppc64le/errl'):
432 errlPath = 'errl/ppc64le/errl'
433 else:
434 errlPath = 'ppc64le/errl'
435 else:
436 print("machine architecture not supported for parsing eSELs")
437 return eselParts
438
439
440
441 if(os.path.exists(errlPath)):
442 output= subprocess.check_output([errlPath, '-d', '--file=/tmp/esel.bin']).decode('utf-8')
443# output = proc.communicate()[0]
444 lines = output.split('\n')
445
446 if(hasattr(args, 'fullEsel')):
447 return output
448
449 for i in range(0, len(lines)):
450 lineParts = lines[i].split(':')
451 if(len(lineParts)>1): #ignore multi lines, output formatting lines, and other information
452 for term in searchTerms:
453 if(term in lineParts[0]):
454 temp = lines[i][lines[i].find(':')+1:].strip()[:-1].strip()
455 if lines[i+1].find(':') != -1:
456 if (len(lines[i+1].split(':')[0][1:].strip())==0):
457 while(len(lines[i][:lines[i].find(':')].strip())>2):
458 if((i+1) <= len(lines)):
459 i+=1
460 else:
461 i=i-1
462 break
463 temp = temp + lines[i][lines[i].find(':'):].strip()[:-1].strip()[:-1].strip()
464 eselParts[searchTerms[term]] = temp
465 os.remove('/tmp/esel.bin')
466 else:
467 print("errl file cannot be found")
468
469 return eselParts
470
471"""
472 sorts the sels by timestamp, then log entry number
473
474 @param events: Dictionary containing events
475 @return: list containing a list of the ordered log entries, and dictionary of keys
476"""
477def sortSELs(events):
478 logNumList = []
479 timestampList = []
480 eventKeyDict = {}
481 eventsWithTimestamp = {}
482 logNum2events = {}
483 for key in events:
484 if key == 'numAlerts': continue
485 if 'callout' in key: continue
486 timestamp = (events[key]['timestamp'])
487 if timestamp not in timestampList:
488 eventsWithTimestamp[timestamp] = [events[key]['logNum']]
489 else:
490 eventsWithTimestamp[timestamp].append(events[key]['logNum'])
491 #map logNumbers to the event dictionary keys
492 eventKeyDict[str(events[key]['logNum'])] = key
493
494 timestampList = list(eventsWithTimestamp.keys())
495 timestampList.sort()
496 for ts in timestampList:
497 if len(eventsWithTimestamp[ts]) > 1:
498 tmplist = eventsWithTimestamp[ts]
499 tmplist.sort()
500 logNumList = logNumList + tmplist
501 else:
502 logNumList = logNumList + eventsWithTimestamp[ts]
503
504 return [logNumList, eventKeyDict]
505
506"""
507 parses alerts in the IBM CER format, using an IBM policy Table
508
509 @param policyTable: dictionary, the policy table entries
510 @param selEntries: dictionary, the alerts retrieved from the bmc
511 @return: A dictionary of the parsed entries, in chronological order
512"""
513def parseAlerts(policyTable, selEntries, args):
514 eventDict = {}
515 eventNum =""
516 count = 0
517 esel = ""
518 eselParts = {}
519 i2cdevice= ""
520
521 'prepare and sort the event entries'
522 for key in selEntries:
523 if 'callout' not in key:
524 selEntries[key]['logNum'] = key.split('/')[-1]
525 selEntries[key]['timestamp'] = selEntries[key]['Timestamp']
526 sortedEntries = sortSELs(selEntries)
527 logNumList = sortedEntries[0]
528 eventKeyDict = sortedEntries[1]
529
530 for logNum in logNumList:
531 key = eventKeyDict[logNum]
532# for key in selEntries:
533 hasEsel=False
534 i2creadFail = False
535 if 'callout' in key:
536 continue
537 else:
538 messageID = str(selEntries[key]['Message'])
539 addDataPiece = selEntries[key]['AdditionalData']
540 calloutIndex = 0
541 calloutFound = False
542 for i in range(len(addDataPiece)):
543 if("CALLOUT_INVENTORY_PATH" in addDataPiece[i]):
544 calloutIndex = i
545 calloutFound = True
546 fruCallout = str(addDataPiece[calloutIndex]).split('=')[1]
547 if("CALLOUT_DEVICE_PATH" in addDataPiece[i]):
548 i2creadFail = True
549 i2cdevice = str(addDataPiece[i]).strip().split('=')[1]
550 i2cdevice = '/'.join(i2cdevice.split('/')[-4:])
551 fruCallout = 'I2C'
552 calloutFound = True
553 if("ESEL" in addDataPiece[i]):
554 esel = str(addDataPiece[i]).strip().split('=')[1]
555 if args.devdebug:
556 eselParts = parseESEL(args, esel)
557 hasEsel=True
558 if("GPU" in addDataPiece[i]):
559 fruCallout = '/xyz/openbmc_project/inventory/system/chassis/motherboard/gpu' + str(addDataPiece[i]).strip()[-1]
560 calloutFound = True
561 if("PROCEDURE" in addDataPiece[i]):
562 fruCallout = str(hex(int(str(addDataPiece[i]).split('=')[1])))[2:]
563 calloutFound = True
564
565 if(calloutFound):
566 policyKey = messageID +"||" + fruCallout
567 else:
568 policyKey = messageID
569 event = {}
570 eventNum = str(count)
571 if policyKey in policyTable:
572 for pkey in policyTable[policyKey]:
573 if(type(policyTable[policyKey][pkey])== bool):
574 event[pkey] = boolToString(policyTable[policyKey][pkey])
575 else:
576 if (i2creadFail and pkey == 'Message'):
577 event[pkey] = policyTable[policyKey][pkey] + ' ' +i2cdevice
578 else:
579 event[pkey] = policyTable[policyKey][pkey]
580 event['timestamp'] = selEntries[key]['Timestamp']
581 event['resolved'] = bool(selEntries[key]['Resolved'])
582 if(hasEsel):
583 if args.devdebug:
584 event['eselParts'] = eselParts
585 event['raweSEL'] = esel
586 event['logNum'] = key.split('/')[-1]
587 eventDict['event' + eventNum] = event
588
589 else:
590 severity = str(selEntries[key]['Severity']).split('.')[-1]
591 if severity == 'Error':
592 severity = 'Critical'
593 eventDict['event'+eventNum] = {}
594 eventDict['event' + eventNum]['error'] = "error: Not found in policy table: " + policyKey
595 eventDict['event' + eventNum]['timestamp'] = selEntries[key]['Timestamp']
596 eventDict['event' + eventNum]['Severity'] = severity
597 if(hasEsel):
598 if args.devdebug:
599 eventDict['event' +eventNum]['eselParts'] = eselParts
600 eventDict['event' +eventNum]['raweSEL'] = esel
601 eventDict['event' +eventNum]['logNum'] = key.split('/')[-1]
602 eventDict['event' +eventNum]['resolved'] = bool(selEntries[key]['Resolved'])
603 #print("Event entry "+eventNum+": not found in policy table: " + policyKey)
604 count += 1
605 return eventDict
606
607
608"""
609 displays alerts in human readable format
610
611 @param events: Dictionary containing events
612 @return:
613"""
614def selDisplay(events, args):
615 activeAlerts = []
616 historyAlerts = []
617 sortedEntries = sortSELs(events)
618 logNumList = sortedEntries[0]
619 eventKeyDict = sortedEntries[1]
620 keylist = ['Entry', 'ID', 'Timestamp', 'Serviceable', 'Severity','Message']
621 if(args.devdebug):
622 colNames = ['Entry', 'ID', 'Timestamp', 'Serviceable', 'Severity','Message', 'eSEL contents']
623 keylist.append('eSEL')
624 else:
625 colNames = ['Entry', 'ID', 'Timestamp', 'Serviceable', 'Severity', 'Message']
626 for log in logNumList:
627 selDict = {}
628 alert = events[eventKeyDict[str(log)]]
629 if('error' in alert):
630 selDict['Entry'] = alert['logNum']
631 selDict['ID'] = 'Unknown'
632 selDict['Timestamp'] = datetime.datetime.fromtimestamp(int(alert['timestamp']/1000)).strftime("%Y-%m-%d %H:%M:%S")
633 msg = alert['error']
634 polMsg = msg.split("policy table:")[0]
635 msg = msg.split("policy table:")[1]
636 msgPieces = msg.split("||")
637 err = msgPieces[0]
638 if(err.find("org.open_power.")!=-1):
639 err = err.split("org.open_power.")[1]
640 elif(err.find("xyz.openbmc_project.")!=-1):
641 err = err.split("xyz.openbmc_project.")[1]
642 else:
643 err = msgPieces[0]
644 callout = ""
645 if len(msgPieces) >1:
646 callout = msgPieces[1]
647 if(callout.find("/org/open_power/")!=-1):
648 callout = callout.split("/org/open_power/")[1]
649 elif(callout.find("/xyz/openbmc_project/")!=-1):
650 callout = callout.split("/xyz/openbmc_project/")[1]
651 else:
652 callout = msgPieces[1]
653 selDict['Message'] = polMsg +"policy table: "+ err + "||" + callout
654 selDict['Serviceable'] = 'Unknown'
655 selDict['Severity'] = alert['Severity']
656 else:
657 selDict['Entry'] = alert['logNum']
658 selDict['ID'] = alert['CommonEventID']
659 selDict['Timestamp'] = datetime.datetime.fromtimestamp(int(alert['timestamp']/1000)).strftime("%Y-%m-%d %H:%M:%S")
660 selDict['Message'] = alert['Message']
661 selDict['Serviceable'] = alert['Serviceable']
662 selDict['Severity'] = alert['Severity']
663
664
665 eselOrder = ['refCode','signatureDescription', 'eselType', 'devdesc', 'calloutType', 'procedure']
666 if ('eselParts' in alert and args.devdebug):
667 eselOutput = ""
668 for item in eselOrder:
669 if item in alert['eselParts']:
670 eselOutput = eselOutput + item + ": " + alert['eselParts'][item] + " | "
671 selDict['eSEL'] = eselOutput
672 else:
673 if args.devdebug:
674 selDict['eSEL'] = "None"
675
676 if not alert['resolved']:
677 activeAlerts.append(selDict)
678 else:
679 historyAlerts.append(selDict)
680 mergedOutput = activeAlerts + historyAlerts
681 colWidth = setColWidth(keylist, len(colNames), dict(enumerate(mergedOutput)), colNames)
682
683 output = ""
684 if(len(activeAlerts)>0):
685 row = ""
686 output +="----Active Alerts----\n"
687 for i in range(0, len(colNames)):
688 if i!=0: row =row + "| "
689 row = row + colNames[i].ljust(colWidth[i])
690 output += row + "\n"
691
692 for i in range(0,len(activeAlerts)):
693 row = ""
694 for j in range(len(activeAlerts[i])):
695 if (j != 0): row = row + "| "
696 row = row + activeAlerts[i][keylist[j]].ljust(colWidth[j])
697 output += row + "\n"
698
699 if(len(historyAlerts)>0):
700 row = ""
701 output+= "----Historical Alerts----\n"
702 for i in range(len(colNames)):
703 if i!=0: row =row + "| "
704 row = row + colNames[i].ljust(colWidth[i])
705 output += row + "\n"
706
707 for i in range(0, len(historyAlerts)):
708 row = ""
709 for j in range(len(historyAlerts[i])):
710 if (j != 0): row = row + "| "
711 row = row + historyAlerts[i][keylist[j]].ljust(colWidth[j])
712 output += row + "\n"
713# print(events[eventKeyDict[str(log)]])
714 return output
715
716"""
717 prints out all bmc alerts
718
719 @param host: string, the hostname or IP address of the bmc
720 @param args: contains additional arguments used by the fru sub command
721 @param session: the active session to use
722 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
723"""
724def selPrint(host, args, session):
725 if(args.policyTableLoc is None):
726 if os.path.exists('policyTable.json'):
727 ptableLoc = "policyTable.json"
728 elif os.path.exists('/opt/ibm/ras/lib/policyTable.json'):
729 ptableLoc = '/opt/ibm/ras/lib/policyTable.json'
730 else:
731 ptableLoc = 'lib/policyTable.json'
732 else:
733 ptableLoc = args.policyTableLoc
734 policyTable = loadPolicyTable(ptableLoc)
735 rawselEntries = ""
736 if(hasattr(args, 'fileloc') and args.fileloc is not None):
737 if os.path.exists(args.fileloc):
738 with open(args.fileloc, 'r') as selFile:
739 selLines = selFile.readlines()
740 rawselEntries = ''.join(selLines)
741 else:
742 print("Error: File not found")
743 sys.exit(1)
744 else:
745 rawselEntries = sel(host, args, session)
746 loadFailed = False
747 try:
748 selEntries = json.loads(rawselEntries)
749 except ValueError:
750 loadFailed = True
751 if loadFailed:
752 cleanSels = json.dumps(rawselEntries).replace('\\n', '')
753 #need to load json twice as original content was string escaped a second time
754 selEntries = json.loads(json.loads(cleanSels))
755 selEntries = selEntries['data']
756 cerList = {}
757 if 'description' in selEntries:
758 if(args.json):
759 return("{\n\t\"numAlerts\": 0\n}")
760 else:
761 return("No log entries found")
762
763 else:
764 if(len(policyTable)>0):
765 events = parseAlerts(policyTable, selEntries, args)
766 if(args.json):
767 events["numAlerts"] = len(events)
768 retValue = str(json.dumps(events, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False))
769 return retValue
770 elif(hasattr(args, 'fullSel')):
771 return events
772 else:
773 #get log numbers to order event entries sequentially
774 return selDisplay(events, args)
775 else:
776 if(args.json):
777 return selEntries
778 else:
779 print("error: Policy Table not found.")
780 return selEntries
781"""
782 prints out all all bmc alerts, or only prints out the specified alerts
783
784 @param host: string, the hostname or IP address of the bmc
785 @param args: contains additional arguments used by the fru sub command
786 @param session: the active session to use
787 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
788"""
789def selList(host, args, session):
790 return(sel(host, args, session))
791
792"""
793 clears all alerts
794
795 @param host: string, the hostname or IP address of the bmc
796 @param args: contains additional arguments used by the fru sub command
797 @param session: the active session to use
798 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
799"""
800def selClear(host, args, session):
801 url="https://"+host+"/xyz/openbmc_project/logging/action/deleteAll"
802 httpHeader = {'Content-Type':'application/json'}
803 data = "{\"data\": [] }"
804 #print(url)
805 res = session.post(url, headers=httpHeader, data=data, verify=False, timeout=30)
806 if res.status_code == 200:
807 return "The Alert Log has been cleared. Please allow a few minutes for the action to complete."
808 else:
809 print("Unable to clear the logs, trying to clear 1 at a time")
810 sels = json.loads(sel(host, args, session))['data']
811 for key in sels:
812 if 'callout' not in key:
813 logNum = key.split('/')[-1]
814 url = "https://"+ host+ "/xyz/openbmc_project/logging/entry/"+logNum+"/action/Delete"
815 try:
816 session.post(url, headers=httpHeader, data=data, verify=False, timeout=30)
817 except(requests.exceptions.Timeout):
818 return connectionErrHandler(args.json, "Timeout", None)
819 sys.exit(1)
820 except(requests.exceptions.ConnectionError) as err:
821 return connectionErrHandler(args.json, "ConnectionError", err)
822 sys.exit(1)
823 return ('Sel clearing complete')
824
825def selSetResolved(host, args, session):
826 url="https://"+host+"/xyz/openbmc_project/logging/entry/" + str(args.selNum) + "/attr/Resolved"
827 httpHeader = {'Content-Type':'application/json'}
828 data = "{\"data\": 1 }"
829 #print(url)
830 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
831 if res.status_code == 200:
832 return "Sel entry "+ str(args.selNum) +" is now set to resolved"
833 else:
834 return "Unable to set the alert to resolved"
835
836# """
837# gathers the esels. deprecated
838#
839# @param host: string, the hostname or IP address of the bmc
840# @param args: contains additional arguments used by the fru sub command
841# @param session: the active session to use
842# @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
843# """
844# def getESEL(host, args, session):
845# selentry= args.selNum
846# url="https://"+host+"/xyz/openbmc_project/logging/entry" + str(selentry)
847# httpHeader = {'Content-Type':'application/json'}
848# print(url)
849# res = session.get(url, headers=httpHeader, verify=False, timeout=20)
850# e = res.json()
851# if e['Message'] != 'org.open_power.Error.Host.Event' and\
852# e['Message'] != 'org.open_power.Error.Host.Event.Event':
853# raise Exception("Event is not from Host: " + e['Message'])
854# for d in e['AdditionalData']:
855# data = d.split("=")
856# tag = data.pop(0)
857# if tag != 'ESEL':
858# continue
859# data = "=".join(data)
860# if args.binary:
861# data = data.split(" ")
862# if '' == data[-1]:
863# data.pop()
864# data = "".join(map(lambda x: chr(int(x, 16)), data))
865# print(data)
866
867"""
868 called by the chassis function. Controls the power state of the chassis, or gets the status
869
870 @param host: string, the hostname or IP address of the bmc
871 @param args: contains additional arguments used by the fru sub command
872 @param session: the active session to use
873 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
874"""
875def chassisPower(host, args, session):
876 if(args.powcmd == 'on'):
877 print("Attempting to Power on...:")
878 url="https://"+host+"/xyz/openbmc_project/state/host0/attr/RequestedHostTransition"
879 httpHeader = {'Content-Type':'application/json',}
880 data = '{"data":"xyz.openbmc_project.State.Host.Transition.On"}'
881 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=20)
882 return res.text
883 elif(args.powcmd == 'off'):
884 print("Attempting to Power off...:")
885 url="https://"+host+"/xyz/openbmc_project/state/host0/attr/RequestedHostTransition"
886 httpHeader = {'Content-Type':'application/json'}
887 data = '{"data":"xyz.openbmc_project.State.Host.Transition.Off"}'
888 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=20)
889 return res.text
890 elif(args.powcmd == 'status'):
891 url="https://"+host+"/xyz/openbmc_project/state/chassis0/attr/CurrentPowerState"
892 httpHeader = {'Content-Type':'application/json'}
893# print(url)
894 res = session.get(url, headers=httpHeader, verify=False, timeout=20)
895 chassisState = json.loads(res.text)['data'].split('.')[-1]
896 url="https://"+host+"/xyz/openbmc_project/state/host0/attr/CurrentHostState"
897 res = session.get(url, headers=httpHeader, verify=False, timeout=20)
898 hostState = json.loads(res.text)['data'].split('.')[-1]
899 url="https://"+host+"/xyz/openbmc_project/state/bmc0/attr/CurrentBMCState"
900 res = session.get(url, headers=httpHeader, verify=False, timeout=20)
901 bmcState = json.loads(res.text)['data'].split('.')[-1]
902 if(args.json):
903 outDict = {"Chassis Power State" : chassisState, "Host Power State" : hostState, "BMC Power State":bmcState}
904 return json.dumps(outDict, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False)
905 else:
906 return "Chassis Power State: " +chassisState + "\nHost Power State: " + hostState + "\nBMC Power State: " + bmcState
907 else:
908 return "Invalid chassis power command"
909
910"""
911 called by the chassis function. Controls the identify led of the chassis. Sets or gets the state
912
913 @param host: string, the hostname or IP address of the bmc
914 @param args: contains additional arguments used by the fru sub command
915 @param session: the active session to use
916 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
917"""
918def chassisIdent(host, args, session):
919 if(args.identcmd == 'on'):
920 print("Attempting to turn identify light on...:")
921 url="https://"+host+"/xyz/openbmc_project/led/groups/enclosure_identify/attr/Asserted"
922 httpHeader = {'Content-Type':'application/json',}
923 data = '{"data":true}'
924 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=20)
925 return res.text
926 elif(args.identcmd == 'off'):
927 print("Attempting to turn identify light off...:")
928 url="https://"+host+"/xyz/openbmc_project/led/groups/enclosure_identify/attr/Asserted"
929 httpHeader = {'Content-Type':'application/json'}
930 data = '{"data":false}'
931 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=20)
932 return res.text
933 elif(args.identcmd == 'status'):
934 url="https://"+host+"/xyz/openbmc_project/led/groups/enclosure_identify"
935 httpHeader = {'Content-Type':'application/json'}
936# print(url)
937 res = session.get(url, headers=httpHeader, verify=False, timeout=20)
938 status = json.loads(res.text)['data']
939 if(args.json):
940 return status
941 else:
942 if status['Asserted'] == 0:
943 return "Identify light is off"
944 else:
945 return "Identify light is blinking"
946 else:
947 return "Invalid chassis identify command"
948
949"""
950 controls the different chassis commands
951
952 @param host: string, the hostname or IP address of the bmc
953 @param args: contains additional arguments used by the fru sub command
954 @param session: the active session to use
955 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
956"""
957def chassis(host, args, session):
958 if(hasattr(args, 'powcmd')):
959 result = chassisPower(host,args,session)
960 elif(hasattr(args, 'identcmd')):
961 result = chassisIdent(host, args, session)
962 else:
963 return "to be completed"
964 return result
965"""
966 Downloads a dump file from the bmc
967
968 @param host: string, the hostname or IP address of the bmc
969 @param args: contains additional arguments used by the collectServiceData sub command
970 @param session: the active session to use
971 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
972"""
973def bmcDumpRetrieve(host, args, session):
974 httpHeader = {'Content-Type':'application/json'}
975 dumpNum = args.dumpNum
976 if (args.dumpSaveLoc is not None):
977 saveLoc = args.dumpSaveLoc
978 else:
979 saveLoc = '/tmp'
980 url ='https://'+host+'/download/dump/' + str(dumpNum)
981 try:
982 r = session.get(url, headers=httpHeader, stream=True, verify=False, timeout=20)
983 if (args.dumpSaveLoc is not None):
984 if os.path.exists(saveLoc):
985 if saveLoc[-1] != os.path.sep:
986 saveLoc = saveLoc + os.path.sep
987 filename = saveLoc + host+'-dump' + str(dumpNum) + '.tar.xz'
988
989 else:
990 return 'Invalid save location specified'
991 else:
992 filename = '/tmp/' + host+'-dump' + str(dumpNum) + '.tar.xz'
993
994 with open(filename, 'wb') as f:
995 for chunk in r.iter_content(chunk_size =1024):
996 if chunk:
997 f.write(chunk)
998 return 'Saved as ' + filename
999
1000 except(requests.exceptions.Timeout):
1001 return connectionErrHandler(args.json, "Timeout", None)
1002 sys.exit(1)
1003 except(requests.exceptions.ConnectionError) as err:
1004 return connectionErrHandler(args.json, "ConnectionError", err)
1005 sys.exit(1)
1006
1007"""
1008 Lists the number of dump files on the bmc
1009
1010 @param host: string, the hostname or IP address of the bmc
1011 @param args: contains additional arguments used by the collectServiceData sub command
1012 @param session: the active session to use
1013 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1014"""
1015def bmcDumpList(host, args, session):
1016 httpHeader = {'Content-Type':'application/json'}
1017 url ='https://'+host+'/xyz/openbmc_project/dump/list'
1018 try:
1019 r = session.get(url, headers=httpHeader, verify=False, timeout=20)
1020 dumpList = json.loads(r.text)
1021 return r.text
1022 except(requests.exceptions.Timeout):
1023 return connectionErrHandler(args.json, "Timeout", None)
1024 sys.exit(1)
1025 except(requests.exceptions.ConnectionError) as err:
1026 return connectionErrHandler(args.json, "ConnectionError", err)
1027 sys.exit(1)
1028
1029
1030"""
1031 Deletes BMC dump files from the bmc
1032
1033 @param host: string, the hostname or IP address of the bmc
1034 @param args: contains additional arguments used by the collectServiceData sub command
1035 @param session: the active session to use
1036 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1037"""
1038def bmcDumpDelete(host, args, session):
1039 httpHeader = {'Content-Type':'application/json'}
1040 dumpList = []
1041 successList = []
1042 failedList = []
1043 if args.dumpNum is not None:
1044 if isinstance(args.dumpNum, list):
1045 dumpList = args.dumpNum
1046 else:
1047 dumpList.append(args.dumpNum)
1048 for dumpNum in dumpList:
1049 url ='https://'+host+'/xyz/openbmc_project/dump/entry/'+str(dumpNum)+'/action/Delete'
1050 try:
1051 r = session.post(url, headers=httpHeader, json = {"data": []}, verify=False, timeout=30)
1052 if r.status_code == 200:
1053 successList.append(str(dumpNum))
1054 #return('Dump ' + str(dumpNum) + ' deleted')
1055 else:
1056 failedList.append(str(dumpNum))
1057 #return('Unable to delete dump ' + str(dumpNum))
1058 except(requests.exceptions.Timeout):
1059 return connectionErrHandler(args.json, "Timeout", None)
1060# sys.exit(1)
1061 except(requests.exceptions.ConnectionError) as err:
1062 return connectionErrHandler(args.json, "ConnectionError", err)
1063# sys.exit(1)
1064 output = "Successfully deleted dumps: " + ', '.join(successList)
1065 if(len(failedList)>0):
1066 output+= '\nFailed to delete dumps: ' + ', '.join(failedList)
1067 return output
1068 else:
1069 return 'You must specify an entry number to delete'
1070
1071"""
1072 Deletes All BMC dump files from the bmc
1073
1074 @param host: string, the hostname or IP address of the bmc
1075 @param args: contains additional arguments used by the collectServiceData sub command
1076 @param session: the active session to use
1077 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1078"""
1079def bmcDumpDeleteAll(host, args, session):
1080 dumpList = json.loads(bmcDumpList(host, args, session))['data']
1081 d = vars(args)
1082 dumpNums = []
1083 for dump in dumpList:
1084 if '/xyz/openbmc_project/dump/internal/manager' not in dump:
1085 dumpNums.append(int(dump.strip().split('/')[-1]))
1086 d['dumpNum'] = dumpNums
1087
1088 return bmcDumpDelete(host, args, session)
1089
1090"""
1091 Creates a bmc dump file
1092
1093 @param host: string, the hostname or IP address of the bmc
1094 @param args: contains additional arguments used by the collectServiceData sub command
1095 @param session: the active session to use
1096 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1097"""
1098def bmcDumpCreate(host, args, session):
1099 httpHeader = {'Content-Type':'application/json'}
1100 url = 'https://'+host+'/xyz/openbmc_project/dump/action/CreateDump'
1101 try:
1102 r = session.post(url, headers=httpHeader, json = {"data": []}, verify=False, timeout=30)
1103 if('"message": "200 OK"' in r.text and not args.json):
1104 return ('Dump successfully created')
1105 else:
1106 return ('Failed to create dump')
1107 except(requests.exceptions.Timeout):
1108 return connectionErrHandler(args.json, "Timeout", None)
1109 except(requests.exceptions.ConnectionError) as err:
1110 return connectionErrHandler(args.json, "ConnectionError", err)
1111
1112
1113
1114"""
1115 Collects all data needed for service from the BMC
1116
1117 @param host: string, the hostname or IP address of the bmc
1118 @param args: contains additional arguments used by the collectServiceData sub command
1119 @param session: the active session to use
1120 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1121"""
1122def collectServiceData(host, args, session):
1123 #create a bmc dump
1124 dumpcount = len(json.loads(bmcDumpList(host, args, session))['data'])
1125 try:
1126 dumpcreated = bmcDumpCreate(host, args, session)
1127 except Exception as e:
1128 print('failed to create a bmc dump')
1129
1130
1131 #Collect Inventory
1132 try:
1133 args.silent = True
1134 myDir = '/tmp/' + host + "--" + datetime.datetime.now().strftime("%Y-%m-%d_%H.%M.%S")
1135 os.makedirs(myDir)
1136 filelist = []
1137 frulist = fruPrint(host, args, session)
1138 with open(myDir +'/inventory.txt', 'w') as f:
1139 f.write(frulist)
1140 print("Inventory collected and stored in " + myDir + "/inventory.txt")
1141 filelist.append(myDir+'/inventory.txt')
1142 except Exception as e:
1143 print("Failed to collect inventory")
1144
1145 #Read all the sensor and OCC status
1146 try:
1147 sensorReadings = sensor(host, args, session)
1148 with open(myDir +'/sensorReadings.txt', 'w') as f:
1149 f.write(sensorReadings)
1150 print("Sensor readings collected and stored in " +myDir + "/sensorReadings.txt")
1151 filelist.append(myDir+'/sensorReadings.txt')
1152 except Exception as e:
1153 print("Failed to collect sensor readings")
1154
1155 #Collect all of the LEDs status
1156 try:
1157 url="https://"+host+"/xyz/openbmc_project/led/enumerate"
1158 httpHeader = {'Content-Type':'application/json'}
1159 leds = session.get(url, headers=httpHeader, verify=False, timeout=20)
1160 with open(myDir +'/ledStatus.txt', 'w') as f:
1161 f.write(leds.text)
1162 print("System LED status collected and stored in "+myDir +"/ledStatus.txt")
1163 filelist.append(myDir+'/ledStatus.txt')
1164 except Exception as e:
1165 print("Failed to collect LED status")
1166
1167 #Collect the bmc logs
1168 try:
1169 sels = selPrint(host,args,session)
1170 with open(myDir +'/SELshortlist.txt', 'w') as f:
1171 f.write(str(sels))
1172 print("sel short list collected and stored in "+myDir +"/SELshortlist.txt")
1173 filelist.append(myDir+'/SELshortlist.txt')
1174 time.sleep(2)
1175
1176 d = vars(args)
1177 d['json'] = True
1178 d['fullSel'] = True
1179 parsedfullsels = json.loads(selPrint(host, args, session))
1180 d['fullEsel'] = True
1181 sortedSELs = sortSELs(parsedfullsels)
1182 with open(myDir +'/parsedSELs.txt', 'w') as f:
1183 for log in sortedSELs[0]:
1184 esel = ""
1185 parsedfullsels[sortedSELs[1][str(log)]]['timestamp'] = datetime.datetime.fromtimestamp(int(parsedfullsels[sortedSELs[1][str(log)]]['timestamp']/1000)).strftime("%Y-%m-%d %H:%M:%S")
1186 if ('raweSEL' in parsedfullsels[sortedSELs[1][str(log)]] and args.devdebug):
1187 esel = parsedfullsels[sortedSELs[1][str(log)]]['raweSEL']
1188 del parsedfullsels[sortedSELs[1][str(log)]]['raweSEL']
1189 f.write(json.dumps(parsedfullsels[sortedSELs[1][str(log)]],sort_keys=True, indent=4, separators=(',', ': ')))
1190 if(args.devdebug and esel != ""):
1191 f.write(parseESEL(args, esel))
1192 print("fully parsed sels collected and stored in "+myDir +"/parsedSELs.txt")
1193 filelist.append(myDir+'/parsedSELs.txt')
1194 except Exception as e:
1195 print("Failed to collect system event logs")
1196 print(e)
1197
1198 #collect RAW bmc enumeration
1199 try:
1200 url="https://"+host+"/xyz/openbmc_project/enumerate"
1201 print("Attempting to get a full BMC enumeration")
1202 fullDump = session.get(url, headers=httpHeader, verify=False, timeout=120)
1203 with open(myDir +'/bmcFullRaw.txt', 'w') as f:
1204 f.write(fullDump.text)
1205 print("RAW BMC data collected and saved into "+myDir +"/bmcFullRaw.txt")
1206 filelist.append(myDir+'/bmcFullRaw.txt')
1207 except Exception as e:
1208 print("Failed to collect bmc full enumeration")
1209
1210 #collect the dump files
1211 waitingForNewDump = True
1212 count = 0;
1213 while(waitingForNewDump):
1214 dumpList = json.loads(bmcDumpList(host, args, session))['data']
1215 if len(dumpList) > dumpcount:
1216 waitingForNewDump = False
1217 break;
1218 elif(count>30):
1219 print("Timed out waiting for bmc to make a new dump file. Dump space may be full.")
1220 break;
1221 else:
1222 time.sleep(2)
1223 count += 1
1224 try:
1225 print('Collecting bmc dump files')
1226 d['dumpSaveLoc'] = myDir
1227 dumpList = json.loads(bmcDumpList(host, args, session))['data']
1228 for dump in dumpList:
1229 if '/xyz/openbmc_project/dump/internal/manager' not in dump:
1230 d['dumpNum'] = int(dump.strip().split('/')[-1])
1231 print('retrieving dump file ' + str(d['dumpNum']))
1232 filename = bmcDumpRetrieve(host, args, session).split('Saved as ')[-1]
1233 filelist.append(filename)
1234 time.sleep(2)
1235 except Exception as e:
1236 print("Failed to collect bmc dump files")
1237 print(e)
1238
1239 #create the zip file
1240 try:
1241 filename = myDir.split('/tmp/')[-1] + '.zip'
1242 zf = zipfile.ZipFile(myDir+'/' + filename, 'w')
1243 for myfile in filelist:
1244 zf.write(myfile, os.path.basename(myfile))
1245 zf.close()
1246 except Exception as e:
1247 print("Failed to create zip file with collected information")
1248 return "data collection complete"
1249
1250"""
1251 handles various bmc level commands, currently bmc rebooting
1252
1253 @param host: string, the hostname or IP address of the bmc
1254 @param args: contains additional arguments used by the bmc sub command
1255 @param session: the active session to use
1256 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1257"""
1258def bmc(host, args, session):
1259 if(args.info):
1260 return "To be completed"
1261 if(args.type is not None):
1262 return bmcReset(host, args, session)
1263
1264"""
1265 controls resetting the bmc. warm reset reboots the bmc, cold reset removes the configuration and reboots.
1266
1267 @param host: string, the hostname or IP address of the bmc
1268 @param args: contains additional arguments used by the bmcReset sub command
1269 @param session: the active session to use
1270 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1271"""
1272def bmcReset(host, args, session):
1273 if(args.type == "warm"):
1274 print("\nAttempting to reboot the BMC...:")
1275 url="https://"+host+"/xyz/openbmc_project/state/bmc0/attr/RequestedBMCTransition"
1276 httpHeader = {'Content-Type':'application/json'}
1277 data = '{"data":"xyz.openbmc_project.State.BMC.Transition.Reboot"'
1278 res = session.post(url, headers=httpHeader, data=data, verify=False, timeout=20)
1279 return res.text
1280 elif(args.type =="cold"):
1281 return "cold reset not available at this time."
1282 else:
1283 return "invalid command"
1284"""
1285 creates the parser for the command line along with help for each command and subcommand
1286
1287 @return: returns the parser for the command line
1288"""
1289def createCommandParser():
1290 parser = argparse.ArgumentParser(description='Process arguments')
1291 parser.add_argument("-H", "--host", required=True, help='A hostname or IP for the BMC')
1292 parser.add_argument("-U", "--user", required=True, help='The username to login with')
1293 #parser.add_argument("-v", "--verbose", help='provides more detail')
1294 group = parser.add_mutually_exclusive_group()
1295 group.add_argument("-A", "--askpw", action='store_true', help='prompt for password')
1296 group.add_argument("-P", "--PW", help='Provide the password in-line')
1297 parser.add_argument('-j', '--json', action='store_true', help='output json data only')
1298 parser.add_argument('-t', '--policyTableLoc', help='The location of the policy table to parse alerts')
1299 parser.add_argument('-c', '--CerFormat', action='store_true', help=argparse.SUPPRESS)
1300 parser.add_argument('-T', '--procTime', action='store_true', help= argparse.SUPPRESS)
1301 subparsers = parser.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help")
1302
1303 #fru command
1304 parser_inv = subparsers.add_parser("fru", help='Work with platform inventory')
1305 #fru print
1306 inv_subparser = parser_inv.add_subparsers(title='subcommands', description='valid inventory actions', help="valid inventory actions")
1307 inv_print = inv_subparser.add_parser("print", help="prints out a list of all FRUs")
1308 inv_print.set_defaults(func=fruPrint)
1309 #fru list [0....n]
1310 inv_list = inv_subparser.add_parser("list", help="print out details on selected FRUs. Specifying no items will list the entire inventory")
1311 inv_list.add_argument('items', nargs='?', help="print out details on selected FRUs. Specifying no items will list the entire inventory")
1312 inv_list.set_defaults(func=fruList)
1313 #fru status
1314 inv_status = inv_subparser.add_parser("status", help="prints out the status of all FRUs")
1315 inv_status.set_defaults(func=fruStatus)
1316 #parser_inv.add_argument('status', action='store_true', help="Lists the status of all BMC detected components")
1317 #parser_inv.add_argument('num', type=int, action='store_true', help="The number of the FRU to list")
1318 #inv_status.set_defaults(func=fruStatus)
1319
1320 #sensors command
1321 parser_sens = subparsers.add_parser("sensors", help="Work with platform sensors")
1322 sens_subparser=parser_sens.add_subparsers(title='subcommands', description='valid sensor actions', help='valid sensor actions')
1323 #sensor print
1324 sens_print= sens_subparser.add_parser('print', help="prints out a list of all Sensors.")
1325 sens_print.set_defaults(func=sensor)
1326 #sensor list[0...n]
1327 sens_list=sens_subparser.add_parser("list", help="Lists all Sensors in the platform. Specify a sensor for full details. ")
1328 sens_list.add_argument("sensNum", nargs='?', help="The Sensor number to get full details on" )
1329 sens_list.set_defaults(func=sensor)
1330
1331
1332 #sel command
1333 parser_sel = subparsers.add_parser("sel", help="Work with platform alerts")
1334 sel_subparser = parser_sel.add_subparsers(title='subcommands', description='valid SEL actions', help = 'valid SEL actions')
1335
1336 #sel print
1337 sel_print = sel_subparser.add_parser("print", help="prints out a list of all sels in a condensed list")
1338 sel_print.add_argument('-d', '--devdebug', action='store_true', help=argparse.SUPPRESS)
1339 sel_print.add_argument('-v', '--verbose', action='store_true', help="Changes the output to being very verbose")
1340 sel_print.add_argument('-f', '--fileloc', help='Parse a file instead of the BMC output')
1341 sel_print.set_defaults(func=selPrint)
1342 #sel list
1343 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")
1344 sel_list.add_argument("selNum", nargs='?', type=int, help="The SEL entry to get details on")
1345 sel_list.set_defaults(func=selList)
1346
1347 sel_get = sel_subparser.add_parser("get", help="Gets the verbose details of a specified SEL entry")
1348 sel_get.add_argument('selNum', type=int, help="the number of the SEL entry to get")
1349 sel_get.set_defaults(func=selList)
1350
1351 sel_clear = sel_subparser.add_parser("clear", help="Clears all entries from the SEL")
1352 sel_clear.set_defaults(func=selClear)
1353
1354 sel_setResolved = sel_subparser.add_parser("resolve", help="Sets the sel entry to resolved")
1355 sel_setResolved.add_argument('selNum', type=int, help="the number of the SEL entry to resolve")
1356 sel_setResolved.set_defaults(func=selSetResolved)
1357
1358 parser_chassis = subparsers.add_parser("chassis", help="Work with chassis power and status")
1359 chas_sub = parser_chassis.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help")
1360
1361 parser_chassis.add_argument('status', action='store_true', help='Returns the current status of the platform')
1362 parser_chassis.set_defaults(func=chassis)
1363
1364 parser_chasPower = chas_sub.add_parser("power", help="Turn the chassis on or off, check the power state")
1365 parser_chasPower.add_argument('powcmd', choices=['on','off', 'status'], help='The value for the power command. on, off, or status')
1366 parser_chasPower.set_defaults(func=chassisPower)
1367
1368 #control the chassis identify led
1369 parser_chasIdent = chas_sub.add_parser("identify", help="Control the chassis identify led")
1370 parser_chasIdent.add_argument('identcmd', choices=['on', 'off', 'status'], help='The control option for the led: on, off, blink, status')
1371 parser_chasIdent.set_defaults(func=chassisIdent)
1372
1373 #collect service data
1374 parser_servData = subparsers.add_parser("collect_service_data", help="Collect all bmc data needed for service")
1375 parser_servData.add_argument('-d', '--devdebug', action='store_true', help=argparse.SUPPRESS)
1376 parser_servData.set_defaults(func=collectServiceData)
1377
1378 #work with bmc dumps
1379 parser_bmcdump = subparsers.add_parser("dump", help="Work with bmc dump files")
1380 bmcDump_sub = parser_bmcdump.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help")
1381 dump_Create = bmcDump_sub.add_parser('create', help="Create a bmc dump")
1382 dump_Create.set_defaults(func=bmcDumpCreate)
1383
1384
1385 dump_list = bmcDump_sub.add_parser('list', help="list all bmc dump files")
1386 dump_list.set_defaults(func=bmcDumpList)
1387
1388 parserdumpdelete = bmcDump_sub.add_parser('delete', help="Delete bmc dump files")
1389 parserdumpdelete.add_argument("-n", "--dumpNum", nargs='*', type=int, help="The Dump entry to delete")
1390# parserdumpdelete.add_argument('deleteAll', choices=['all'], help="Delete all entries")
1391 parserdumpdelete.set_defaults(func=bmcDumpDelete)
1392
1393 bmcDumpDelsub = parserdumpdelete.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help")
1394 deleteAllDumps = bmcDumpDelsub.add_parser('all', help='Delete all bmc dump files')
1395 deleteAllDumps.set_defaults(func=bmcDumpDeleteAll)
1396
1397 parser_dumpretrieve = bmcDump_sub.add_parser('retrieve', help='Retrieve a dump file')
1398 parser_dumpretrieve.add_argument("dumpNum", type=int, help="The Dump entry to delete")
1399 parser_dumpretrieve.add_argument("-s", "--dumpSaveLoc", help="The location to save the bmc dump file")
1400 parser_dumpretrieve.set_defaults(func=bmcDumpRetrieve)
1401
1402
1403 #esel command ###notused###
1404# esel_parser = subparsers.add_parser("esel", help ="Work with an ESEL entry")
1405# esel_subparser = esel_parser.add_subparsers(title='subcommands', description='valid SEL actions', help = 'valid SEL actions')
1406# esel_get = esel_subparser.add_parser("get", help="Gets the details of an ESEL entry")
1407# esel_get.add_argument('selNum', type=int, help="the number of the SEL entry to get")
1408# esel_get.set_defaults(func=getESEL)
1409
1410
1411 parser_bmc = subparsers.add_parser('bmc', help="Work with the bmc")
1412 bmc_sub = parser_bmc.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help")
1413 parser_BMCReset = bmc_sub.add_parser('reset', help='Reset the bmc' )
1414 parser_BMCReset.add_argument('type', choices=['warm','cold'], help="Warm: Reboot the BMC, Cold: CLEAR config and reboot bmc")
1415# parser_BMCReset.add_argument('cold', action='store_true', help="Reboot the BMC and CLEAR the configuration")
1416 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.")
1417 parser_bmc.set_defaults(func=bmc)
1418# parser_BMCReset.set_defaults(func=bmcReset)
1419
1420 #add alias to the bmc command
1421 parser_mc = subparsers.add_parser('mc', help="Work with the management controller")
1422 mc_sub = parser_mc.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help")
1423 parser_MCReset = mc_sub.add_parser('reset', help='Reset the bmc' )
1424 parser_MCReset.add_argument('type', choices=['warm','cold'], help="Reboot the BMC")
1425 #parser_MCReset.add_argument('cold', action='store_true', help="Reboot the BMC and CLEAR the configuration")
1426 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.")
1427 parser_mc.set_defaults(func=bmc)
1428 #parser_MCReset.set_defaults(func=bmcReset)
1429 return parser
1430
1431"""
1432 main function for running the command line utility as a sub application
1433
1434"""
1435def main(argv=None):
1436
1437
1438 parser = createCommandParser()
1439
1440 #host power on/off
1441 #reboot bmc
1442 #host current state
1443 #chassis power
1444 #error collection - nonipmi
1445 #clear logs
1446 #bmc state
1447
1448 args = parser.parse_args(argv)
1449 if (args.askpw):
1450 pw = getpass.getpass()
1451 elif(args.PW is not None):
1452 pw = args.PW
1453 else:
1454 print("You must specify a password")
1455 sys.exit()
1456
1457 totTimeStart = int(round(time.time()*1000))
1458
1459 if(sys.version_info < (3,0)):
1460 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
1461 if sys.version_info >= (3,0):
1462 requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
1463 if (hasattr(args, 'fileloc') and args.fileloc is not None):
1464 mysess = None
1465 else:
1466 logintimeStart = int(round(time.time()*1000))
1467 mysess = login(args.host, args.user, pw, args.json)
1468 logintimeStop = int(round(time.time()*1000))
1469
1470 commandTimeStart = int(round(time.time()*1000))
1471 output = args.func(args.host, args, mysess)
1472 commandTimeStop = int(round(time.time()*1000))
1473 print(output)
1474 if (mysess is not None):
1475 logout(args.host, args.user, pw, mysess, args.json)
1476 if(args.procTime):
1477 print("Total time: " + str(int(round(time.time()*1000))- totTimeStart))
1478 print("loginTime: " + str(logintimeStop - logintimeStart))
1479 print("command Time: " + str(commandTimeStop - commandTimeStart))
1480
1481"""
1482 main function when called from the command line
1483
1484"""
1485if __name__ == '__main__':
1486 import sys
1487
1488 isTTY = sys.stdout.isatty()
1489 assert sys.version_info >= (2,7)
1490 main()