blob: 9af4a7f18d8d84aefeb749db81cc076aa67e2072 [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 Thalera6b5df72018-07-16 11:10:07 -050031import re
Justin Thalerf9aee3e2017-12-05 12:11:09 -060032
Justin Thalerf9aee3e2017-12-05 12:11:09 -060033def hilight(textToColor, color, bold):
Justin Thalere412dc22018-01-12 16:28:24 -060034 """
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 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -060042 if(sys.platform.__contains__("win")):
43 if(color == "red"):
44 os.system('color 04')
45 elif(color == "green"):
46 os.system('color 02')
47 else:
48 os.system('color') #reset to default
49 return textToColor
50 else:
51 attr = []
52 if(color == "red"):
53 attr.append('31')
54 elif(color == "green"):
55 attr.append('32')
56 else:
57 attr.append('0')
58 if bold:
59 attr.append('1')
60 else:
61 attr.append('0')
62 return '\x1b[%sm%s\x1b[0m' % (';'.join(attr),textToColor)
63
Justin Thalere412dc22018-01-12 16:28:24 -060064
Justin Thalerf9aee3e2017-12-05 12:11:09 -060065def connectionErrHandler(jsonFormat, errorStr, err):
Justin Thalere412dc22018-01-12 16:28:24 -060066 """
67 Error handler various connection errors to bmcs
68
69 @param jsonFormat: boolean, used to output in json format with an error code.
70 @param errorStr: string, used to color the text red or green
71 @param err: string, the text from the exception
72 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -060073 if errorStr == "Timeout":
74 if not jsonFormat:
75 return("FQPSPIN0000M: Connection timed out. Ensure you have network connectivity to the bmc")
76 else:
Justin Thaler115bca72018-05-25 19:29:08 -050077 conerror = {}
78 conerror['CommonEventID'] = 'FQPSPIN0000M'
79 conerror['sensor']="N/A"
80 conerror['state']="N/A"
81 conerror['additionalDetails'] = "N/A"
82 conerror['Message']="Connection timed out. Ensure you have network connectivity to the BMC"
83 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."
84 conerror['Serviceable']="Yes"
85 conerror['CallHomeCandidate']= "No"
86 conerror['Severity'] = "Critical"
87 conerror['EventType'] = "Communication Failure/Timeout"
88 conerror['VMMigrationFlag'] = "Yes"
89 conerror["AffectedSubsystem"] = "Interconnect (Networking)"
90 conerror["timestamp"] = str(int(time.time()))
91 conerror["UserAction"] = "Verify network connectivity between the two systems and the bmc is functional."
92 eventdict = {}
93 eventdict['event0'] = conerror
94 eventdict['numAlerts'] = '1'
95
96 errorMessageStr = errorMessageStr = json.dumps(eventdict, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False)
Justin Thalerf9aee3e2017-12-05 12:11:09 -060097 return(errorMessageStr)
98 elif errorStr == "ConnectionError":
99 if not jsonFormat:
100 return("FQPSPIN0001M: " + str(err))
101 else:
Justin Thaler115bca72018-05-25 19:29:08 -0500102 conerror = {}
103 conerror['CommonEventID'] = 'FQPSPIN0001M'
104 conerror['sensor']="N/A"
105 conerror['state']="N/A"
106 conerror['additionalDetails'] = str(err)
107 conerror['Message']="Connection Error. View additional details for more information"
108 conerror['LengthyDescription'] = "A connection error to the specified BMC occurred and additional details are provided. Review these details to resolve the issue."
109 conerror['Serviceable']="Yes"
110 conerror['CallHomeCandidate']= "No"
111 conerror['Severity'] = "Critical"
112 conerror['EventType'] = "Communication Failure/Timeout"
113 conerror['VMMigrationFlag'] = "Yes"
114 conerror["AffectedSubsystem"] = "Interconnect (Networking)"
115 conerror["timestamp"] = str(int(time.time()))
116 conerror["UserAction"] = "Correct the issue highlighted in additional details and try again"
117 eventdict = {}
118 eventdict['event0'] = conerror
119 eventdict['numAlerts'] = '1'
120
121 errorMessageStr = json.dumps(eventdict, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False)
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600122 return(errorMessageStr)
Justin Thaler115bca72018-05-25 19:29:08 -0500123
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600124 else:
125 return("Unknown Error: "+ str(err))
126
Justin Thalere412dc22018-01-12 16:28:24 -0600127
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600128def setColWidth(keylist, numCols, dictForOutput, colNames):
Justin Thalere412dc22018-01-12 16:28:24 -0600129 """
130 Sets the output width of the columns to display
131
132 @param keylist: list, list of strings representing the keys for the dictForOutput
133 @param numcols: the total number of columns in the final output
134 @param dictForOutput: dictionary, contains the information to print to the screen
135 @param colNames: list, The strings to use for the column headings, in order of the keylist
136 @return: A list of the column widths for each respective column.
137 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600138 colWidths = []
139 for x in range(0, numCols):
140 colWidths.append(0)
141 for key in dictForOutput:
142 for x in range(0, numCols):
143 colWidths[x] = max(colWidths[x], len(str(dictForOutput[key][keylist[x]])))
144
145 for x in range(0, numCols):
146 colWidths[x] = max(colWidths[x], len(colNames[x])) +2
147
148 return colWidths
149
150def loadPolicyTable(pathToPolicyTable):
Justin Thalere412dc22018-01-12 16:28:24 -0600151 """
152 loads a json based policy table into a dictionary
153
154 @param value: boolean, the value to convert
155 @return: A string of "Yes" or "No"
156 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600157 policyTable = {}
158 if(os.path.exists(pathToPolicyTable)):
159 with open(pathToPolicyTable, 'r') as stream:
160 try:
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600161 contents =json.load(stream)
162 policyTable = contents['events']
Justin Thalere412dc22018-01-12 16:28:24 -0600163 except Exception as err:
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600164 print(err)
165 return policyTable
166
Justin Thalere412dc22018-01-12 16:28:24 -0600167
168def boolToString(value):
169 """
170 converts a boolean value to a human readable string value
171
172 @param value: boolean, the value to convert
173 @return: A string of "Yes" or "No"
174 """
175 if(value):
176 return "Yes"
177 else:
178 return "No"
179
Justin Thalera6b5df72018-07-16 11:10:07 -0500180def stringToInt(text):
181 """
182 returns an integer if the string can be converted, otherwise returns the string
183
184 @param text: the string to try to convert to an integer
185 """
186 if text.isdigit():
187 return int(text)
188 else:
189 return text
Justin Thalere412dc22018-01-12 16:28:24 -0600190
Justin Thalera6b5df72018-07-16 11:10:07 -0500191def naturalSort(text):
192 """
193 provides a way to naturally sort a list
194
195 @param text: the key to convert for sorting
196 @return list containing the broken up string parts by integers and strings
197 """
198 stringPartList = []
199 for c in re.split('(\d+)', text):
200 stringPartList.append(stringToInt(c))
201 return stringPartList
202
Justin Thalere412dc22018-01-12 16:28:24 -0600203def tableDisplay(keylist, colNames, output):
204 """
205 Logs into the BMC and creates a session
206
207 @param keylist: list, keys for the output dictionary, ordered by colNames
208 @param colNames: Names for the Table of the columns
209 @param output: The dictionary of data to display
210 @return: Session object
211 """
212 colWidth = setColWidth(keylist, len(colNames), output, colNames)
213 row = ""
214 outputText = ""
215 for i in range(len(colNames)):
216 if (i != 0): row = row + "| "
217 row = row + colNames[i].ljust(colWidth[i])
218 outputText += row + "\n"
Justin Thalera6b5df72018-07-16 11:10:07 -0500219
220 output_keys = list(output.keys())
221 output_keys.sort(key=naturalSort)
222 for key in output_keys:
Justin Thalere412dc22018-01-12 16:28:24 -0600223 row = ""
224 for i in range(len(output[key])):
225 if (i != 0): row = row + "| "
226 row = row + output[key][keylist[i]].ljust(colWidth[i])
227 outputText += row + "\n"
228
229 return outputText
230
Justin Thaler22b1bb52018-03-15 13:31:32 -0500231def checkFWactivation(host, args, session):
232 """
233 Checks the software inventory for an image that is being activated.
234
235 @return: True if an image is being activated, false is no activations are happening
236 """
237 url="https://"+host+"/xyz/openbmc_project/software/enumerate"
238 httpHeader = {'Content-Type':'application/json'}
239 try:
240 resp = session.get(url, headers=httpHeader, verify=False, timeout=30)
241 except(requests.exceptions.Timeout):
242 print(connectionErrHandler(args.json, "Timeout", None))
243 return(True)
244 except(requests.exceptions.ConnectionError) as err:
245 print( connectionErrHandler(args.json, "ConnectionError", err))
246 return True
247 fwInfo = json.loads(resp.text)['data']
248 for key in fwInfo:
249 if 'Activation' in fwInfo[key]:
250 if 'Activating' in fwInfo[key]['Activation'] or 'Activating' in fwInfo[key]['RequestedActivation']:
251 return True
252 return False
253
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600254def login(host, username, pw,jsonFormat):
Justin Thalere412dc22018-01-12 16:28:24 -0600255 """
256 Logs into the BMC and creates a session
257
258 @param host: string, the hostname or IP address of the bmc to log into
259 @param username: The user name for the bmc to log into
260 @param pw: The password for the BMC to log into
261 @param jsonFormat: boolean, flag that will only allow relevant data from user command to be display. This function becomes silent when set to true.
262 @return: Session object
263 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600264 if(jsonFormat==False):
265 print("Attempting login...")
266 httpHeader = {'Content-Type':'application/json'}
267 mysess = requests.session()
268 try:
269 r = mysess.post('https://'+host+'/login', headers=httpHeader, json = {"data": [username, pw]}, verify=False, timeout=30)
270 loginMessage = json.loads(r.text)
271 if (loginMessage['status'] != "ok"):
272 print(loginMessage["data"]["description"].encode('utf-8'))
273 sys.exit(1)
274# if(sys.version_info < (3,0)):
275# urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
276# if sys.version_info >= (3,0):
277# requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
278 return mysess
279 except(requests.exceptions.Timeout):
Justin Thaler115bca72018-05-25 19:29:08 -0500280 return (connectionErrHandler(jsonFormat, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600281 except(requests.exceptions.ConnectionError) as err:
Justin Thaler115bca72018-05-25 19:29:08 -0500282 return (connectionErrHandler(jsonFormat, "ConnectionError", err))
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600283
Justin Thalere412dc22018-01-12 16:28:24 -0600284
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600285def logout(host, username, pw, session, jsonFormat):
Justin Thalere412dc22018-01-12 16:28:24 -0600286 """
287 Logs out of the bmc and terminates the session
288
289 @param host: string, the hostname or IP address of the bmc to log out of
290 @param username: The user name for the bmc to log out of
291 @param pw: The password for the BMC to log out of
292 @param session: the active session to use
293 @param jsonFormat: boolean, flag that will only allow relevant data from user command to be display. This function becomes silent when set to true.
294 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600295 httpHeader = {'Content-Type':'application/json'}
Justin Thalere412dc22018-01-12 16:28:24 -0600296 try:
297 r = session.post('https://'+host+'/logout', headers=httpHeader,json = {"data": [username, pw]}, verify=False, timeout=10)
298 except(requests.exceptions.Timeout):
299 print(connectionErrHandler(jsonFormat, "Timeout", None))
300
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600301 if(jsonFormat==False):
302 if('"message": "200 OK"' in r.text):
303 print('User ' +username + ' has been logged out')
304
Justin Thalere412dc22018-01-12 16:28:24 -0600305
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600306def fru(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600307 """
308 prints out the system inventory. deprecated see fruPrint and fruList
309
310 @param host: string, the hostname or IP address of the bmc
311 @param args: contains additional arguments used by the fru sub command
312 @param session: the active session to use
313 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
314 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600315 #url="https://"+host+"/org/openbmc/inventory/system/chassis/enumerate"
316
317 #print(url)
318 #res = session.get(url, headers=httpHeader, verify=False)
319 #print(res.text)
320 #sample = res.text
321
322 #inv_list = json.loads(sample)["data"]
323
324 url="https://"+host+"/xyz/openbmc_project/inventory/enumerate"
325 httpHeader = {'Content-Type':'application/json'}
Justin Thalere412dc22018-01-12 16:28:24 -0600326 try:
327 res = session.get(url, headers=httpHeader, verify=False, timeout=40)
328 except(requests.exceptions.Timeout):
329 return(connectionErrHandler(args.json, "Timeout", None))
330
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600331 sample = res.text
332# inv_list.update(json.loads(sample)["data"])
333#
334# #determine column width's
335# colNames = ["FRU Name", "FRU Type", "Has Fault", "Is FRU", "Present", "Version"]
336# colWidths = setColWidth(["FRU Name", "fru_type", "fault", "is_fru", "present", "version"], 6, inv_list, colNames)
337#
338# print("FRU Name".ljust(colWidths[0])+ "FRU Type".ljust(colWidths[1]) + "Has Fault".ljust(colWidths[2]) + "Is FRU".ljust(colWidths[3])+
339# "Present".ljust(colWidths[4]) + "Version".ljust(colWidths[5]))
340# format the output
341# for key in sorted(inv_list.keys()):
342# keyParts = key.split("/")
343# isFRU = "True" if (inv_list[key]["is_fru"]==1) else "False"
344#
345# fruEntry = (keyParts[len(keyParts) - 1].ljust(colWidths[0]) + inv_list[key]["fru_type"].ljust(colWidths[1])+
346# inv_list[key]["fault"].ljust(colWidths[2])+isFRU.ljust(colWidths[3])+
347# inv_list[key]["present"].ljust(colWidths[4])+ inv_list[key]["version"].ljust(colWidths[5]))
348# if(isTTY):
349# if(inv_list[key]["is_fru"] == 1):
350# color = "green"
351# bold = True
352# else:
353# color='black'
354# bold = False
355# fruEntry = hilight(fruEntry, color, bold)
356# print (fruEntry)
357 return sample
Justin Thalere412dc22018-01-12 16:28:24 -0600358
359def fruPrint(host, args, session):
360 """
361 prints out all inventory
362
363 @param host: string, the hostname or IP address of the bmc
364 @param args: contains additional arguments used by the fru sub command
365 @param session: the active session to use
366 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
367 @return returns the total fru list.
368 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600369 url="https://"+host+"/xyz/openbmc_project/inventory/enumerate"
370 httpHeader = {'Content-Type':'application/json'}
Justin Thalere412dc22018-01-12 16:28:24 -0600371 try:
372 res = session.get(url, headers=httpHeader, verify=False, timeout=40)
373 except(requests.exceptions.Timeout):
374 return(connectionErrHandler(args.json, "Timeout", None))
375
376
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600377# print(res.text)
378 frulist = res.text
379 url="https://"+host+"/xyz/openbmc_project/software/enumerate"
Justin Thalere412dc22018-01-12 16:28:24 -0600380 try:
381 res = session.get(url, headers=httpHeader, verify=False, timeout=40)
382 except(requests.exceptions.Timeout):
383 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600384# print(res.text)
385 frulist = frulist +"\n" + res.text
386
387 return frulist
388
Justin Thalere412dc22018-01-12 16:28:24 -0600389
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600390def fruList(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600391 """
392 prints out all inventory or only a specific specified item
393
394 @param host: string, the hostname or IP address of the bmc
395 @param args: contains additional arguments used by the fru sub command
396 @param session: the active session to use
397 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
398 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600399 if(args.items==True):
400 return fruPrint(host, args, session)
401 else:
Justin Thalere412dc22018-01-12 16:28:24 -0600402 return fruPrint(host, args, session)
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600403
404
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600405
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600406def fruStatus(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600407 """
408 prints out the status of all FRUs
409
410 @param host: string, the hostname or IP address of the bmc
411 @param args: contains additional arguments used by the fru sub command
412 @param session: the active session to use
413 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
414 """
415 url="https://"+host+"/xyz/openbmc_project/inventory/enumerate"
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600416 httpHeader = {'Content-Type':'application/json'}
Justin Thalere412dc22018-01-12 16:28:24 -0600417 try:
418 res = session.get(url, headers=httpHeader, verify=False)
419 except(requests.exceptions.Timeout):
420 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600421# print(res.text)
Justin Thalere412dc22018-01-12 16:28:24 -0600422 frulist = json.loads(res.text)['data']
423 frus = {}
424 for key in frulist:
425 component = frulist[key]
426 isFru = False
427 present = False
428 func = False
429 hasSels = False
430 keyPieces = key.split('/')
431 fruName = keyPieces[-1]
432 if 'core' in fruName: #associate cores to cpus
433 fruName = keyPieces[-2] + '-' + keyPieces[-1]
434 if 'Functional' in component:
435 if('Present' in component):
436
437 if 'FieldReplaceable' in component:
438 if component['FieldReplaceable'] == 1:
439 isFru = True
440 if "fan" in fruName:
441 isFru = True;
442 if component['Present'] == 1:
443 present = True
444 if component['Functional'] == 1:
445 func = True
446 if ((key + "/fault") in frulist):
447 hasSels = True;
448 if args.verbose:
449 if hasSels:
450 loglist = []
451 faults = frulist[key+"/fault"]['endpoints']
452 for item in faults:
453 loglist.append(item.split('/')[-1])
454 frus[fruName] = {"compName": fruName, "Functional": boolToString(func), "Present":boolToString(present), "IsFru": boolToString(isFru), "selList": ', '.join(loglist).strip() }
455 else:
456 frus[fruName] = {"compName": fruName, "Functional": boolToString(func), "Present":boolToString(present), "IsFru": boolToString(isFru), "selList": "None" }
457 else:
458 frus[fruName] = {"compName": fruName, "Functional": boolToString(func), "Present":boolToString(present), "IsFru": boolToString(isFru), "hasSEL": boolToString(hasSels) }
459 elif "power_supply" in fruName:
460 if component['Present'] ==1:
461 present = True
462 isFru = True
463 if ((key + "/fault") in frulist):
464 hasSels = True;
465 if args.verbose:
466 if hasSels:
467 loglist = []
468 faults = frulist[key+"/fault"]['endpoints']
469 for key in faults:
470 loglist.append(faults[key].split('/')[-1])
471 frus[fruName] = {"compName": fruName, "Functional": "No", "Present":boolToString(present), "IsFru": boolToString(isFru), "selList": ', '.join(loglist).strip() }
472 else:
473 frus[fruName] = {"compName": fruName, "Functional": "Yes", "Present":boolToString(present), "IsFru": boolToString(isFru), "selList": "None" }
474 else:
475 frus[fruName] = {"compName": fruName, "Functional": boolToString(not hasSels), "Present":boolToString(present), "IsFru": boolToString(isFru), "hasSEL": boolToString(hasSels) }
476 if not args.json:
477 if not args.verbose:
478 colNames = ["Component", "Is a FRU", "Present", "Functional", "Has Logs"]
479 keylist = ["compName", "IsFru", "Present", "Functional", "hasSEL"]
480 else:
481 colNames = ["Component", "Is a FRU", "Present", "Functional", "Assoc. Log Number(s)"]
482 keylist = ["compName", "IsFru", "Present", "Functional", "selList"]
483 return tableDisplay(keylist, colNames, frus)
484 else:
485 return str(json.dumps(frus, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False))
486
487def sensor(host, args, session):
488 """
489 prints out all sensors
490
491 @param host: string, the hostname or IP address of the bmc
492 @param args: contains additional arguments used by the sensor sub command
493 @param session: the active session to use
494 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
495 """
496 httpHeader = {'Content-Type':'application/json'}
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600497 url="https://"+host+"/xyz/openbmc_project/sensors/enumerate"
Justin Thalere412dc22018-01-12 16:28:24 -0600498 try:
499 res = session.get(url, headers=httpHeader, verify=False, timeout=30)
500 except(requests.exceptions.Timeout):
501 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600502
503 #Get OCC status
504 url="https://"+host+"/org/open_power/control/enumerate"
Justin Thalere412dc22018-01-12 16:28:24 -0600505 try:
506 occres = session.get(url, headers=httpHeader, verify=False, timeout=30)
507 except(requests.exceptions.Timeout):
508 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600509 if not args.json:
510 colNames = ['sensor', 'type', 'units', 'value', 'target']
511 sensors = json.loads(res.text)["data"]
512 output = {}
513 for key in sensors:
514 senDict = {}
515 keyparts = key.split("/")
516 senDict['sensorName'] = keyparts[-1]
517 senDict['type'] = keyparts[-2]
Justin Thalere412dc22018-01-12 16:28:24 -0600518 try:
519 senDict['units'] = sensors[key]['Unit'].split('.')[-1]
520 except KeyError:
Justin Thaler22b1bb52018-03-15 13:31:32 -0500521 senDict['units'] = "N/A"
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600522 if('Scale' in sensors[key]):
523 scale = 10 ** sensors[key]['Scale']
524 else:
525 scale = 1
Justin Thaler22b1bb52018-03-15 13:31:32 -0500526 try:
527 senDict['value'] = str(sensors[key]['Value'] * scale)
528 except KeyError:
529 if 'value' in sensors[key]:
530 senDict['value'] = sensors[key]['value']
531 else:
532 senDict['value'] = "N/A"
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600533 if 'Target' in sensors[key]:
534 senDict['target'] = str(sensors[key]['Target'])
535 else:
536 senDict['target'] = 'N/A'
537 output[senDict['sensorName']] = senDict
538
539 occstatus = json.loads(occres.text)["data"]
540 if '/org/open_power/control/occ0' in occstatus:
541 occ0 = occstatus["/org/open_power/control/occ0"]['OccActive']
542 if occ0 == 1:
543 occ0 = 'Active'
544 else:
545 occ0 = 'Inactive'
546 output['OCC0'] = {'sensorName':'OCC0', 'type': 'Discrete', 'units': 'N/A', 'value': occ0, 'target': 'Active'}
547 occ1 = occstatus["/org/open_power/control/occ1"]['OccActive']
548 if occ1 == 1:
549 occ1 = 'Active'
550 else:
551 occ1 = 'Inactive'
552 output['OCC1'] = {'sensorName':'OCC1', 'type': 'Discrete', 'units': 'N/A', 'value': occ0, 'target': 'Active'}
553 else:
554 output['OCC0'] = {'sensorName':'OCC0', 'type': 'Discrete', 'units': 'N/A', 'value': 'Inactive', 'target': 'Inactive'}
555 output['OCC1'] = {'sensorName':'OCC1', 'type': 'Discrete', 'units': 'N/A', 'value': 'Inactive', 'target': 'Inactive'}
556 keylist = ['sensorName', 'type', 'units', 'value', 'target']
Justin Thalere412dc22018-01-12 16:28:24 -0600557
558 return tableDisplay(keylist, colNames, output)
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600559 else:
560 return res.text + occres.text
Justin Thalere412dc22018-01-12 16:28:24 -0600561
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600562def sel(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600563 """
564 prints out the bmc alerts
565
566 @param host: string, the hostname or IP address of the bmc
567 @param args: contains additional arguments used by the sel sub command
568 @param session: the active session to use
569 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
570 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600571
572 url="https://"+host+"/xyz/openbmc_project/logging/entry/enumerate"
573 httpHeader = {'Content-Type':'application/json'}
Justin Thalere412dc22018-01-12 16:28:24 -0600574 try:
575 res = session.get(url, headers=httpHeader, verify=False, timeout=60)
576 except(requests.exceptions.Timeout):
577 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600578 return res.text
Justin Thalere412dc22018-01-12 16:28:24 -0600579
580
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600581def parseESEL(args, eselRAW):
Justin Thalere412dc22018-01-12 16:28:24 -0600582 """
583 parses the esel data and gets predetermined search terms
584
585 @param eselRAW: string, the raw esel string from the bmc
586 @return: A dictionary containing the quick snapshot data unless args.fullEsel is listed then a full PEL log is returned
587 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600588 eselParts = {}
589 esel_bin = binascii.unhexlify(''.join(eselRAW.split()[16:]))
590 #search terms contains the search term as the key and the return dictionary key as it's value
591 searchTerms = { 'Signature Description':'signatureDescription', 'devdesc':'devdesc',
Justin Thaler22b1bb52018-03-15 13:31:32 -0500592 'Callout type': 'calloutType', 'Procedure':'procedure', 'Sensor Type': 'sensorType'}
Justin Thalercf1deae2018-05-25 19:35:21 -0500593 eselBinPath = tempfile.gettempdir() + os.sep + 'esel.bin'
594 with open(eselBinPath, 'wb') as f:
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600595 f.write(esel_bin)
596 errlPath = ""
597 #use the right errl file for the machine architecture
598 arch = platform.machine()
599 if(arch =='x86_64' or arch =='AMD64'):
600 if os.path.exists('/opt/ibm/ras/bin/x86_64/errl'):
601 errlPath = '/opt/ibm/ras/bin/x86_64/errl'
602 elif os.path.exists('errl/x86_64/errl'):
603 errlPath = 'errl/x86_64/errl'
604 else:
605 errlPath = 'x86_64/errl'
606 elif (platform.machine()=='ppc64le'):
607 if os.path.exists('/opt/ibm/ras/bin/ppc64le/errl'):
608 errlPath = '/opt/ibm/ras/bin/ppc64le/errl'
609 elif os.path.exists('errl/ppc64le/errl'):
610 errlPath = 'errl/ppc64le/errl'
611 else:
612 errlPath = 'ppc64le/errl'
613 else:
614 print("machine architecture not supported for parsing eSELs")
615 return eselParts
616
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600617 if(os.path.exists(errlPath)):
Justin Thalercf1deae2018-05-25 19:35:21 -0500618 output= subprocess.check_output([errlPath, '-d', '--file='+eselBinPath]).decode('utf-8')
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600619# output = proc.communicate()[0]
620 lines = output.split('\n')
621
622 if(hasattr(args, 'fullEsel')):
623 return output
624
625 for i in range(0, len(lines)):
626 lineParts = lines[i].split(':')
627 if(len(lineParts)>1): #ignore multi lines, output formatting lines, and other information
628 for term in searchTerms:
629 if(term in lineParts[0]):
630 temp = lines[i][lines[i].find(':')+1:].strip()[:-1].strip()
631 if lines[i+1].find(':') != -1:
632 if (len(lines[i+1].split(':')[0][1:].strip())==0):
633 while(len(lines[i][:lines[i].find(':')].strip())>2):
634 if((i+1) <= len(lines)):
635 i+=1
636 else:
637 i=i-1
638 break
639 temp = temp + lines[i][lines[i].find(':'):].strip()[:-1].strip()[:-1].strip()
Justin Thaler22b1bb52018-03-15 13:31:32 -0500640 if(searchTerms[term] in eselParts):
641 eselParts[searchTerms[term]] = eselParts[searchTerms[term]] + ", " + temp
642 else:
643 eselParts[searchTerms[term]] = temp
Justin Thalercf1deae2018-05-25 19:35:21 -0500644 os.remove(eselBinPath)
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600645 else:
646 print("errl file cannot be found")
647
648 return eselParts
649
Justin Thalere412dc22018-01-12 16:28:24 -0600650
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600651def sortSELs(events):
Justin Thalere412dc22018-01-12 16:28:24 -0600652 """
653 sorts the sels by timestamp, then log entry number
654
655 @param events: Dictionary containing events
656 @return: list containing a list of the ordered log entries, and dictionary of keys
657 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600658 logNumList = []
659 timestampList = []
660 eventKeyDict = {}
661 eventsWithTimestamp = {}
662 logNum2events = {}
663 for key in events:
664 if key == 'numAlerts': continue
665 if 'callout' in key: continue
666 timestamp = (events[key]['timestamp'])
667 if timestamp not in timestampList:
668 eventsWithTimestamp[timestamp] = [events[key]['logNum']]
669 else:
670 eventsWithTimestamp[timestamp].append(events[key]['logNum'])
671 #map logNumbers to the event dictionary keys
672 eventKeyDict[str(events[key]['logNum'])] = key
673
674 timestampList = list(eventsWithTimestamp.keys())
675 timestampList.sort()
676 for ts in timestampList:
677 if len(eventsWithTimestamp[ts]) > 1:
678 tmplist = eventsWithTimestamp[ts]
679 tmplist.sort()
680 logNumList = logNumList + tmplist
681 else:
682 logNumList = logNumList + eventsWithTimestamp[ts]
683
684 return [logNumList, eventKeyDict]
685
Justin Thalere412dc22018-01-12 16:28:24 -0600686
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600687def parseAlerts(policyTable, selEntries, args):
Justin Thalere412dc22018-01-12 16:28:24 -0600688 """
689 parses alerts in the IBM CER format, using an IBM policy Table
690
691 @param policyTable: dictionary, the policy table entries
692 @param selEntries: dictionary, the alerts retrieved from the bmc
693 @return: A dictionary of the parsed entries, in chronological order
694 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600695 eventDict = {}
696 eventNum =""
697 count = 0
698 esel = ""
699 eselParts = {}
700 i2cdevice= ""
701
702 'prepare and sort the event entries'
703 for key in selEntries:
704 if 'callout' not in key:
705 selEntries[key]['logNum'] = key.split('/')[-1]
706 selEntries[key]['timestamp'] = selEntries[key]['Timestamp']
707 sortedEntries = sortSELs(selEntries)
708 logNumList = sortedEntries[0]
709 eventKeyDict = sortedEntries[1]
710
711 for logNum in logNumList:
712 key = eventKeyDict[logNum]
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600713 hasEsel=False
714 i2creadFail = False
715 if 'callout' in key:
716 continue
717 else:
718 messageID = str(selEntries[key]['Message'])
719 addDataPiece = selEntries[key]['AdditionalData']
720 calloutIndex = 0
721 calloutFound = False
722 for i in range(len(addDataPiece)):
723 if("CALLOUT_INVENTORY_PATH" in addDataPiece[i]):
724 calloutIndex = i
725 calloutFound = True
726 fruCallout = str(addDataPiece[calloutIndex]).split('=')[1]
727 if("CALLOUT_DEVICE_PATH" in addDataPiece[i]):
728 i2creadFail = True
729 i2cdevice = str(addDataPiece[i]).strip().split('=')[1]
730 i2cdevice = '/'.join(i2cdevice.split('/')[-4:])
Justin Thalere34c43a2018-05-25 19:37:55 -0500731 if 'fsi' in str(addDataPiece[calloutIndex]).split('=')[1]:
732 fruCallout = 'FSI'
733 else:
734 fruCallout = 'I2C'
735 calloutFound = True
736 if("CALLOUT_GPIO_NUM" in addDataPiece[i]):
737 if not calloutFound:
738 fruCallout = 'GPIO'
739 calloutFound = True
740 if("CALLOUT_IIC_BUS" in addDataPiece[i]):
741 if not calloutFound:
742 fruCallout = "I2C"
743 calloutFound = True
744 if("CALLOUT_IPMI_SENSOR_NUM" in addDataPiece[i]):
745 if not calloutFound:
746 fruCallout = "IPMI"
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600747 calloutFound = True
748 if("ESEL" in addDataPiece[i]):
749 esel = str(addDataPiece[i]).strip().split('=')[1]
750 if args.devdebug:
751 eselParts = parseESEL(args, esel)
752 hasEsel=True
753 if("GPU" in addDataPiece[i]):
754 fruCallout = '/xyz/openbmc_project/inventory/system/chassis/motherboard/gpu' + str(addDataPiece[i]).strip()[-1]
755 calloutFound = True
756 if("PROCEDURE" in addDataPiece[i]):
757 fruCallout = str(hex(int(str(addDataPiece[i]).split('=')[1])))[2:]
758 calloutFound = True
Justin Thalere412dc22018-01-12 16:28:24 -0600759 if("RAIL_NAME" in addDataPiece[i]):
760 calloutFound=True
761 fruCallout = str(addDataPiece[i]).split('=')[1].strip()
762 if("INPUT_NAME" in addDataPiece[i]):
763 calloutFound=True
764 fruCallout = str(addDataPiece[i]).split('=')[1].strip()
765 if("SENSOR_TYPE" in addDataPiece[i]):
766 calloutFound=True
767 fruCallout = str(addDataPiece[i]).split('=')[1].strip()
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600768
769 if(calloutFound):
Justin Thaler22b1bb52018-03-15 13:31:32 -0500770 if fruCallout != "":
771 policyKey = messageID +"||" + fruCallout
Justin Thalere34c43a2018-05-25 19:37:55 -0500772 if policyKey not in policyTable:
773 policyKey = messageID
Justin Thaler22b1bb52018-03-15 13:31:32 -0500774 else:
775 policyKey = messageID
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600776 else:
777 policyKey = messageID
778 event = {}
779 eventNum = str(count)
780 if policyKey in policyTable:
781 for pkey in policyTable[policyKey]:
782 if(type(policyTable[policyKey][pkey])== bool):
783 event[pkey] = boolToString(policyTable[policyKey][pkey])
784 else:
785 if (i2creadFail and pkey == 'Message'):
786 event[pkey] = policyTable[policyKey][pkey] + ' ' +i2cdevice
787 else:
788 event[pkey] = policyTable[policyKey][pkey]
789 event['timestamp'] = selEntries[key]['Timestamp']
790 event['resolved'] = bool(selEntries[key]['Resolved'])
791 if(hasEsel):
792 if args.devdebug:
793 event['eselParts'] = eselParts
794 event['raweSEL'] = esel
795 event['logNum'] = key.split('/')[-1]
796 eventDict['event' + eventNum] = event
797
798 else:
799 severity = str(selEntries[key]['Severity']).split('.')[-1]
800 if severity == 'Error':
801 severity = 'Critical'
802 eventDict['event'+eventNum] = {}
803 eventDict['event' + eventNum]['error'] = "error: Not found in policy table: " + policyKey
804 eventDict['event' + eventNum]['timestamp'] = selEntries[key]['Timestamp']
805 eventDict['event' + eventNum]['Severity'] = severity
806 if(hasEsel):
807 if args.devdebug:
808 eventDict['event' +eventNum]['eselParts'] = eselParts
809 eventDict['event' +eventNum]['raweSEL'] = esel
810 eventDict['event' +eventNum]['logNum'] = key.split('/')[-1]
811 eventDict['event' +eventNum]['resolved'] = bool(selEntries[key]['Resolved'])
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600812 count += 1
813 return eventDict
814
815
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600816def selDisplay(events, args):
Justin Thalere412dc22018-01-12 16:28:24 -0600817 """
818 displays alerts in human readable format
819
820 @param events: Dictionary containing events
821 @return:
822 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600823 activeAlerts = []
824 historyAlerts = []
825 sortedEntries = sortSELs(events)
826 logNumList = sortedEntries[0]
827 eventKeyDict = sortedEntries[1]
828 keylist = ['Entry', 'ID', 'Timestamp', 'Serviceable', 'Severity','Message']
829 if(args.devdebug):
830 colNames = ['Entry', 'ID', 'Timestamp', 'Serviceable', 'Severity','Message', 'eSEL contents']
831 keylist.append('eSEL')
832 else:
833 colNames = ['Entry', 'ID', 'Timestamp', 'Serviceable', 'Severity', 'Message']
834 for log in logNumList:
835 selDict = {}
836 alert = events[eventKeyDict[str(log)]]
837 if('error' in alert):
838 selDict['Entry'] = alert['logNum']
839 selDict['ID'] = 'Unknown'
840 selDict['Timestamp'] = datetime.datetime.fromtimestamp(int(alert['timestamp']/1000)).strftime("%Y-%m-%d %H:%M:%S")
841 msg = alert['error']
842 polMsg = msg.split("policy table:")[0]
843 msg = msg.split("policy table:")[1]
844 msgPieces = msg.split("||")
845 err = msgPieces[0]
846 if(err.find("org.open_power.")!=-1):
847 err = err.split("org.open_power.")[1]
848 elif(err.find("xyz.openbmc_project.")!=-1):
849 err = err.split("xyz.openbmc_project.")[1]
850 else:
851 err = msgPieces[0]
852 callout = ""
853 if len(msgPieces) >1:
854 callout = msgPieces[1]
855 if(callout.find("/org/open_power/")!=-1):
856 callout = callout.split("/org/open_power/")[1]
857 elif(callout.find("/xyz/openbmc_project/")!=-1):
858 callout = callout.split("/xyz/openbmc_project/")[1]
859 else:
860 callout = msgPieces[1]
861 selDict['Message'] = polMsg +"policy table: "+ err + "||" + callout
862 selDict['Serviceable'] = 'Unknown'
863 selDict['Severity'] = alert['Severity']
864 else:
865 selDict['Entry'] = alert['logNum']
866 selDict['ID'] = alert['CommonEventID']
867 selDict['Timestamp'] = datetime.datetime.fromtimestamp(int(alert['timestamp']/1000)).strftime("%Y-%m-%d %H:%M:%S")
868 selDict['Message'] = alert['Message']
869 selDict['Serviceable'] = alert['Serviceable']
870 selDict['Severity'] = alert['Severity']
871
872
873 eselOrder = ['refCode','signatureDescription', 'eselType', 'devdesc', 'calloutType', 'procedure']
874 if ('eselParts' in alert and args.devdebug):
875 eselOutput = ""
876 for item in eselOrder:
877 if item in alert['eselParts']:
878 eselOutput = eselOutput + item + ": " + alert['eselParts'][item] + " | "
879 selDict['eSEL'] = eselOutput
880 else:
881 if args.devdebug:
882 selDict['eSEL'] = "None"
883
884 if not alert['resolved']:
885 activeAlerts.append(selDict)
886 else:
887 historyAlerts.append(selDict)
888 mergedOutput = activeAlerts + historyAlerts
889 colWidth = setColWidth(keylist, len(colNames), dict(enumerate(mergedOutput)), colNames)
890
891 output = ""
892 if(len(activeAlerts)>0):
893 row = ""
894 output +="----Active Alerts----\n"
895 for i in range(0, len(colNames)):
896 if i!=0: row =row + "| "
897 row = row + colNames[i].ljust(colWidth[i])
898 output += row + "\n"
899
900 for i in range(0,len(activeAlerts)):
901 row = ""
902 for j in range(len(activeAlerts[i])):
903 if (j != 0): row = row + "| "
904 row = row + activeAlerts[i][keylist[j]].ljust(colWidth[j])
905 output += row + "\n"
906
907 if(len(historyAlerts)>0):
908 row = ""
909 output+= "----Historical Alerts----\n"
910 for i in range(len(colNames)):
911 if i!=0: row =row + "| "
912 row = row + colNames[i].ljust(colWidth[i])
913 output += row + "\n"
914
915 for i in range(0, len(historyAlerts)):
916 row = ""
917 for j in range(len(historyAlerts[i])):
918 if (j != 0): row = row + "| "
919 row = row + historyAlerts[i][keylist[j]].ljust(colWidth[j])
920 output += row + "\n"
921# print(events[eventKeyDict[str(log)]])
922 return output
923
Justin Thalere412dc22018-01-12 16:28:24 -0600924
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600925def selPrint(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600926 """
927 prints out all bmc alerts
928
929 @param host: string, the hostname or IP address of the bmc
930 @param args: contains additional arguments used by the fru sub command
931 @param session: the active session to use
932 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
933 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600934 if(args.policyTableLoc is None):
935 if os.path.exists('policyTable.json'):
936 ptableLoc = "policyTable.json"
937 elif os.path.exists('/opt/ibm/ras/lib/policyTable.json'):
938 ptableLoc = '/opt/ibm/ras/lib/policyTable.json'
939 else:
940 ptableLoc = 'lib/policyTable.json'
941 else:
942 ptableLoc = args.policyTableLoc
943 policyTable = loadPolicyTable(ptableLoc)
944 rawselEntries = ""
945 if(hasattr(args, 'fileloc') and args.fileloc is not None):
946 if os.path.exists(args.fileloc):
947 with open(args.fileloc, 'r') as selFile:
948 selLines = selFile.readlines()
949 rawselEntries = ''.join(selLines)
950 else:
951 print("Error: File not found")
952 sys.exit(1)
953 else:
954 rawselEntries = sel(host, args, session)
955 loadFailed = False
956 try:
957 selEntries = json.loads(rawselEntries)
958 except ValueError:
959 loadFailed = True
960 if loadFailed:
961 cleanSels = json.dumps(rawselEntries).replace('\\n', '')
962 #need to load json twice as original content was string escaped a second time
963 selEntries = json.loads(json.loads(cleanSels))
964 selEntries = selEntries['data']
Justin Thalere412dc22018-01-12 16:28:24 -0600965
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600966 if 'description' in selEntries:
967 if(args.json):
968 return("{\n\t\"numAlerts\": 0\n}")
969 else:
970 return("No log entries found")
971
972 else:
973 if(len(policyTable)>0):
974 events = parseAlerts(policyTable, selEntries, args)
975 if(args.json):
976 events["numAlerts"] = len(events)
977 retValue = str(json.dumps(events, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False))
978 return retValue
979 elif(hasattr(args, 'fullSel')):
980 return events
981 else:
982 #get log numbers to order event entries sequentially
983 return selDisplay(events, args)
984 else:
985 if(args.json):
986 return selEntries
987 else:
988 print("error: Policy Table not found.")
989 return selEntries
Justin Thalere412dc22018-01-12 16:28:24 -0600990
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600991def selList(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600992 """
993 prints out all all bmc alerts, or only prints out the specified alerts
994
995 @param host: string, the hostname or IP address of the bmc
996 @param args: contains additional arguments used by the fru sub command
997 @param session: the active session to use
998 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
999 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001000 return(sel(host, args, session))
1001
Justin Thalere412dc22018-01-12 16:28:24 -06001002
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001003def selClear(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001004 """
1005 clears all alerts
1006
1007 @param host: string, the hostname or IP address of the bmc
1008 @param args: contains additional arguments used by the fru sub command
1009 @param session: the active session to use
1010 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1011 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001012 url="https://"+host+"/xyz/openbmc_project/logging/action/deleteAll"
1013 httpHeader = {'Content-Type':'application/json'}
1014 data = "{\"data\": [] }"
Justin Thalere412dc22018-01-12 16:28:24 -06001015
1016 try:
1017 res = session.post(url, headers=httpHeader, data=data, verify=False, timeout=30)
1018 except(requests.exceptions.Timeout):
1019 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001020 if res.status_code == 200:
1021 return "The Alert Log has been cleared. Please allow a few minutes for the action to complete."
1022 else:
1023 print("Unable to clear the logs, trying to clear 1 at a time")
1024 sels = json.loads(sel(host, args, session))['data']
1025 for key in sels:
1026 if 'callout' not in key:
1027 logNum = key.split('/')[-1]
1028 url = "https://"+ host+ "/xyz/openbmc_project/logging/entry/"+logNum+"/action/Delete"
1029 try:
1030 session.post(url, headers=httpHeader, data=data, verify=False, timeout=30)
1031 except(requests.exceptions.Timeout):
1032 return connectionErrHandler(args.json, "Timeout", None)
1033 sys.exit(1)
1034 except(requests.exceptions.ConnectionError) as err:
1035 return connectionErrHandler(args.json, "ConnectionError", err)
1036 sys.exit(1)
1037 return ('Sel clearing complete')
1038
1039def selSetResolved(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001040 """
1041 sets a sel entry to resolved
1042
1043 @param host: string, the hostname or IP address of the bmc
1044 @param args: contains additional arguments used by the fru sub command
1045 @param session: the active session to use
1046 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1047 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001048 url="https://"+host+"/xyz/openbmc_project/logging/entry/" + str(args.selNum) + "/attr/Resolved"
1049 httpHeader = {'Content-Type':'application/json'}
1050 data = "{\"data\": 1 }"
Justin Thalere412dc22018-01-12 16:28:24 -06001051 try:
1052 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1053 except(requests.exceptions.Timeout):
1054 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001055 if res.status_code == 200:
1056 return "Sel entry "+ str(args.selNum) +" is now set to resolved"
1057 else:
1058 return "Unable to set the alert to resolved"
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001059
Justin Thalere412dc22018-01-12 16:28:24 -06001060def selResolveAll(host, args, session):
1061 """
1062 sets a sel entry to resolved
1063
1064 @param host: string, the hostname or IP address of the bmc
1065 @param args: contains additional arguments used by the fru sub command
1066 @param session: the active session to use
1067 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1068 """
1069 rawselEntries = sel(host, args, session)
1070 loadFailed = False
1071 try:
1072 selEntries = json.loads(rawselEntries)
1073 except ValueError:
1074 loadFailed = True
1075 if loadFailed:
1076 cleanSels = json.dumps(rawselEntries).replace('\\n', '')
1077 #need to load json twice as original content was string escaped a second time
1078 selEntries = json.loads(json.loads(cleanSels))
1079 selEntries = selEntries['data']
1080
1081 if 'description' in selEntries:
1082 if(args.json):
1083 return("{\n\t\"selsResolved\": 0\n}")
1084 else:
1085 return("No log entries found")
1086 else:
1087 d = vars(args)
1088 successlist = []
1089 failedlist = []
1090 for key in selEntries:
1091 if 'callout' not in key:
1092 d['selNum'] = key.split('/')[-1]
1093 resolved = selSetResolved(host,args,session)
1094 if 'Sel entry' in resolved:
1095 successlist.append(d['selNum'])
1096 else:
1097 failedlist.append(d['selNum'])
1098 output = ""
1099 successlist.sort()
1100 failedlist.sort()
1101 if len(successlist)>0:
1102 output = "Successfully resolved: " +', '.join(successlist) +"\n"
1103 if len(failedlist)>0:
1104 output += "Failed to resolve: " + ', '.join(failedlist) + "\n"
1105 return output
1106
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001107def chassisPower(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001108 """
1109 called by the chassis function. Controls the power state of the chassis, or gets the status
1110
1111 @param host: string, the hostname or IP address of the bmc
1112 @param args: contains additional arguments used by the fru sub command
1113 @param session: the active session to use
1114 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1115 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001116 if(args.powcmd == 'on'):
Justin Thaler22b1bb52018-03-15 13:31:32 -05001117 if checkFWactivation(host, args, session):
1118 return ("Chassis Power control disabled during firmware activation")
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001119 print("Attempting to Power on...:")
1120 url="https://"+host+"/xyz/openbmc_project/state/host0/attr/RequestedHostTransition"
1121 httpHeader = {'Content-Type':'application/json',}
1122 data = '{"data":"xyz.openbmc_project.State.Host.Transition.On"}'
Justin Thalere412dc22018-01-12 16:28:24 -06001123 try:
1124 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1125 except(requests.exceptions.Timeout):
1126 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001127 return res.text
Justin Thalere412dc22018-01-12 16:28:24 -06001128 elif(args.powcmd == 'softoff'):
Justin Thaler22b1bb52018-03-15 13:31:32 -05001129 if checkFWactivation(host, args, session):
1130 return ("Chassis Power control disabled during firmware activation")
Justin Thalere412dc22018-01-12 16:28:24 -06001131 print("Attempting to Power off gracefully...:")
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001132 url="https://"+host+"/xyz/openbmc_project/state/host0/attr/RequestedHostTransition"
1133 httpHeader = {'Content-Type':'application/json'}
1134 data = '{"data":"xyz.openbmc_project.State.Host.Transition.Off"}'
Justin Thalere412dc22018-01-12 16:28:24 -06001135 try:
1136 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1137 except(requests.exceptions.Timeout):
1138 return(connectionErrHandler(args.json, "Timeout", None))
1139 return res.text
1140 elif(args.powcmd == 'hardoff'):
Justin Thaler22b1bb52018-03-15 13:31:32 -05001141 if checkFWactivation(host, args, session):
1142 return ("Chassis Power control disabled during firmware activation")
Justin Thalere412dc22018-01-12 16:28:24 -06001143 print("Attempting to Power off immediately...:")
1144 url="https://"+host+"/xyz/openbmc_project/state/chassis0/attr/RequestedPowerTransition"
1145 httpHeader = {'Content-Type':'application/json'}
1146 data = '{"data":"xyz.openbmc_project.State.Chassis.Transition.Off"}'
1147 try:
1148 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1149 except(requests.exceptions.Timeout):
1150 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001151 return res.text
1152 elif(args.powcmd == 'status'):
1153 url="https://"+host+"/xyz/openbmc_project/state/chassis0/attr/CurrentPowerState"
1154 httpHeader = {'Content-Type':'application/json'}
1155# print(url)
Justin Thalere412dc22018-01-12 16:28:24 -06001156 try:
1157 res = session.get(url, headers=httpHeader, verify=False, timeout=30)
1158 except(requests.exceptions.Timeout):
1159 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001160 chassisState = json.loads(res.text)['data'].split('.')[-1]
1161 url="https://"+host+"/xyz/openbmc_project/state/host0/attr/CurrentHostState"
Justin Thalere412dc22018-01-12 16:28:24 -06001162 try:
1163 res = session.get(url, headers=httpHeader, verify=False, timeout=30)
1164 except(requests.exceptions.Timeout):
1165 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001166 hostState = json.loads(res.text)['data'].split('.')[-1]
1167 url="https://"+host+"/xyz/openbmc_project/state/bmc0/attr/CurrentBMCState"
Justin Thalere412dc22018-01-12 16:28:24 -06001168 try:
1169 res = session.get(url, headers=httpHeader, verify=False, timeout=30)
1170 except(requests.exceptions.Timeout):
1171 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001172 bmcState = json.loads(res.text)['data'].split('.')[-1]
1173 if(args.json):
1174 outDict = {"Chassis Power State" : chassisState, "Host Power State" : hostState, "BMC Power State":bmcState}
1175 return json.dumps(outDict, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False)
1176 else:
1177 return "Chassis Power State: " +chassisState + "\nHost Power State: " + hostState + "\nBMC Power State: " + bmcState
1178 else:
1179 return "Invalid chassis power command"
1180
Justin Thalere412dc22018-01-12 16:28:24 -06001181
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001182def chassisIdent(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001183 """
1184 called by the chassis function. Controls the identify led of the chassis. Sets or gets the state
1185
1186 @param host: string, the hostname or IP address of the bmc
1187 @param args: contains additional arguments used by the fru sub command
1188 @param session: the active session to use
1189 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1190 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001191 if(args.identcmd == 'on'):
1192 print("Attempting to turn identify light on...:")
1193 url="https://"+host+"/xyz/openbmc_project/led/groups/enclosure_identify/attr/Asserted"
1194 httpHeader = {'Content-Type':'application/json',}
1195 data = '{"data":true}'
Justin Thalere412dc22018-01-12 16:28:24 -06001196 try:
1197 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1198 except(requests.exceptions.Timeout):
1199 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001200 return res.text
1201 elif(args.identcmd == 'off'):
1202 print("Attempting to turn identify light off...:")
1203 url="https://"+host+"/xyz/openbmc_project/led/groups/enclosure_identify/attr/Asserted"
1204 httpHeader = {'Content-Type':'application/json'}
1205 data = '{"data":false}'
Justin Thalere412dc22018-01-12 16:28:24 -06001206 try:
1207 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1208 except(requests.exceptions.Timeout):
1209 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001210 return res.text
1211 elif(args.identcmd == 'status'):
1212 url="https://"+host+"/xyz/openbmc_project/led/groups/enclosure_identify"
1213 httpHeader = {'Content-Type':'application/json'}
Justin Thalere412dc22018-01-12 16:28:24 -06001214 try:
1215 res = session.get(url, headers=httpHeader, verify=False, timeout=30)
1216 except(requests.exceptions.Timeout):
1217 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001218 status = json.loads(res.text)['data']
1219 if(args.json):
1220 return status
1221 else:
1222 if status['Asserted'] == 0:
1223 return "Identify light is off"
1224 else:
1225 return "Identify light is blinking"
1226 else:
1227 return "Invalid chassis identify command"
1228
Justin Thalere412dc22018-01-12 16:28:24 -06001229
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001230def chassis(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001231 """
1232 controls the different chassis commands
1233
1234 @param host: string, the hostname or IP address of the bmc
1235 @param args: contains additional arguments used by the fru sub command
1236 @param session: the active session to use
1237 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1238 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001239 if(hasattr(args, 'powcmd')):
1240 result = chassisPower(host,args,session)
1241 elif(hasattr(args, 'identcmd')):
1242 result = chassisIdent(host, args, session)
1243 else:
Justin Thaler22b1bb52018-03-15 13:31:32 -05001244 return "This feature is not yet implemented"
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001245 return result
Justin Thalere412dc22018-01-12 16:28:24 -06001246
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001247def bmcDumpRetrieve(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001248 """
1249 Downloads a dump file from the bmc
1250
1251 @param host: string, the hostname or IP address of the bmc
1252 @param args: contains additional arguments used by the collectServiceData sub command
1253 @param session: the active session to use
1254 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1255 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001256 httpHeader = {'Content-Type':'application/json'}
1257 dumpNum = args.dumpNum
1258 if (args.dumpSaveLoc is not None):
1259 saveLoc = args.dumpSaveLoc
1260 else:
Justin Thalercf1deae2018-05-25 19:35:21 -05001261 saveLoc = tempfile.gettempdir()
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001262 url ='https://'+host+'/download/dump/' + str(dumpNum)
1263 try:
Justin Thalere412dc22018-01-12 16:28:24 -06001264 r = session.get(url, headers=httpHeader, stream=True, verify=False, timeout=30)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001265 if (args.dumpSaveLoc is not None):
1266 if os.path.exists(saveLoc):
1267 if saveLoc[-1] != os.path.sep:
1268 saveLoc = saveLoc + os.path.sep
1269 filename = saveLoc + host+'-dump' + str(dumpNum) + '.tar.xz'
1270
1271 else:
1272 return 'Invalid save location specified'
1273 else:
Justin Thalercf1deae2018-05-25 19:35:21 -05001274 filename = tempfile.gettempdir()+os.sep + host+'-dump' + str(dumpNum) + '.tar.xz'
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001275
1276 with open(filename, 'wb') as f:
1277 for chunk in r.iter_content(chunk_size =1024):
1278 if chunk:
1279 f.write(chunk)
1280 return 'Saved as ' + filename
1281
1282 except(requests.exceptions.Timeout):
1283 return connectionErrHandler(args.json, "Timeout", None)
Justin Thalere412dc22018-01-12 16:28:24 -06001284
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001285 except(requests.exceptions.ConnectionError) as err:
1286 return connectionErrHandler(args.json, "ConnectionError", err)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001287
Justin Thalere412dc22018-01-12 16:28:24 -06001288def bmcDumpList(host, args, session):
1289 """
1290 Lists the number of dump files on the bmc
1291
1292 @param host: string, the hostname or IP address of the bmc
1293 @param args: contains additional arguments used by the collectServiceData sub command
1294 @param session: the active session to use
1295 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1296 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001297 httpHeader = {'Content-Type':'application/json'}
1298 url ='https://'+host+'/xyz/openbmc_project/dump/list'
1299 try:
1300 r = session.get(url, headers=httpHeader, verify=False, timeout=20)
1301 dumpList = json.loads(r.text)
1302 return r.text
1303 except(requests.exceptions.Timeout):
1304 return connectionErrHandler(args.json, "Timeout", None)
Justin Thalere412dc22018-01-12 16:28:24 -06001305
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001306 except(requests.exceptions.ConnectionError) as err:
Justin Thalere412dc22018-01-12 16:28:24 -06001307 return connectionErrHandler(args.json, "ConnectionError", err)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001308
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001309def bmcDumpDelete(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001310 """
1311 Deletes BMC dump files from the bmc
1312
1313 @param host: string, the hostname or IP address of the bmc
1314 @param args: contains additional arguments used by the collectServiceData sub command
1315 @param session: the active session to use
1316 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1317 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001318 httpHeader = {'Content-Type':'application/json'}
1319 dumpList = []
1320 successList = []
1321 failedList = []
1322 if args.dumpNum is not None:
1323 if isinstance(args.dumpNum, list):
1324 dumpList = args.dumpNum
1325 else:
1326 dumpList.append(args.dumpNum)
1327 for dumpNum in dumpList:
1328 url ='https://'+host+'/xyz/openbmc_project/dump/entry/'+str(dumpNum)+'/action/Delete'
1329 try:
1330 r = session.post(url, headers=httpHeader, json = {"data": []}, verify=False, timeout=30)
1331 if r.status_code == 200:
1332 successList.append(str(dumpNum))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001333 else:
1334 failedList.append(str(dumpNum))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001335 except(requests.exceptions.Timeout):
1336 return connectionErrHandler(args.json, "Timeout", None)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001337 except(requests.exceptions.ConnectionError) as err:
1338 return connectionErrHandler(args.json, "ConnectionError", err)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001339 output = "Successfully deleted dumps: " + ', '.join(successList)
1340 if(len(failedList)>0):
1341 output+= '\nFailed to delete dumps: ' + ', '.join(failedList)
1342 return output
1343 else:
1344 return 'You must specify an entry number to delete'
1345
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001346def bmcDumpDeleteAll(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001347 """
1348 Deletes All BMC dump files from the bmc
1349
1350 @param host: string, the hostname or IP address of the bmc
1351 @param args: contains additional arguments used by the collectServiceData sub command
1352 @param session: the active session to use
1353 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1354 """
1355 dumpResp = bmcDumpList(host, args, session)
1356 if 'FQPSPIN0000M' in dumpResp or 'FQPSPIN0001M'in dumpResp:
1357 return dumpResp
1358 dumpList = json.loads(dumpResp)['data']
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001359 d = vars(args)
1360 dumpNums = []
1361 for dump in dumpList:
1362 if '/xyz/openbmc_project/dump/internal/manager' not in dump:
1363 dumpNums.append(int(dump.strip().split('/')[-1]))
1364 d['dumpNum'] = dumpNums
1365
1366 return bmcDumpDelete(host, args, session)
1367
Justin Thalere412dc22018-01-12 16:28:24 -06001368
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001369def bmcDumpCreate(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001370 """
1371 Creates a bmc dump file
1372
1373 @param host: string, the hostname or IP address of the bmc
1374 @param args: contains additional arguments used by the collectServiceData sub command
1375 @param session: the active session to use
1376 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1377 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001378 httpHeader = {'Content-Type':'application/json'}
1379 url = 'https://'+host+'/xyz/openbmc_project/dump/action/CreateDump'
1380 try:
1381 r = session.post(url, headers=httpHeader, json = {"data": []}, verify=False, timeout=30)
1382 if('"message": "200 OK"' in r.text and not args.json):
1383 return ('Dump successfully created')
1384 else:
1385 return ('Failed to create dump')
1386 except(requests.exceptions.Timeout):
1387 return connectionErrHandler(args.json, "Timeout", None)
1388 except(requests.exceptions.ConnectionError) as err:
1389 return connectionErrHandler(args.json, "ConnectionError", err)
1390
1391
1392
Justin Thalere412dc22018-01-12 16:28:24 -06001393
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001394def collectServiceData(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001395 """
1396 Collects all data needed for service from the BMC
1397
1398 @param host: string, the hostname or IP address of the bmc
1399 @param args: contains additional arguments used by the collectServiceData sub command
1400 @param session: the active session to use
1401 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1402 """
Justin Thaler22b1bb52018-03-15 13:31:32 -05001403
1404 global toolVersion
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001405 #create a bmc dump
1406 dumpcount = len(json.loads(bmcDumpList(host, args, session))['data'])
1407 try:
1408 dumpcreated = bmcDumpCreate(host, args, session)
1409 except Exception as e:
1410 print('failed to create a bmc dump')
1411
1412
1413 #Collect Inventory
1414 try:
1415 args.silent = True
Justin Thalercf1deae2018-05-25 19:35:21 -05001416 myDir = tempfile.gettempdir()+os.sep + host + "--" + datetime.datetime.now().strftime("%Y-%m-%d_%H.%M.%S")
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001417 os.makedirs(myDir)
1418 filelist = []
1419 frulist = fruPrint(host, args, session)
1420 with open(myDir +'/inventory.txt', 'w') as f:
1421 f.write(frulist)
1422 print("Inventory collected and stored in " + myDir + "/inventory.txt")
1423 filelist.append(myDir+'/inventory.txt')
1424 except Exception as e:
1425 print("Failed to collect inventory")
1426
1427 #Read all the sensor and OCC status
1428 try:
1429 sensorReadings = sensor(host, args, session)
1430 with open(myDir +'/sensorReadings.txt', 'w') as f:
1431 f.write(sensorReadings)
1432 print("Sensor readings collected and stored in " +myDir + "/sensorReadings.txt")
1433 filelist.append(myDir+'/sensorReadings.txt')
1434 except Exception as e:
1435 print("Failed to collect sensor readings")
1436
1437 #Collect all of the LEDs status
1438 try:
1439 url="https://"+host+"/xyz/openbmc_project/led/enumerate"
1440 httpHeader = {'Content-Type':'application/json'}
1441 leds = session.get(url, headers=httpHeader, verify=False, timeout=20)
1442 with open(myDir +'/ledStatus.txt', 'w') as f:
1443 f.write(leds.text)
1444 print("System LED status collected and stored in "+myDir +"/ledStatus.txt")
1445 filelist.append(myDir+'/ledStatus.txt')
1446 except Exception as e:
1447 print("Failed to collect LED status")
1448
1449 #Collect the bmc logs
1450 try:
1451 sels = selPrint(host,args,session)
1452 with open(myDir +'/SELshortlist.txt', 'w') as f:
1453 f.write(str(sels))
1454 print("sel short list collected and stored in "+myDir +"/SELshortlist.txt")
1455 filelist.append(myDir+'/SELshortlist.txt')
1456 time.sleep(2)
1457
1458 d = vars(args)
1459 d['json'] = True
1460 d['fullSel'] = True
1461 parsedfullsels = json.loads(selPrint(host, args, session))
1462 d['fullEsel'] = True
1463 sortedSELs = sortSELs(parsedfullsels)
1464 with open(myDir +'/parsedSELs.txt', 'w') as f:
1465 for log in sortedSELs[0]:
1466 esel = ""
1467 parsedfullsels[sortedSELs[1][str(log)]]['timestamp'] = datetime.datetime.fromtimestamp(int(parsedfullsels[sortedSELs[1][str(log)]]['timestamp']/1000)).strftime("%Y-%m-%d %H:%M:%S")
1468 if ('raweSEL' in parsedfullsels[sortedSELs[1][str(log)]] and args.devdebug):
1469 esel = parsedfullsels[sortedSELs[1][str(log)]]['raweSEL']
1470 del parsedfullsels[sortedSELs[1][str(log)]]['raweSEL']
1471 f.write(json.dumps(parsedfullsels[sortedSELs[1][str(log)]],sort_keys=True, indent=4, separators=(',', ': ')))
1472 if(args.devdebug and esel != ""):
1473 f.write(parseESEL(args, esel))
1474 print("fully parsed sels collected and stored in "+myDir +"/parsedSELs.txt")
1475 filelist.append(myDir+'/parsedSELs.txt')
1476 except Exception as e:
1477 print("Failed to collect system event logs")
1478 print(e)
1479
1480 #collect RAW bmc enumeration
1481 try:
1482 url="https://"+host+"/xyz/openbmc_project/enumerate"
1483 print("Attempting to get a full BMC enumeration")
1484 fullDump = session.get(url, headers=httpHeader, verify=False, timeout=120)
1485 with open(myDir +'/bmcFullRaw.txt', 'w') as f:
1486 f.write(fullDump.text)
1487 print("RAW BMC data collected and saved into "+myDir +"/bmcFullRaw.txt")
1488 filelist.append(myDir+'/bmcFullRaw.txt')
1489 except Exception as e:
1490 print("Failed to collect bmc full enumeration")
1491
1492 #collect the dump files
1493 waitingForNewDump = True
1494 count = 0;
1495 while(waitingForNewDump):
1496 dumpList = json.loads(bmcDumpList(host, args, session))['data']
1497 if len(dumpList) > dumpcount:
1498 waitingForNewDump = False
1499 break;
1500 elif(count>30):
1501 print("Timed out waiting for bmc to make a new dump file. Dump space may be full.")
1502 break;
1503 else:
1504 time.sleep(2)
1505 count += 1
1506 try:
1507 print('Collecting bmc dump files')
1508 d['dumpSaveLoc'] = myDir
1509 dumpList = json.loads(bmcDumpList(host, args, session))['data']
1510 for dump in dumpList:
1511 if '/xyz/openbmc_project/dump/internal/manager' not in dump:
1512 d['dumpNum'] = int(dump.strip().split('/')[-1])
1513 print('retrieving dump file ' + str(d['dumpNum']))
1514 filename = bmcDumpRetrieve(host, args, session).split('Saved as ')[-1]
1515 filelist.append(filename)
1516 time.sleep(2)
1517 except Exception as e:
1518 print("Failed to collect bmc dump files")
1519 print(e)
1520
1521 #create the zip file
1522 try:
Justin Thalercf1deae2018-05-25 19:35:21 -05001523 filename = myDir.split(tempfile.gettempdir()+os.sep)[-1] + "_" + toolVersion + '_openbmc.zip'
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001524 zf = zipfile.ZipFile(myDir+'/' + filename, 'w')
1525 for myfile in filelist:
1526 zf.write(myfile, os.path.basename(myfile))
1527 zf.close()
1528 except Exception as e:
1529 print("Failed to create zip file with collected information")
1530 return "data collection complete"
1531
Justin Thalere412dc22018-01-12 16:28:24 -06001532
1533def healthCheck(host, args, session):
1534 """
1535 runs a health check on the platform
1536
1537 @param host: string, the hostname or IP address of the bmc
1538 @param args: contains additional arguments used by the bmc sub command
1539 @param session: the active session to use
1540 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1541 """
1542 #check fru status and get as json to easily work through
1543 d = vars(args)
1544 useJson = d['json']
1545 d['json'] = True
1546 d['verbose']= False
1547
1548 frus = json.loads(fruStatus(host, args, session))
1549
1550 hwStatus= "OK"
1551 performanceStatus = "OK"
1552 for key in frus:
1553 if frus[key]["Functional"] == "No" and frus[key]["Present"] == "Yes":
1554 hwStatus= "Degraded"
1555 if("power_supply" in key):
1556 gpuCount =0;
1557 frulist = json.loads(fruList(host, args, session))
1558 for comp in frulist:
1559 if "gv100card" in comp:
1560 gpuCount +=1
1561 if gpuCount > 4:
1562 hwStatus = "Critical"
1563 performanceStatus="Degraded"
1564 break;
1565 elif("fan" in key):
1566 hwStatus = "Degraded"
1567 else:
1568 performanceStatus = "Degraded"
1569 if useJson:
1570 output = {"Hardware Status": hwStatus, "Performance": performanceStatus}
1571 output = json.dumps(output, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False)
1572 else:
1573 output = ("Hardware Status: " + hwStatus +
1574 "\nPerformance: " +performanceStatus )
1575
1576
1577 #SW407886: Clear the duplicate entries
1578 #collect the dups
1579 d['devdebug'] = False
1580 sels = json.loads(selPrint(host, args, session))
1581 logNums2Clr = []
1582 oldestLogNum={"logNum": "bogus" ,"key" : ""}
1583 count = 0
1584 if sels['numAlerts'] > 0:
1585 for key in sels:
1586 if "numAlerts" in key:
1587 continue
1588 try:
1589 if "slave@00:00/00:00:00:06/sbefifo1-dev0/occ1-dev0" in sels[key]['Message']:
1590 count += 1
1591 if count > 1:
1592 #preserve first occurrence
1593 if sels[key]['timestamp'] < sels[oldestLogNum['key']]['timestamp']:
1594 oldestLogNum['key']=key
1595 oldestLogNum['logNum'] = sels[key]['logNum']
1596 else:
1597 oldestLogNum['key']=key
1598 oldestLogNum['logNum'] = sels[key]['logNum']
1599 logNums2Clr.append(sels[key]['logNum'])
1600 except KeyError:
1601 continue
1602 if(count >0):
1603 logNums2Clr.remove(oldestLogNum['logNum'])
1604 #delete the dups
1605 if count >1:
1606 httpHeader = {'Content-Type':'application/json'}
1607 data = "{\"data\": [] }"
1608 for logNum in logNums2Clr:
1609 url = "https://"+ host+ "/xyz/openbmc_project/logging/entry/"+logNum+"/action/Delete"
1610 try:
1611 session.post(url, headers=httpHeader, data=data, verify=False, timeout=30)
1612 except(requests.exceptions.Timeout):
1613 deleteFailed = True
1614 except(requests.exceptions.ConnectionError) as err:
1615 deleteFailed = True
1616 #End of defect resolve code
1617 d['json'] = useJson
1618 return output
1619
1620
1621
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001622def bmc(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001623 """
1624 handles various bmc level commands, currently bmc rebooting
1625
1626 @param host: string, the hostname or IP address of the bmc
1627 @param args: contains additional arguments used by the bmc sub command
1628 @param session: the active session to use
1629 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1630 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001631 if(args.type is not None):
1632 return bmcReset(host, args, session)
Justin Thalere412dc22018-01-12 16:28:24 -06001633 if(args.info):
1634 return "Not implemented at this time"
1635
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001636
Justin Thalere412dc22018-01-12 16:28:24 -06001637
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001638def bmcReset(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001639 """
1640 controls resetting the bmc. warm reset reboots the bmc, cold reset removes the configuration and reboots.
1641
1642 @param host: string, the hostname or IP address of the bmc
1643 @param args: contains additional arguments used by the bmcReset sub command
1644 @param session: the active session to use
1645 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1646 """
Justin Thaler22b1bb52018-03-15 13:31:32 -05001647 if checkFWactivation(host, args, session):
1648 return ("BMC reset control disabled during firmware activation")
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001649 if(args.type == "warm"):
1650 print("\nAttempting to reboot the BMC...:")
1651 url="https://"+host+"/xyz/openbmc_project/state/bmc0/attr/RequestedBMCTransition"
1652 httpHeader = {'Content-Type':'application/json'}
Justin Thalere412dc22018-01-12 16:28:24 -06001653 data = '{"data":"xyz.openbmc_project.State.BMC.Transition.Reboot"}'
1654 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=20)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001655 return res.text
1656 elif(args.type =="cold"):
Justin Thalere412dc22018-01-12 16:28:24 -06001657 print("\nAttempting to reboot the BMC...:")
1658 url="https://"+host+"/xyz/openbmc_project/state/bmc0/attr/RequestedBMCTransition"
1659 httpHeader = {'Content-Type':'application/json'}
1660 data = '{"data":"xyz.openbmc_project.State.BMC.Transition.Reboot"}'
1661 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=20)
1662 return res.text
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001663 else:
1664 return "invalid command"
Justin Thalere412dc22018-01-12 16:28:24 -06001665
1666def gardClear(host, args, session):
1667 """
1668 clears the gard records from the bmc
1669
1670 @param host: string, the hostname or IP address of the bmc
1671 @param args: contains additional arguments used by the gardClear sub command
1672 @param session: the active session to use
1673 """
1674 url="https://"+host+"/org/open_power/control/gard/action/Reset"
1675 httpHeader = {'Content-Type':'application/json'}
1676 data = '{"data":[]}'
1677 try:
1678
1679 res = session.post(url, headers=httpHeader, data=data, verify=False, timeout=30)
1680 if res.status_code == 404:
1681 return "Command not supported by this firmware version"
1682 else:
1683 return res.text
1684 except(requests.exceptions.Timeout):
1685 return connectionErrHandler(args.json, "Timeout", None)
1686 except(requests.exceptions.ConnectionError) as err:
1687 return connectionErrHandler(args.json, "ConnectionError", err)
1688
1689def activateFWImage(host, args, session):
1690 """
1691 activates a firmware image on the bmc
1692
1693 @param host: string, the hostname or IP address of the bmc
1694 @param args: contains additional arguments used by the fwflash sub command
1695 @param session: the active session to use
1696 @param fwID: the unique ID of the fw image to activate
1697 """
1698 fwID = args.imageID
1699
1700 #determine the existing versions
1701 httpHeader = {'Content-Type':'application/json'}
1702 url="https://"+host+"/xyz/openbmc_project/software/enumerate"
1703 try:
1704 resp = session.get(url, headers=httpHeader, verify=False, timeout=30)
1705 except(requests.exceptions.Timeout):
1706 return connectionErrHandler(args.json, "Timeout", None)
1707 except(requests.exceptions.ConnectionError) as err:
1708 return connectionErrHandler(args.json, "ConnectionError", err)
1709 existingSoftware = json.loads(resp.text)['data']
1710 altVersionID = ''
1711 versionType = ''
1712 imageKey = '/xyz/openbmc_project/software/'+fwID
1713 if imageKey in existingSoftware:
1714 versionType = existingSoftware[imageKey]['Purpose']
1715 for key in existingSoftware:
1716 if imageKey == key:
1717 continue
1718 if 'Purpose' in existingSoftware[key]:
1719 if versionType == existingSoftware[key]['Purpose']:
1720 altVersionID = key.split('/')[-1]
1721
1722
1723
1724
1725 url="https://"+host+"/xyz/openbmc_project/software/"+ fwID + "/attr/Priority"
1726 url1="https://"+host+"/xyz/openbmc_project/software/"+ altVersionID + "/attr/Priority"
1727 data = "{\"data\": 0}"
1728 data1 = "{\"data\": 1 }"
1729 try:
1730 resp = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1731 resp1 = session.put(url1, headers=httpHeader, data=data1, verify=False, timeout=30)
1732 except(requests.exceptions.Timeout):
1733 return connectionErrHandler(args.json, "Timeout", None)
1734 except(requests.exceptions.ConnectionError) as err:
1735 return connectionErrHandler(args.json, "ConnectionError", err)
1736 if(not args.json):
1737 if resp.status_code == 200 and resp1.status_code == 200:
Justin Thaler22b1bb52018-03-15 13:31:32 -05001738 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 -06001739 else:
1740 return "Firmware activation failed."
1741 else:
1742 return resp.text + resp1.text
Justin Thaler22b1bb52018-03-15 13:31:32 -05001743
1744def activateStatus(host, args, session):
1745 if checkFWactivation(host, args, session):
1746 return("Firmware is currently being activated. Do not reboot the BMC or start the Host OS")
1747 else:
1748 return("No firmware activations are pending")
1749
1750def extractFWimage(path, imageType):
1751 """
1752 extracts the bmc image and returns information about the package
1753
1754 @param path: the path and file name of the firmware image
1755 @param imageType: The type of image the user is trying to flash. Host or BMC
1756 @return: the image id associated with the package. returns an empty string on error.
1757 """
1758 f = tempfile.TemporaryFile()
1759 tmpDir = tempfile.gettempdir()
1760 newImageID = ""
1761 if os.path.exists(path):
1762 try:
1763 imageFile = tarfile.open(path,'r')
1764 contents = imageFile.getmembers()
1765 for tf in contents:
1766 if 'MANIFEST' in tf.name:
1767 imageFile.extract(tf.name, path=tmpDir)
1768 with open(tempfile.gettempdir() +os.sep+ tf.name, 'r') as imageInfo:
1769 for line in imageInfo:
1770 if 'purpose' in line:
1771 purpose = line.split('=')[1]
1772 if imageType not in purpose.split('.')[-1]:
1773 print('The specified image is not for ' + imageType)
1774 print('Please try again with the image for ' + imageType)
1775 return ""
1776 if 'version' == line.split('=')[0]:
1777 version = line.split('=')[1].strip().encode('utf-8')
1778 m = hashlib.sha512()
1779 m.update(version)
1780 newImageID = m.hexdigest()[:8]
1781 break
1782 try:
1783 os.remove(tempfile.gettempdir() +os.sep+ tf.name)
1784 except OSError:
1785 pass
1786 return newImageID
1787 except tarfile.ExtractError as e:
1788 print('Unable to extract information from the firmware file.')
1789 print('Ensure you have write access to the directory: ' + tmpDir)
1790 return newImageID
1791 except tarfile.TarError as e:
1792 print('This is not a valid firmware file.')
1793 return newImageID
1794 print("This is not a valid firmware file.")
1795 return newImageID
1796 else:
1797 print('The filename and path provided are not valid.')
1798 return newImageID
1799
1800def getAllFWImageIDs(fwInvDict):
1801 """
1802 gets a list of all the firmware image IDs
1803
1804 @param fwInvDict: the dictionary to search for FW image IDs
1805 @return: list containing string representation of the found image ids
1806 """
1807 idList = []
1808 for key in fwInvDict:
1809 if 'Version' in fwInvDict[key]:
1810 idList.append(key.split('/')[-1])
1811 return idList
1812
Justin Thalere412dc22018-01-12 16:28:24 -06001813def fwFlash(host, args, session):
1814 """
1815 updates the bmc firmware and pnor firmware
1816
1817 @param host: string, the hostname or IP address of the bmc
1818 @param args: contains additional arguments used by the fwflash sub command
1819 @param session: the active session to use
1820 """
Justin Thaler22b1bb52018-03-15 13:31:32 -05001821 d = vars(args)
Justin Thalere412dc22018-01-12 16:28:24 -06001822 if(args.type == 'bmc'):
1823 purp = 'BMC'
1824 else:
1825 purp = 'Host'
Justin Thaler22b1bb52018-03-15 13:31:32 -05001826
1827 #check power state of the machine. No concurrent FW updates allowed
1828 d['powcmd'] = 'status'
1829 powerstate = chassisPower(host, args, session)
1830 if 'Chassis Power State: On' in powerstate:
1831 return("Aborting firmware update. Host is powered on. Please turn off the host and try again.")
1832
1833 #determine the existing images on the bmc
Justin Thalere412dc22018-01-12 16:28:24 -06001834 httpHeader = {'Content-Type':'application/json'}
1835 url="https://"+host+"/xyz/openbmc_project/software/enumerate"
1836 try:
1837 resp = session.get(url, headers=httpHeader, verify=False, timeout=30)
1838 except(requests.exceptions.Timeout):
1839 return connectionErrHandler(args.json, "Timeout", None)
1840 except(requests.exceptions.ConnectionError) as err:
1841 return connectionErrHandler(args.json, "ConnectionError", err)
1842 oldsoftware = json.loads(resp.text)['data']
1843
Justin Thaler22b1bb52018-03-15 13:31:32 -05001844 #Extract the tar and get information from the manifest file
1845 newversionID = extractFWimage(args.fileloc, purp)
1846 if newversionID == "":
1847 return "Unable to verify FW image."
1848
1849
1850 #check if the new image is already on the bmc
1851 if newversionID not in getAllFWImageIDs(oldsoftware):
1852
1853 #upload the file
1854 httpHeader = {'Content-Type':'application/octet-stream'}
1855 url="https://"+host+"/upload/image"
1856 data=open(args.fileloc,'rb').read()
1857 print("Uploading file to BMC")
Justin Thalere412dc22018-01-12 16:28:24 -06001858 try:
Justin Thaler22b1bb52018-03-15 13:31:32 -05001859 resp = session.post(url, headers=httpHeader, data=data, verify=False)
Justin Thalere412dc22018-01-12 16:28:24 -06001860 except(requests.exceptions.Timeout):
1861 return connectionErrHandler(args.json, "Timeout", None)
1862 except(requests.exceptions.ConnectionError) as err:
1863 return connectionErrHandler(args.json, "ConnectionError", err)
Justin Thaler22b1bb52018-03-15 13:31:32 -05001864 if resp.status_code != 200:
1865 return "Failed to upload the file to the bmc"
Justin Thalere412dc22018-01-12 16:28:24 -06001866 else:
Justin Thaler22b1bb52018-03-15 13:31:32 -05001867 print("Upload complete.")
1868
1869 #verify bmc processed the image
1870 software ={}
1871 for i in range(0, 5):
1872 httpHeader = {'Content-Type':'application/json'}
1873 url="https://"+host+"/xyz/openbmc_project/software/enumerate"
1874 try:
1875 resp = session.get(url, headers=httpHeader, verify=False, timeout=30)
1876 except(requests.exceptions.Timeout):
1877 return connectionErrHandler(args.json, "Timeout", None)
1878 except(requests.exceptions.ConnectionError) as err:
1879 return connectionErrHandler(args.json, "ConnectionError", err)
1880 software = json.loads(resp.text)['data']
1881 #check if bmc is done processing the new image
1882 if (newversionID in getAllFWImageIDs(software)):
Justin Thalere412dc22018-01-12 16:28:24 -06001883 break
Justin Thaler22b1bb52018-03-15 13:31:32 -05001884 else:
1885 time.sleep(15)
1886
1887 #activate the new image
1888 print("Activating new image: "+newversionID)
1889 url="https://"+host+"/xyz/openbmc_project/software/"+ newversionID + "/attr/RequestedActivation"
1890 data = '{"data":"xyz.openbmc_project.Software.Activation.RequestedActivations.Active"}'
1891 try:
1892 resp = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1893 except(requests.exceptions.Timeout):
1894 return connectionErrHandler(args.json, "Timeout", None)
1895 except(requests.exceptions.ConnectionError) as err:
1896 return connectionErrHandler(args.json, "ConnectionError", err)
1897
1898 #wait for the activation to complete, timeout after ~1 hour
1899 i=0
1900 while i < 360:
1901 url="https://"+host+"/xyz/openbmc_project/software/"+ newversionID
1902 data = '{"data":"xyz.openbmc_project.Software.Activation.RequestedActivations.Active"}'
1903 try:
1904 resp = session.get(url, headers=httpHeader, verify=False, timeout=30)
1905 except(requests.exceptions.Timeout):
1906 return connectionErrHandler(args.json, "Timeout", None)
1907 except(requests.exceptions.ConnectionError) as err:
1908 return connectionErrHandler(args.json, "ConnectionError", err)
1909 fwInfo = json.loads(resp.text)['data']
1910 if 'Activating' not in fwInfo['Activation'] and 'Activating' not in fwInfo['RequestedActivation']:
1911 print('')
1912 break
1913 else:
1914 sys.stdout.write('.')
1915 sys.stdout.flush()
1916 time.sleep(10) #check every 10 seconds
1917 return "Firmware flash and activation completed. Please reboot the bmc and then boot the host OS for the changes to take effect. "
1918 else:
1919 print("This image has been found on the bmc. Activating image: " + newversionID)
1920
1921 d['imageID'] = newversionID
1922 return activateFWImage(host, args, session)
Justin Thalere412dc22018-01-12 16:28:24 -06001923
Justin Thaler22b1bb52018-03-15 13:31:32 -05001924
Justin Thalere412dc22018-01-12 16:28:24 -06001925
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001926def createCommandParser():
Justin Thalere412dc22018-01-12 16:28:24 -06001927 """
1928 creates the parser for the command line along with help for each command and subcommand
1929
1930 @return: returns the parser for the command line
1931 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001932 parser = argparse.ArgumentParser(description='Process arguments')
Justin Thalere412dc22018-01-12 16:28:24 -06001933 parser.add_argument("-H", "--host", help='A hostname or IP for the BMC')
1934 parser.add_argument("-U", "--user", help='The username to login with')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001935 group = parser.add_mutually_exclusive_group()
1936 group.add_argument("-A", "--askpw", action='store_true', help='prompt for password')
1937 group.add_argument("-P", "--PW", help='Provide the password in-line')
1938 parser.add_argument('-j', '--json', action='store_true', help='output json data only')
1939 parser.add_argument('-t', '--policyTableLoc', help='The location of the policy table to parse alerts')
1940 parser.add_argument('-c', '--CerFormat', action='store_true', help=argparse.SUPPRESS)
1941 parser.add_argument('-T', '--procTime', action='store_true', help= argparse.SUPPRESS)
Justin Thalere412dc22018-01-12 16:28:24 -06001942 parser.add_argument('-V', '--version', action='store_true', help='Display the version number of the openbmctool')
1943 subparsers = parser.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001944
1945 #fru command
1946 parser_inv = subparsers.add_parser("fru", help='Work with platform inventory')
1947 #fru print
Justin Thalere412dc22018-01-12 16:28:24 -06001948 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 -06001949 inv_print = inv_subparser.add_parser("print", help="prints out a list of all FRUs")
1950 inv_print.set_defaults(func=fruPrint)
1951 #fru list [0....n]
1952 inv_list = inv_subparser.add_parser("list", help="print out details on selected FRUs. Specifying no items will list the entire inventory")
1953 inv_list.add_argument('items', nargs='?', help="print out details on selected FRUs. Specifying no items will list the entire inventory")
1954 inv_list.set_defaults(func=fruList)
1955 #fru status
1956 inv_status = inv_subparser.add_parser("status", help="prints out the status of all FRUs")
Justin Thalere412dc22018-01-12 16:28:24 -06001957 inv_status.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001958 inv_status.set_defaults(func=fruStatus)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001959
1960 #sensors command
1961 parser_sens = subparsers.add_parser("sensors", help="Work with platform sensors")
Justin Thalere412dc22018-01-12 16:28:24 -06001962 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 -06001963 #sensor print
1964 sens_print= sens_subparser.add_parser('print', help="prints out a list of all Sensors.")
1965 sens_print.set_defaults(func=sensor)
1966 #sensor list[0...n]
1967 sens_list=sens_subparser.add_parser("list", help="Lists all Sensors in the platform. Specify a sensor for full details. ")
1968 sens_list.add_argument("sensNum", nargs='?', help="The Sensor number to get full details on" )
1969 sens_list.set_defaults(func=sensor)
1970
1971
1972 #sel command
1973 parser_sel = subparsers.add_parser("sel", help="Work with platform alerts")
Justin Thalere412dc22018-01-12 16:28:24 -06001974 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 -06001975
1976 #sel print
1977 sel_print = sel_subparser.add_parser("print", help="prints out a list of all sels in a condensed list")
1978 sel_print.add_argument('-d', '--devdebug', action='store_true', help=argparse.SUPPRESS)
1979 sel_print.add_argument('-v', '--verbose', action='store_true', help="Changes the output to being very verbose")
1980 sel_print.add_argument('-f', '--fileloc', help='Parse a file instead of the BMC output')
1981 sel_print.set_defaults(func=selPrint)
Justin Thaler22b1bb52018-03-15 13:31:32 -05001982
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001983 #sel list
1984 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")
1985 sel_list.add_argument("selNum", nargs='?', type=int, help="The SEL entry to get details on")
1986 sel_list.set_defaults(func=selList)
1987
1988 sel_get = sel_subparser.add_parser("get", help="Gets the verbose details of a specified SEL entry")
1989 sel_get.add_argument('selNum', type=int, help="the number of the SEL entry to get")
1990 sel_get.set_defaults(func=selList)
1991
1992 sel_clear = sel_subparser.add_parser("clear", help="Clears all entries from the SEL")
1993 sel_clear.set_defaults(func=selClear)
1994
1995 sel_setResolved = sel_subparser.add_parser("resolve", help="Sets the sel entry to resolved")
Justin Thalere412dc22018-01-12 16:28:24 -06001996 sel_setResolved.add_argument('-n', '--selNum', type=int, help="the number of the SEL entry to resolve")
1997 sel_ResolveAll_sub = sel_setResolved.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
1998 sel_ResolveAll = sel_ResolveAll_sub.add_parser('all', help='Resolve all SEL entries')
1999 sel_ResolveAll.set_defaults(func=selResolveAll)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002000 sel_setResolved.set_defaults(func=selSetResolved)
2001
2002 parser_chassis = subparsers.add_parser("chassis", help="Work with chassis power and status")
Justin Thalere412dc22018-01-12 16:28:24 -06002003 chas_sub = parser_chassis.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002004
2005 parser_chassis.add_argument('status', action='store_true', help='Returns the current status of the platform')
2006 parser_chassis.set_defaults(func=chassis)
2007
2008 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 -06002009 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 -06002010 parser_chasPower.set_defaults(func=chassisPower)
2011
2012 #control the chassis identify led
2013 parser_chasIdent = chas_sub.add_parser("identify", help="Control the chassis identify led")
2014 parser_chasIdent.add_argument('identcmd', choices=['on', 'off', 'status'], help='The control option for the led: on, off, blink, status')
2015 parser_chasIdent.set_defaults(func=chassisIdent)
2016
2017 #collect service data
2018 parser_servData = subparsers.add_parser("collect_service_data", help="Collect all bmc data needed for service")
2019 parser_servData.add_argument('-d', '--devdebug', action='store_true', help=argparse.SUPPRESS)
2020 parser_servData.set_defaults(func=collectServiceData)
2021
Justin Thalere412dc22018-01-12 16:28:24 -06002022 #system quick health check
2023 parser_healthChk = subparsers.add_parser("health_check", help="Work with platform sensors")
2024 parser_healthChk.set_defaults(func=healthCheck)
2025
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002026 #work with bmc dumps
2027 parser_bmcdump = subparsers.add_parser("dump", help="Work with bmc dump files")
Justin Thalere412dc22018-01-12 16:28:24 -06002028 bmcDump_sub = parser_bmcdump.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002029 dump_Create = bmcDump_sub.add_parser('create', help="Create a bmc dump")
2030 dump_Create.set_defaults(func=bmcDumpCreate)
2031
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002032 dump_list = bmcDump_sub.add_parser('list', help="list all bmc dump files")
2033 dump_list.set_defaults(func=bmcDumpList)
2034
2035 parserdumpdelete = bmcDump_sub.add_parser('delete', help="Delete bmc dump files")
2036 parserdumpdelete.add_argument("-n", "--dumpNum", nargs='*', type=int, help="The Dump entry to delete")
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002037 parserdumpdelete.set_defaults(func=bmcDumpDelete)
2038
Justin Thalere412dc22018-01-12 16:28:24 -06002039 bmcDumpDelsub = parserdumpdelete.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002040 deleteAllDumps = bmcDumpDelsub.add_parser('all', help='Delete all bmc dump files')
2041 deleteAllDumps.set_defaults(func=bmcDumpDeleteAll)
2042
2043 parser_dumpretrieve = bmcDump_sub.add_parser('retrieve', help='Retrieve a dump file')
2044 parser_dumpretrieve.add_argument("dumpNum", type=int, help="The Dump entry to delete")
2045 parser_dumpretrieve.add_argument("-s", "--dumpSaveLoc", help="The location to save the bmc dump file")
2046 parser_dumpretrieve.set_defaults(func=bmcDumpRetrieve)
2047
Justin Thaler22b1bb52018-03-15 13:31:32 -05002048 #bmc command for reseting the bmc
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002049 parser_bmc = subparsers.add_parser('bmc', help="Work with the bmc")
Justin Thalere412dc22018-01-12 16:28:24 -06002050 bmc_sub = parser_bmc.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002051 parser_BMCReset = bmc_sub.add_parser('reset', help='Reset the bmc' )
2052 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 -06002053 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.")
2054 parser_bmc.set_defaults(func=bmc)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002055
2056 #add alias to the bmc command
2057 parser_mc = subparsers.add_parser('mc', help="Work with the management controller")
Justin Thalere412dc22018-01-12 16:28:24 -06002058 mc_sub = parser_mc.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002059 parser_MCReset = mc_sub.add_parser('reset', help='Reset the bmc' )
2060 parser_MCReset.add_argument('type', choices=['warm','cold'], help="Reboot the BMC")
2061 #parser_MCReset.add_argument('cold', action='store_true', help="Reboot the BMC and CLEAR the configuration")
2062 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 -06002063 parser_MCReset.set_defaults(func=bmcReset)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002064 parser_mc.set_defaults(func=bmc)
Justin Thalere412dc22018-01-12 16:28:24 -06002065
2066 #gard clear
2067 parser_gc = subparsers.add_parser("gardclear", help="Used to clear gard records")
2068 parser_gc.set_defaults(func=gardClear)
2069
2070 #firmware_flash
2071 parser_fw = subparsers.add_parser("firmware", help="Work with the system firmware")
2072 fwflash_subproc = parser_fw.add_subparsers(title='subcommands', description='valid firmware commands', help='sub-command help', dest='command')
2073 fwflash = fwflash_subproc.add_parser('flash', help="Flash the system firmware")
2074 fwflash.add_argument('type', choices=['bmc', 'pnor'], help="image type to flash")
2075 fwflash.add_argument('-f', '--fileloc', required=True, help="The absolute path to the firmware image")
2076 fwflash.set_defaults(func=fwFlash)
2077
Justin Thaler22b1bb52018-03-15 13:31:32 -05002078 fwActivate = fwflash_subproc.add_parser('activate', help="Activate existing image on the bmc")
Justin Thalere412dc22018-01-12 16:28:24 -06002079 fwActivate.add_argument('imageID', help="The image ID to activate from the firmware list. Ex: 63c95399")
2080 fwActivate.set_defaults(func=activateFWImage)
2081
Justin Thaler22b1bb52018-03-15 13:31:32 -05002082 fwActivateStatus = fwflash_subproc.add_parser('activation_status', help="Check Status of activations")
2083 fwActivateStatus.set_defaults(func=activateStatus)
2084
2085
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002086 return parser
2087
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002088def main(argv=None):
Justin Thalere412dc22018-01-12 16:28:24 -06002089 """
2090 main function for running the command line utility as a sub application
2091 """
Justin Thaler22b1bb52018-03-15 13:31:32 -05002092 global toolVersion
Justin Thalerf17d9eb2018-05-25 19:42:24 -05002093 toolVersion = "1.04"
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002094 parser = createCommandParser()
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002095 args = parser.parse_args(argv)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002096
2097 totTimeStart = int(round(time.time()*1000))
2098
2099 if(sys.version_info < (3,0)):
2100 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
2101 if sys.version_info >= (3,0):
2102 requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
Justin Thalere412dc22018-01-12 16:28:24 -06002103 if (args.version):
Justin Thaler22b1bb52018-03-15 13:31:32 -05002104 print("Version: "+ toolVersion)
Justin Thalere412dc22018-01-12 16:28:24 -06002105 sys.exit(0)
2106 if (hasattr(args, 'fileloc') and args.fileloc is not None and 'print' in args.command):
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002107 mysess = None
Justin Thalere412dc22018-01-12 16:28:24 -06002108 print(selPrint('N/A', args, mysess))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002109 else:
Justin Thalere412dc22018-01-12 16:28:24 -06002110 if(hasattr(args, 'host') and hasattr(args,'user')):
2111 if (args.askpw):
2112 pw = getpass.getpass()
2113 elif(args.PW is not None):
2114 pw = args.PW
2115 else:
2116 print("You must specify a password")
2117 sys.exit()
2118 logintimeStart = int(round(time.time()*1000))
2119 mysess = login(args.host, args.user, pw, args.json)
Justin Thalera9415b42018-05-25 19:40:13 -05002120 if(sys.version_info < (3,0)):
2121 if isinstance(mysess, basestring):
2122 print(mysess)
2123 sys.exit(1)
2124 elif sys.version_info >= (3,0):
2125 if isinstance(mysess, str):
2126 print(mysess)
2127 sys.exit(1)
Justin Thalere412dc22018-01-12 16:28:24 -06002128 logintimeStop = int(round(time.time()*1000))
2129
2130 commandTimeStart = int(round(time.time()*1000))
2131 output = args.func(args.host, args, mysess)
2132 commandTimeStop = int(round(time.time()*1000))
2133 print(output)
2134 if (mysess is not None):
2135 logout(args.host, args.user, pw, mysess, args.json)
2136 if(args.procTime):
2137 print("Total time: " + str(int(round(time.time()*1000))- totTimeStart))
2138 print("loginTime: " + str(logintimeStop - logintimeStart))
2139 print("command Time: " + str(commandTimeStop - commandTimeStart))
2140 else:
2141 print("usage: openbmctool.py [-h] -H HOST -U USER [-A | -P PW] [-j]\n" +
2142 "\t[-t POLICYTABLELOC] [-V]\n" +
2143 "\t{fru,sensors,sel,chassis,collect_service_data,health_check,dump,bmc,mc,gardclear,firmware}\n" +
2144 "\t...\n" +
2145 "openbmctool.py: error: the following arguments are required: -H/--host, -U/--user")
2146 sys.exit()
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002147
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002148if __name__ == '__main__':
Justin Thalere412dc22018-01-12 16:28:24 -06002149 """
2150 main function when called from the command line
2151
2152 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002153 import sys
2154
2155 isTTY = sys.stdout.isatty()
2156 assert sys.version_info >= (2,7)
2157 main()