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