blob: 5d220b2f8d7987bd632466efca7f032f543803cf [file] [log] [blame]
Chris Austen4f394962018-03-21 08:52:41 -05001#!/usr/bin/env 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:
76 errorMessageStr = ("{\n\t\"event0\":{\n" +
77 "\t\t\"CommonEventID\": \"FQPSPIN0000M\",\n"+
78 "\t\t\"sensor\": \"N/A\",\n"+
79 "\t\t\"state\": \"N/A\",\n" +
80 "\t\t\"additionalDetails\": \"N/A\",\n" +
81 "\t\t\"Message\": \"Connection timed out. Ensure you have network connectivity to the BMC\",\n" +
82 "\t\t\"LengthyDescription\": \"While trying to establish a connection with the specified BMC, the BMC failed to respond in adequate time. Verify the BMC is functioning properly, and the network connectivity to the BMC is stable.\",\n" +
83 "\t\t\"Serviceable\": \"Yes\",\n" +
84 "\t\t\"CallHomeCandidate\": \"No\",\n" +
85 "\t\t\"Severity\": \"Critical\",\n" +
86 "\t\t\"EventType\": \"Communication Failure/Timeout\",\n" +
87 "\t\t\"VMMigrationFlag\": \"Yes\",\n" +
88 "\t\t\"AffectedSubsystem\": \"Interconnect (Networking)\",\n" +
89 "\t\t\"timestamp\": \""+str(int(time.time()))+"\",\n" +
90 "\t\t\"UserAction\": \"Verify network connectivity between the two systems and the bmc is functional.\"" +
91 "\t\n}, \n" +
92 "\t\"numAlerts\": \"1\" \n}");
93 return(errorMessageStr)
94 elif errorStr == "ConnectionError":
95 if not jsonFormat:
96 return("FQPSPIN0001M: " + str(err))
97 else:
98 errorMessageStr = ("{\n\t\"event0\":{\n" +
99 "\t\t\"CommonEventID\": \"FQPSPIN0001M\",\n"+
100 "\t\t\"sensor\": \"N/A\",\n"+
101 "\t\t\"state\": \"N/A\",\n" +
102 "\t\t\"additionalDetails\": \"" + str(err)+"\",\n" +
103 "\t\t\"Message\": \"Connection Error. View additional details for more information\",\n" +
104 "\t\t\"LengthyDescription\": \"A connection error to the specified BMC occurred and additional details are provided. Review these details to resolve the issue.\",\n" +
105 "\t\t\"Serviceable\": \"Yes\",\n" +
106 "\t\t\"CallHomeCandidate\": \"No\",\n" +
107 "\t\t\"Severity\": \"Critical\",\n" +
108 "\t\t\"EventType\": \"Communication Failure/Timeout\",\n" +
109 "\t\t\"VMMigrationFlag\": \"Yes\",\n" +
110 "\t\t\"AffectedSubsystem\": \"Interconnect (Networking)\",\n" +
111 "\t\t\"timestamp\": \""+str(int(time.time()))+"\",\n" +
112 "\t\t\"UserAction\": \"Correct the issue highlighted in additional details and try again\"" +
113 "\t\n}, \n" +
114 "\t\"numAlerts\": \"1\" \n}");
115 return(errorMessageStr)
116 else:
117 return("Unknown Error: "+ str(err))
118
Justin Thalere412dc22018-01-12 16:28:24 -0600119
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600120def setColWidth(keylist, numCols, dictForOutput, colNames):
Justin Thalere412dc22018-01-12 16:28:24 -0600121 """
122 Sets the output width of the columns to display
123
124 @param keylist: list, list of strings representing the keys for the dictForOutput
125 @param numcols: the total number of columns in the final output
126 @param dictForOutput: dictionary, contains the information to print to the screen
127 @param colNames: list, The strings to use for the column headings, in order of the keylist
128 @return: A list of the column widths for each respective column.
129 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600130 colWidths = []
131 for x in range(0, numCols):
132 colWidths.append(0)
133 for key in dictForOutput:
134 for x in range(0, numCols):
135 colWidths[x] = max(colWidths[x], len(str(dictForOutput[key][keylist[x]])))
136
137 for x in range(0, numCols):
138 colWidths[x] = max(colWidths[x], len(colNames[x])) +2
139
140 return colWidths
141
142def loadPolicyTable(pathToPolicyTable):
Justin Thalere412dc22018-01-12 16:28:24 -0600143 """
144 loads a json based policy table into a dictionary
145
146 @param value: boolean, the value to convert
147 @return: A string of "Yes" or "No"
148 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600149 policyTable = {}
150 if(os.path.exists(pathToPolicyTable)):
151 with open(pathToPolicyTable, 'r') as stream:
152 try:
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600153 contents =json.load(stream)
154 policyTable = contents['events']
Justin Thalere412dc22018-01-12 16:28:24 -0600155 except Exception as err:
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600156 print(err)
157 return policyTable
158
Justin Thalere412dc22018-01-12 16:28:24 -0600159
160def boolToString(value):
161 """
162 converts a boolean value to a human readable string value
163
164 @param value: boolean, the value to convert
165 @return: A string of "Yes" or "No"
166 """
167 if(value):
168 return "Yes"
169 else:
170 return "No"
171
172
173def tableDisplay(keylist, colNames, output):
174 """
175 Logs into the BMC and creates a session
176
177 @param keylist: list, keys for the output dictionary, ordered by colNames
178 @param colNames: Names for the Table of the columns
179 @param output: The dictionary of data to display
180 @return: Session object
181 """
182 colWidth = setColWidth(keylist, len(colNames), output, colNames)
183 row = ""
184 outputText = ""
185 for i in range(len(colNames)):
186 if (i != 0): row = row + "| "
187 row = row + colNames[i].ljust(colWidth[i])
188 outputText += row + "\n"
189
190 for key in sorted(output.keys()):
191 row = ""
192 for i in range(len(output[key])):
193 if (i != 0): row = row + "| "
194 row = row + output[key][keylist[i]].ljust(colWidth[i])
195 outputText += row + "\n"
196
197 return outputText
198
Justin Thaler22b1bb52018-03-15 13:31:32 -0500199def checkFWactivation(host, args, session):
200 """
201 Checks the software inventory for an image that is being activated.
202
203 @return: True if an image is being activated, false is no activations are happening
204 """
205 url="https://"+host+"/xyz/openbmc_project/software/enumerate"
206 httpHeader = {'Content-Type':'application/json'}
207 try:
208 resp = session.get(url, headers=httpHeader, verify=False, timeout=30)
209 except(requests.exceptions.Timeout):
210 print(connectionErrHandler(args.json, "Timeout", None))
211 return(True)
212 except(requests.exceptions.ConnectionError) as err:
213 print( connectionErrHandler(args.json, "ConnectionError", err))
214 return True
215 fwInfo = json.loads(resp.text)['data']
216 for key in fwInfo:
217 if 'Activation' in fwInfo[key]:
218 if 'Activating' in fwInfo[key]['Activation'] or 'Activating' in fwInfo[key]['RequestedActivation']:
219 return True
220 return False
221
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600222def login(host, username, pw,jsonFormat):
Justin Thalere412dc22018-01-12 16:28:24 -0600223 """
224 Logs into the BMC and creates a session
225
226 @param host: string, the hostname or IP address of the bmc to log into
227 @param username: The user name for the bmc to log into
228 @param pw: The password for the BMC to log into
229 @param jsonFormat: boolean, flag that will only allow relevant data from user command to be display. This function becomes silent when set to true.
230 @return: Session object
231 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600232 if(jsonFormat==False):
233 print("Attempting login...")
234 httpHeader = {'Content-Type':'application/json'}
235 mysess = requests.session()
236 try:
237 r = mysess.post('https://'+host+'/login', headers=httpHeader, json = {"data": [username, pw]}, verify=False, timeout=30)
238 loginMessage = json.loads(r.text)
239 if (loginMessage['status'] != "ok"):
240 print(loginMessage["data"]["description"].encode('utf-8'))
241 sys.exit(1)
242# if(sys.version_info < (3,0)):
243# urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
244# if sys.version_info >= (3,0):
245# requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
246 return mysess
247 except(requests.exceptions.Timeout):
248 print(connectionErrHandler(jsonFormat, "Timeout", None))
249 sys.exit(1)
250 except(requests.exceptions.ConnectionError) as err:
251 print(connectionErrHandler(jsonFormat, "ConnectionError", err))
252 sys.exit(1)
253
Justin Thalere412dc22018-01-12 16:28:24 -0600254
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600255def logout(host, username, pw, session, jsonFormat):
Justin Thalere412dc22018-01-12 16:28:24 -0600256 """
257 Logs out of the bmc and terminates the session
258
259 @param host: string, the hostname or IP address of the bmc to log out of
260 @param username: The user name for the bmc to log out of
261 @param pw: The password for the BMC to log out of
262 @param session: the active session to use
263 @param jsonFormat: boolean, flag that will only allow relevant data from user command to be display. This function becomes silent when set to true.
264 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600265 httpHeader = {'Content-Type':'application/json'}
Justin Thalere412dc22018-01-12 16:28:24 -0600266 try:
267 r = session.post('https://'+host+'/logout', headers=httpHeader,json = {"data": [username, pw]}, verify=False, timeout=10)
268 except(requests.exceptions.Timeout):
269 print(connectionErrHandler(jsonFormat, "Timeout", None))
270
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600271 if(jsonFormat==False):
272 if('"message": "200 OK"' in r.text):
273 print('User ' +username + ' has been logged out')
274
Justin Thalere412dc22018-01-12 16:28:24 -0600275
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600276def fru(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600277 """
278 prints out the system inventory. deprecated see fruPrint and fruList
279
280 @param host: string, the hostname or IP address of the bmc
281 @param args: contains additional arguments used by the fru sub command
282 @param session: the active session to use
283 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
284 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600285 #url="https://"+host+"/org/openbmc/inventory/system/chassis/enumerate"
286
287 #print(url)
288 #res = session.get(url, headers=httpHeader, verify=False)
289 #print(res.text)
290 #sample = res.text
291
292 #inv_list = json.loads(sample)["data"]
293
294 url="https://"+host+"/xyz/openbmc_project/inventory/enumerate"
295 httpHeader = {'Content-Type':'application/json'}
Justin Thalere412dc22018-01-12 16:28:24 -0600296 try:
297 res = session.get(url, headers=httpHeader, verify=False, timeout=40)
298 except(requests.exceptions.Timeout):
299 return(connectionErrHandler(args.json, "Timeout", None))
300
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600301 sample = res.text
302# inv_list.update(json.loads(sample)["data"])
303#
304# #determine column width's
305# colNames = ["FRU Name", "FRU Type", "Has Fault", "Is FRU", "Present", "Version"]
306# colWidths = setColWidth(["FRU Name", "fru_type", "fault", "is_fru", "present", "version"], 6, inv_list, colNames)
307#
308# print("FRU Name".ljust(colWidths[0])+ "FRU Type".ljust(colWidths[1]) + "Has Fault".ljust(colWidths[2]) + "Is FRU".ljust(colWidths[3])+
309# "Present".ljust(colWidths[4]) + "Version".ljust(colWidths[5]))
310# format the output
311# for key in sorted(inv_list.keys()):
312# keyParts = key.split("/")
313# isFRU = "True" if (inv_list[key]["is_fru"]==1) else "False"
314#
315# fruEntry = (keyParts[len(keyParts) - 1].ljust(colWidths[0]) + inv_list[key]["fru_type"].ljust(colWidths[1])+
316# inv_list[key]["fault"].ljust(colWidths[2])+isFRU.ljust(colWidths[3])+
317# inv_list[key]["present"].ljust(colWidths[4])+ inv_list[key]["version"].ljust(colWidths[5]))
318# if(isTTY):
319# if(inv_list[key]["is_fru"] == 1):
320# color = "green"
321# bold = True
322# else:
323# color='black'
324# bold = False
325# fruEntry = hilight(fruEntry, color, bold)
326# print (fruEntry)
327 return sample
Justin Thalere412dc22018-01-12 16:28:24 -0600328
329def fruPrint(host, args, session):
330 """
331 prints out all inventory
332
333 @param host: string, the hostname or IP address of the bmc
334 @param args: contains additional arguments used by the fru sub command
335 @param session: the active session to use
336 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
337 @return returns the total fru list.
338 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600339 url="https://"+host+"/xyz/openbmc_project/inventory/enumerate"
340 httpHeader = {'Content-Type':'application/json'}
Justin Thalere412dc22018-01-12 16:28:24 -0600341 try:
342 res = session.get(url, headers=httpHeader, verify=False, timeout=40)
343 except(requests.exceptions.Timeout):
344 return(connectionErrHandler(args.json, "Timeout", None))
345
346
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600347# print(res.text)
348 frulist = res.text
349 url="https://"+host+"/xyz/openbmc_project/software/enumerate"
Justin Thalere412dc22018-01-12 16:28:24 -0600350 try:
351 res = session.get(url, headers=httpHeader, verify=False, timeout=40)
352 except(requests.exceptions.Timeout):
353 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600354# print(res.text)
355 frulist = frulist +"\n" + res.text
356
357 return frulist
358
Justin Thalere412dc22018-01-12 16:28:24 -0600359
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600360def fruList(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600361 """
362 prints out all inventory or only a specific specified item
363
364 @param host: string, the hostname or IP address of the bmc
365 @param args: contains additional arguments used by the fru sub command
366 @param session: the active session to use
367 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
368 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600369 if(args.items==True):
370 return fruPrint(host, args, session)
371 else:
Justin Thalere412dc22018-01-12 16:28:24 -0600372 return fruPrint(host, args, session)
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600373
374
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600375
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600376def fruStatus(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600377 """
378 prints out the status of all FRUs
379
380 @param host: string, the hostname or IP address of the bmc
381 @param args: contains additional arguments used by the fru sub command
382 @param session: the active session to use
383 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
384 """
385 url="https://"+host+"/xyz/openbmc_project/inventory/enumerate"
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600386 httpHeader = {'Content-Type':'application/json'}
Justin Thalere412dc22018-01-12 16:28:24 -0600387 try:
388 res = session.get(url, headers=httpHeader, verify=False)
389 except(requests.exceptions.Timeout):
390 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600391# print(res.text)
Justin Thalere412dc22018-01-12 16:28:24 -0600392 frulist = json.loads(res.text)['data']
393 frus = {}
394 for key in frulist:
395 component = frulist[key]
396 isFru = False
397 present = False
398 func = False
399 hasSels = False
400 keyPieces = key.split('/')
401 fruName = keyPieces[-1]
402 if 'core' in fruName: #associate cores to cpus
403 fruName = keyPieces[-2] + '-' + keyPieces[-1]
404 if 'Functional' in component:
405 if('Present' in component):
406
407 if 'FieldReplaceable' in component:
408 if component['FieldReplaceable'] == 1:
409 isFru = True
410 if "fan" in fruName:
411 isFru = True;
412 if component['Present'] == 1:
413 present = True
414 if component['Functional'] == 1:
415 func = True
416 if ((key + "/fault") in frulist):
417 hasSels = True;
418 if args.verbose:
419 if hasSels:
420 loglist = []
421 faults = frulist[key+"/fault"]['endpoints']
422 for item in faults:
423 loglist.append(item.split('/')[-1])
424 frus[fruName] = {"compName": fruName, "Functional": boolToString(func), "Present":boolToString(present), "IsFru": boolToString(isFru), "selList": ', '.join(loglist).strip() }
425 else:
426 frus[fruName] = {"compName": fruName, "Functional": boolToString(func), "Present":boolToString(present), "IsFru": boolToString(isFru), "selList": "None" }
427 else:
428 frus[fruName] = {"compName": fruName, "Functional": boolToString(func), "Present":boolToString(present), "IsFru": boolToString(isFru), "hasSEL": boolToString(hasSels) }
429 elif "power_supply" in fruName:
430 if component['Present'] ==1:
431 present = True
432 isFru = True
433 if ((key + "/fault") in frulist):
434 hasSels = True;
435 if args.verbose:
436 if hasSels:
437 loglist = []
438 faults = frulist[key+"/fault"]['endpoints']
439 for key in faults:
440 loglist.append(faults[key].split('/')[-1])
441 frus[fruName] = {"compName": fruName, "Functional": "No", "Present":boolToString(present), "IsFru": boolToString(isFru), "selList": ', '.join(loglist).strip() }
442 else:
443 frus[fruName] = {"compName": fruName, "Functional": "Yes", "Present":boolToString(present), "IsFru": boolToString(isFru), "selList": "None" }
444 else:
445 frus[fruName] = {"compName": fruName, "Functional": boolToString(not hasSels), "Present":boolToString(present), "IsFru": boolToString(isFru), "hasSEL": boolToString(hasSels) }
446 if not args.json:
447 if not args.verbose:
448 colNames = ["Component", "Is a FRU", "Present", "Functional", "Has Logs"]
449 keylist = ["compName", "IsFru", "Present", "Functional", "hasSEL"]
450 else:
451 colNames = ["Component", "Is a FRU", "Present", "Functional", "Assoc. Log Number(s)"]
452 keylist = ["compName", "IsFru", "Present", "Functional", "selList"]
453 return tableDisplay(keylist, colNames, frus)
454 else:
455 return str(json.dumps(frus, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False))
456
457def sensor(host, args, session):
458 """
459 prints out all sensors
460
461 @param host: string, the hostname or IP address of the bmc
462 @param args: contains additional arguments used by the sensor sub command
463 @param session: the active session to use
464 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
465 """
466 httpHeader = {'Content-Type':'application/json'}
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600467 url="https://"+host+"/xyz/openbmc_project/sensors/enumerate"
Justin Thalere412dc22018-01-12 16:28:24 -0600468 try:
469 res = session.get(url, headers=httpHeader, verify=False, timeout=30)
470 except(requests.exceptions.Timeout):
471 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600472
473 #Get OCC status
474 url="https://"+host+"/org/open_power/control/enumerate"
Justin Thalere412dc22018-01-12 16:28:24 -0600475 try:
476 occres = session.get(url, headers=httpHeader, verify=False, timeout=30)
477 except(requests.exceptions.Timeout):
478 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600479 if not args.json:
480 colNames = ['sensor', 'type', 'units', 'value', 'target']
481 sensors = json.loads(res.text)["data"]
482 output = {}
483 for key in sensors:
484 senDict = {}
485 keyparts = key.split("/")
486 senDict['sensorName'] = keyparts[-1]
487 senDict['type'] = keyparts[-2]
Justin Thalere412dc22018-01-12 16:28:24 -0600488 try:
489 senDict['units'] = sensors[key]['Unit'].split('.')[-1]
490 except KeyError:
Justin Thaler22b1bb52018-03-15 13:31:32 -0500491 senDict['units'] = "N/A"
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600492 if('Scale' in sensors[key]):
493 scale = 10 ** sensors[key]['Scale']
494 else:
495 scale = 1
Justin Thaler22b1bb52018-03-15 13:31:32 -0500496 try:
497 senDict['value'] = str(sensors[key]['Value'] * scale)
498 except KeyError:
499 if 'value' in sensors[key]:
500 senDict['value'] = sensors[key]['value']
501 else:
502 senDict['value'] = "N/A"
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600503 if 'Target' in sensors[key]:
504 senDict['target'] = str(sensors[key]['Target'])
505 else:
506 senDict['target'] = 'N/A'
507 output[senDict['sensorName']] = senDict
508
509 occstatus = json.loads(occres.text)["data"]
510 if '/org/open_power/control/occ0' in occstatus:
511 occ0 = occstatus["/org/open_power/control/occ0"]['OccActive']
512 if occ0 == 1:
513 occ0 = 'Active'
514 else:
515 occ0 = 'Inactive'
516 output['OCC0'] = {'sensorName':'OCC0', 'type': 'Discrete', 'units': 'N/A', 'value': occ0, 'target': 'Active'}
517 occ1 = occstatus["/org/open_power/control/occ1"]['OccActive']
518 if occ1 == 1:
519 occ1 = 'Active'
520 else:
521 occ1 = 'Inactive'
522 output['OCC1'] = {'sensorName':'OCC1', 'type': 'Discrete', 'units': 'N/A', 'value': occ0, 'target': 'Active'}
523 else:
524 output['OCC0'] = {'sensorName':'OCC0', 'type': 'Discrete', 'units': 'N/A', 'value': 'Inactive', 'target': 'Inactive'}
525 output['OCC1'] = {'sensorName':'OCC1', 'type': 'Discrete', 'units': 'N/A', 'value': 'Inactive', 'target': 'Inactive'}
526 keylist = ['sensorName', 'type', 'units', 'value', 'target']
Justin Thalere412dc22018-01-12 16:28:24 -0600527
528 return tableDisplay(keylist, colNames, output)
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600529 else:
530 return res.text + occres.text
Justin Thalere412dc22018-01-12 16:28:24 -0600531
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600532def sel(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600533 """
534 prints out the bmc alerts
535
536 @param host: string, the hostname or IP address of the bmc
537 @param args: contains additional arguments used by the sel sub command
538 @param session: the active session to use
539 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
540 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600541
542 url="https://"+host+"/xyz/openbmc_project/logging/entry/enumerate"
543 httpHeader = {'Content-Type':'application/json'}
Justin Thalere412dc22018-01-12 16:28:24 -0600544 try:
545 res = session.get(url, headers=httpHeader, verify=False, timeout=60)
546 except(requests.exceptions.Timeout):
547 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600548 return res.text
Justin Thalere412dc22018-01-12 16:28:24 -0600549
550
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600551def parseESEL(args, eselRAW):
Justin Thalere412dc22018-01-12 16:28:24 -0600552 """
553 parses the esel data and gets predetermined search terms
554
555 @param eselRAW: string, the raw esel string from the bmc
556 @return: A dictionary containing the quick snapshot data unless args.fullEsel is listed then a full PEL log is returned
557 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600558 eselParts = {}
559 esel_bin = binascii.unhexlify(''.join(eselRAW.split()[16:]))
560 #search terms contains the search term as the key and the return dictionary key as it's value
561 searchTerms = { 'Signature Description':'signatureDescription', 'devdesc':'devdesc',
Justin Thaler22b1bb52018-03-15 13:31:32 -0500562 'Callout type': 'calloutType', 'Procedure':'procedure', 'Sensor Type': 'sensorType'}
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600563
564 with open('/tmp/esel.bin', 'wb') as f:
565 f.write(esel_bin)
566 errlPath = ""
567 #use the right errl file for the machine architecture
568 arch = platform.machine()
569 if(arch =='x86_64' or arch =='AMD64'):
570 if os.path.exists('/opt/ibm/ras/bin/x86_64/errl'):
571 errlPath = '/opt/ibm/ras/bin/x86_64/errl'
572 elif os.path.exists('errl/x86_64/errl'):
573 errlPath = 'errl/x86_64/errl'
574 else:
575 errlPath = 'x86_64/errl'
576 elif (platform.machine()=='ppc64le'):
577 if os.path.exists('/opt/ibm/ras/bin/ppc64le/errl'):
578 errlPath = '/opt/ibm/ras/bin/ppc64le/errl'
579 elif os.path.exists('errl/ppc64le/errl'):
580 errlPath = 'errl/ppc64le/errl'
581 else:
582 errlPath = 'ppc64le/errl'
583 else:
584 print("machine architecture not supported for parsing eSELs")
585 return eselParts
586
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600587 if(os.path.exists(errlPath)):
588 output= subprocess.check_output([errlPath, '-d', '--file=/tmp/esel.bin']).decode('utf-8')
589# output = proc.communicate()[0]
590 lines = output.split('\n')
591
592 if(hasattr(args, 'fullEsel')):
593 return output
594
595 for i in range(0, len(lines)):
596 lineParts = lines[i].split(':')
597 if(len(lineParts)>1): #ignore multi lines, output formatting lines, and other information
598 for term in searchTerms:
599 if(term in lineParts[0]):
600 temp = lines[i][lines[i].find(':')+1:].strip()[:-1].strip()
601 if lines[i+1].find(':') != -1:
602 if (len(lines[i+1].split(':')[0][1:].strip())==0):
603 while(len(lines[i][:lines[i].find(':')].strip())>2):
604 if((i+1) <= len(lines)):
605 i+=1
606 else:
607 i=i-1
608 break
609 temp = temp + lines[i][lines[i].find(':'):].strip()[:-1].strip()[:-1].strip()
Justin Thaler22b1bb52018-03-15 13:31:32 -0500610 if(searchTerms[term] in eselParts):
611 eselParts[searchTerms[term]] = eselParts[searchTerms[term]] + ", " + temp
612 else:
613 eselParts[searchTerms[term]] = temp
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600614 os.remove('/tmp/esel.bin')
615 else:
616 print("errl file cannot be found")
617
618 return eselParts
619
Justin Thalere412dc22018-01-12 16:28:24 -0600620
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600621def sortSELs(events):
Justin Thalere412dc22018-01-12 16:28:24 -0600622 """
623 sorts the sels by timestamp, then log entry number
624
625 @param events: Dictionary containing events
626 @return: list containing a list of the ordered log entries, and dictionary of keys
627 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600628 logNumList = []
629 timestampList = []
630 eventKeyDict = {}
631 eventsWithTimestamp = {}
632 logNum2events = {}
633 for key in events:
634 if key == 'numAlerts': continue
635 if 'callout' in key: continue
636 timestamp = (events[key]['timestamp'])
637 if timestamp not in timestampList:
638 eventsWithTimestamp[timestamp] = [events[key]['logNum']]
639 else:
640 eventsWithTimestamp[timestamp].append(events[key]['logNum'])
641 #map logNumbers to the event dictionary keys
642 eventKeyDict[str(events[key]['logNum'])] = key
643
644 timestampList = list(eventsWithTimestamp.keys())
645 timestampList.sort()
646 for ts in timestampList:
647 if len(eventsWithTimestamp[ts]) > 1:
648 tmplist = eventsWithTimestamp[ts]
649 tmplist.sort()
650 logNumList = logNumList + tmplist
651 else:
652 logNumList = logNumList + eventsWithTimestamp[ts]
653
654 return [logNumList, eventKeyDict]
655
Justin Thalere412dc22018-01-12 16:28:24 -0600656
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600657def parseAlerts(policyTable, selEntries, args):
Justin Thalere412dc22018-01-12 16:28:24 -0600658 """
659 parses alerts in the IBM CER format, using an IBM policy Table
660
661 @param policyTable: dictionary, the policy table entries
662 @param selEntries: dictionary, the alerts retrieved from the bmc
663 @return: A dictionary of the parsed entries, in chronological order
664 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600665 eventDict = {}
666 eventNum =""
667 count = 0
668 esel = ""
669 eselParts = {}
670 i2cdevice= ""
671
672 'prepare and sort the event entries'
673 for key in selEntries:
674 if 'callout' not in key:
675 selEntries[key]['logNum'] = key.split('/')[-1]
676 selEntries[key]['timestamp'] = selEntries[key]['Timestamp']
677 sortedEntries = sortSELs(selEntries)
678 logNumList = sortedEntries[0]
679 eventKeyDict = sortedEntries[1]
680
681 for logNum in logNumList:
682 key = eventKeyDict[logNum]
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600683 hasEsel=False
684 i2creadFail = False
685 if 'callout' in key:
686 continue
687 else:
688 messageID = str(selEntries[key]['Message'])
689 addDataPiece = selEntries[key]['AdditionalData']
690 calloutIndex = 0
691 calloutFound = False
692 for i in range(len(addDataPiece)):
693 if("CALLOUT_INVENTORY_PATH" in addDataPiece[i]):
694 calloutIndex = i
695 calloutFound = True
696 fruCallout = str(addDataPiece[calloutIndex]).split('=')[1]
697 if("CALLOUT_DEVICE_PATH" in addDataPiece[i]):
698 i2creadFail = True
699 i2cdevice = str(addDataPiece[i]).strip().split('=')[1]
700 i2cdevice = '/'.join(i2cdevice.split('/')[-4:])
701 fruCallout = 'I2C'
702 calloutFound = True
703 if("ESEL" in addDataPiece[i]):
704 esel = str(addDataPiece[i]).strip().split('=')[1]
705 if args.devdebug:
706 eselParts = parseESEL(args, esel)
707 hasEsel=True
708 if("GPU" in addDataPiece[i]):
709 fruCallout = '/xyz/openbmc_project/inventory/system/chassis/motherboard/gpu' + str(addDataPiece[i]).strip()[-1]
710 calloutFound = True
711 if("PROCEDURE" in addDataPiece[i]):
712 fruCallout = str(hex(int(str(addDataPiece[i]).split('=')[1])))[2:]
713 calloutFound = True
Justin Thalere412dc22018-01-12 16:28:24 -0600714 if("RAIL_NAME" in addDataPiece[i]):
715 calloutFound=True
716 fruCallout = str(addDataPiece[i]).split('=')[1].strip()
717 if("INPUT_NAME" in addDataPiece[i]):
718 calloutFound=True
719 fruCallout = str(addDataPiece[i]).split('=')[1].strip()
720 if("SENSOR_TYPE" in addDataPiece[i]):
721 calloutFound=True
722 fruCallout = str(addDataPiece[i]).split('=')[1].strip()
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600723
724 if(calloutFound):
Justin Thaler22b1bb52018-03-15 13:31:32 -0500725 if fruCallout != "":
726 policyKey = messageID +"||" + fruCallout
727 else:
728 policyKey = messageID
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600729 else:
730 policyKey = messageID
731 event = {}
732 eventNum = str(count)
733 if policyKey in policyTable:
734 for pkey in policyTable[policyKey]:
735 if(type(policyTable[policyKey][pkey])== bool):
736 event[pkey] = boolToString(policyTable[policyKey][pkey])
737 else:
738 if (i2creadFail and pkey == 'Message'):
739 event[pkey] = policyTable[policyKey][pkey] + ' ' +i2cdevice
740 else:
741 event[pkey] = policyTable[policyKey][pkey]
742 event['timestamp'] = selEntries[key]['Timestamp']
743 event['resolved'] = bool(selEntries[key]['Resolved'])
744 if(hasEsel):
745 if args.devdebug:
746 event['eselParts'] = eselParts
747 event['raweSEL'] = esel
748 event['logNum'] = key.split('/')[-1]
749 eventDict['event' + eventNum] = event
750
751 else:
752 severity = str(selEntries[key]['Severity']).split('.')[-1]
753 if severity == 'Error':
754 severity = 'Critical'
755 eventDict['event'+eventNum] = {}
756 eventDict['event' + eventNum]['error'] = "error: Not found in policy table: " + policyKey
757 eventDict['event' + eventNum]['timestamp'] = selEntries[key]['Timestamp']
758 eventDict['event' + eventNum]['Severity'] = severity
759 if(hasEsel):
760 if args.devdebug:
761 eventDict['event' +eventNum]['eselParts'] = eselParts
762 eventDict['event' +eventNum]['raweSEL'] = esel
763 eventDict['event' +eventNum]['logNum'] = key.split('/')[-1]
764 eventDict['event' +eventNum]['resolved'] = bool(selEntries[key]['Resolved'])
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600765 count += 1
766 return eventDict
767
768
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600769def selDisplay(events, args):
Justin Thalere412dc22018-01-12 16:28:24 -0600770 """
771 displays alerts in human readable format
772
773 @param events: Dictionary containing events
774 @return:
775 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600776 activeAlerts = []
777 historyAlerts = []
778 sortedEntries = sortSELs(events)
779 logNumList = sortedEntries[0]
780 eventKeyDict = sortedEntries[1]
781 keylist = ['Entry', 'ID', 'Timestamp', 'Serviceable', 'Severity','Message']
782 if(args.devdebug):
783 colNames = ['Entry', 'ID', 'Timestamp', 'Serviceable', 'Severity','Message', 'eSEL contents']
784 keylist.append('eSEL')
785 else:
786 colNames = ['Entry', 'ID', 'Timestamp', 'Serviceable', 'Severity', 'Message']
787 for log in logNumList:
788 selDict = {}
789 alert = events[eventKeyDict[str(log)]]
790 if('error' in alert):
791 selDict['Entry'] = alert['logNum']
792 selDict['ID'] = 'Unknown'
793 selDict['Timestamp'] = datetime.datetime.fromtimestamp(int(alert['timestamp']/1000)).strftime("%Y-%m-%d %H:%M:%S")
794 msg = alert['error']
795 polMsg = msg.split("policy table:")[0]
796 msg = msg.split("policy table:")[1]
797 msgPieces = msg.split("||")
798 err = msgPieces[0]
799 if(err.find("org.open_power.")!=-1):
800 err = err.split("org.open_power.")[1]
801 elif(err.find("xyz.openbmc_project.")!=-1):
802 err = err.split("xyz.openbmc_project.")[1]
803 else:
804 err = msgPieces[0]
805 callout = ""
806 if len(msgPieces) >1:
807 callout = msgPieces[1]
808 if(callout.find("/org/open_power/")!=-1):
809 callout = callout.split("/org/open_power/")[1]
810 elif(callout.find("/xyz/openbmc_project/")!=-1):
811 callout = callout.split("/xyz/openbmc_project/")[1]
812 else:
813 callout = msgPieces[1]
814 selDict['Message'] = polMsg +"policy table: "+ err + "||" + callout
815 selDict['Serviceable'] = 'Unknown'
816 selDict['Severity'] = alert['Severity']
817 else:
818 selDict['Entry'] = alert['logNum']
819 selDict['ID'] = alert['CommonEventID']
820 selDict['Timestamp'] = datetime.datetime.fromtimestamp(int(alert['timestamp']/1000)).strftime("%Y-%m-%d %H:%M:%S")
821 selDict['Message'] = alert['Message']
822 selDict['Serviceable'] = alert['Serviceable']
823 selDict['Severity'] = alert['Severity']
824
825
826 eselOrder = ['refCode','signatureDescription', 'eselType', 'devdesc', 'calloutType', 'procedure']
827 if ('eselParts' in alert and args.devdebug):
828 eselOutput = ""
829 for item in eselOrder:
830 if item in alert['eselParts']:
831 eselOutput = eselOutput + item + ": " + alert['eselParts'][item] + " | "
832 selDict['eSEL'] = eselOutput
833 else:
834 if args.devdebug:
835 selDict['eSEL'] = "None"
836
837 if not alert['resolved']:
838 activeAlerts.append(selDict)
839 else:
840 historyAlerts.append(selDict)
841 mergedOutput = activeAlerts + historyAlerts
842 colWidth = setColWidth(keylist, len(colNames), dict(enumerate(mergedOutput)), colNames)
843
844 output = ""
845 if(len(activeAlerts)>0):
846 row = ""
847 output +="----Active Alerts----\n"
848 for i in range(0, len(colNames)):
849 if i!=0: row =row + "| "
850 row = row + colNames[i].ljust(colWidth[i])
851 output += row + "\n"
852
853 for i in range(0,len(activeAlerts)):
854 row = ""
855 for j in range(len(activeAlerts[i])):
856 if (j != 0): row = row + "| "
857 row = row + activeAlerts[i][keylist[j]].ljust(colWidth[j])
858 output += row + "\n"
859
860 if(len(historyAlerts)>0):
861 row = ""
862 output+= "----Historical Alerts----\n"
863 for i in range(len(colNames)):
864 if i!=0: row =row + "| "
865 row = row + colNames[i].ljust(colWidth[i])
866 output += row + "\n"
867
868 for i in range(0, len(historyAlerts)):
869 row = ""
870 for j in range(len(historyAlerts[i])):
871 if (j != 0): row = row + "| "
872 row = row + historyAlerts[i][keylist[j]].ljust(colWidth[j])
873 output += row + "\n"
874# print(events[eventKeyDict[str(log)]])
875 return output
876
Justin Thalere412dc22018-01-12 16:28:24 -0600877
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600878def selPrint(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600879 """
880 prints out all bmc alerts
881
882 @param host: string, the hostname or IP address of the bmc
883 @param args: contains additional arguments used by the fru sub command
884 @param session: the active session to use
885 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
886 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600887 if(args.policyTableLoc is None):
888 if os.path.exists('policyTable.json'):
889 ptableLoc = "policyTable.json"
890 elif os.path.exists('/opt/ibm/ras/lib/policyTable.json'):
891 ptableLoc = '/opt/ibm/ras/lib/policyTable.json'
892 else:
893 ptableLoc = 'lib/policyTable.json'
894 else:
895 ptableLoc = args.policyTableLoc
896 policyTable = loadPolicyTable(ptableLoc)
897 rawselEntries = ""
898 if(hasattr(args, 'fileloc') and args.fileloc is not None):
899 if os.path.exists(args.fileloc):
900 with open(args.fileloc, 'r') as selFile:
901 selLines = selFile.readlines()
902 rawselEntries = ''.join(selLines)
903 else:
904 print("Error: File not found")
905 sys.exit(1)
906 else:
907 rawselEntries = sel(host, args, session)
908 loadFailed = False
909 try:
910 selEntries = json.loads(rawselEntries)
911 except ValueError:
912 loadFailed = True
913 if loadFailed:
914 cleanSels = json.dumps(rawselEntries).replace('\\n', '')
915 #need to load json twice as original content was string escaped a second time
916 selEntries = json.loads(json.loads(cleanSels))
917 selEntries = selEntries['data']
Justin Thalere412dc22018-01-12 16:28:24 -0600918
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600919 if 'description' in selEntries:
920 if(args.json):
921 return("{\n\t\"numAlerts\": 0\n}")
922 else:
923 return("No log entries found")
924
925 else:
926 if(len(policyTable)>0):
927 events = parseAlerts(policyTable, selEntries, args)
928 if(args.json):
929 events["numAlerts"] = len(events)
930 retValue = str(json.dumps(events, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False))
931 return retValue
932 elif(hasattr(args, 'fullSel')):
933 return events
934 else:
935 #get log numbers to order event entries sequentially
936 return selDisplay(events, args)
937 else:
938 if(args.json):
939 return selEntries
940 else:
941 print("error: Policy Table not found.")
942 return selEntries
Justin Thalere412dc22018-01-12 16:28:24 -0600943
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600944def selList(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600945 """
946 prints out all all bmc alerts, or only prints out the specified alerts
947
948 @param host: string, the hostname or IP address of the bmc
949 @param args: contains additional arguments used by the fru sub command
950 @param session: the active session to use
951 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
952 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600953 return(sel(host, args, session))
954
Justin Thalere412dc22018-01-12 16:28:24 -0600955
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600956def selClear(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600957 """
958 clears all alerts
959
960 @param host: string, the hostname or IP address of the bmc
961 @param args: contains additional arguments used by the fru sub command
962 @param session: the active session to use
963 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
964 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600965 url="https://"+host+"/xyz/openbmc_project/logging/action/deleteAll"
966 httpHeader = {'Content-Type':'application/json'}
967 data = "{\"data\": [] }"
Justin Thalere412dc22018-01-12 16:28:24 -0600968
969 try:
970 res = session.post(url, headers=httpHeader, data=data, verify=False, timeout=30)
971 except(requests.exceptions.Timeout):
972 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -0600973 if res.status_code == 200:
974 return "The Alert Log has been cleared. Please allow a few minutes for the action to complete."
975 else:
976 print("Unable to clear the logs, trying to clear 1 at a time")
977 sels = json.loads(sel(host, args, session))['data']
978 for key in sels:
979 if 'callout' not in key:
980 logNum = key.split('/')[-1]
981 url = "https://"+ host+ "/xyz/openbmc_project/logging/entry/"+logNum+"/action/Delete"
982 try:
983 session.post(url, headers=httpHeader, data=data, verify=False, timeout=30)
984 except(requests.exceptions.Timeout):
985 return connectionErrHandler(args.json, "Timeout", None)
986 sys.exit(1)
987 except(requests.exceptions.ConnectionError) as err:
988 return connectionErrHandler(args.json, "ConnectionError", err)
989 sys.exit(1)
990 return ('Sel clearing complete')
991
992def selSetResolved(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -0600993 """
994 sets a sel entry to resolved
995
996 @param host: string, the hostname or IP address of the bmc
997 @param args: contains additional arguments used by the fru sub command
998 @param session: the active session to use
999 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1000 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001001 url="https://"+host+"/xyz/openbmc_project/logging/entry/" + str(args.selNum) + "/attr/Resolved"
1002 httpHeader = {'Content-Type':'application/json'}
1003 data = "{\"data\": 1 }"
Justin Thalere412dc22018-01-12 16:28:24 -06001004 try:
1005 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1006 except(requests.exceptions.Timeout):
1007 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001008 if res.status_code == 200:
1009 return "Sel entry "+ str(args.selNum) +" is now set to resolved"
1010 else:
1011 return "Unable to set the alert to resolved"
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001012
Justin Thalere412dc22018-01-12 16:28:24 -06001013def selResolveAll(host, args, session):
1014 """
1015 sets a sel entry to resolved
1016
1017 @param host: string, the hostname or IP address of the bmc
1018 @param args: contains additional arguments used by the fru sub command
1019 @param session: the active session to use
1020 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1021 """
1022 rawselEntries = sel(host, args, session)
1023 loadFailed = False
1024 try:
1025 selEntries = json.loads(rawselEntries)
1026 except ValueError:
1027 loadFailed = True
1028 if loadFailed:
1029 cleanSels = json.dumps(rawselEntries).replace('\\n', '')
1030 #need to load json twice as original content was string escaped a second time
1031 selEntries = json.loads(json.loads(cleanSels))
1032 selEntries = selEntries['data']
1033
1034 if 'description' in selEntries:
1035 if(args.json):
1036 return("{\n\t\"selsResolved\": 0\n}")
1037 else:
1038 return("No log entries found")
1039 else:
1040 d = vars(args)
1041 successlist = []
1042 failedlist = []
1043 for key in selEntries:
1044 if 'callout' not in key:
1045 d['selNum'] = key.split('/')[-1]
1046 resolved = selSetResolved(host,args,session)
1047 if 'Sel entry' in resolved:
1048 successlist.append(d['selNum'])
1049 else:
1050 failedlist.append(d['selNum'])
1051 output = ""
1052 successlist.sort()
1053 failedlist.sort()
1054 if len(successlist)>0:
1055 output = "Successfully resolved: " +', '.join(successlist) +"\n"
1056 if len(failedlist)>0:
1057 output += "Failed to resolve: " + ', '.join(failedlist) + "\n"
1058 return output
1059
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001060def chassisPower(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001061 """
1062 called by the chassis function. Controls the power state of the chassis, or gets the status
1063
1064 @param host: string, the hostname or IP address of the bmc
1065 @param args: contains additional arguments used by the fru sub command
1066 @param session: the active session to use
1067 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1068 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001069 if(args.powcmd == 'on'):
Justin Thaler22b1bb52018-03-15 13:31:32 -05001070 if checkFWactivation(host, args, session):
1071 return ("Chassis Power control disabled during firmware activation")
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001072 print("Attempting to Power on...:")
1073 url="https://"+host+"/xyz/openbmc_project/state/host0/attr/RequestedHostTransition"
1074 httpHeader = {'Content-Type':'application/json',}
1075 data = '{"data":"xyz.openbmc_project.State.Host.Transition.On"}'
Justin Thalere412dc22018-01-12 16:28:24 -06001076 try:
1077 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1078 except(requests.exceptions.Timeout):
1079 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001080 return res.text
Justin Thalere412dc22018-01-12 16:28:24 -06001081 elif(args.powcmd == 'softoff'):
Justin Thaler22b1bb52018-03-15 13:31:32 -05001082 if checkFWactivation(host, args, session):
1083 return ("Chassis Power control disabled during firmware activation")
Justin Thalere412dc22018-01-12 16:28:24 -06001084 print("Attempting to Power off gracefully...:")
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001085 url="https://"+host+"/xyz/openbmc_project/state/host0/attr/RequestedHostTransition"
1086 httpHeader = {'Content-Type':'application/json'}
1087 data = '{"data":"xyz.openbmc_project.State.Host.Transition.Off"}'
Justin Thalere412dc22018-01-12 16:28:24 -06001088 try:
1089 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1090 except(requests.exceptions.Timeout):
1091 return(connectionErrHandler(args.json, "Timeout", None))
1092 return res.text
1093 elif(args.powcmd == 'hardoff'):
Justin Thaler22b1bb52018-03-15 13:31:32 -05001094 if checkFWactivation(host, args, session):
1095 return ("Chassis Power control disabled during firmware activation")
Justin Thalere412dc22018-01-12 16:28:24 -06001096 print("Attempting to Power off immediately...:")
1097 url="https://"+host+"/xyz/openbmc_project/state/chassis0/attr/RequestedPowerTransition"
1098 httpHeader = {'Content-Type':'application/json'}
1099 data = '{"data":"xyz.openbmc_project.State.Chassis.Transition.Off"}'
1100 try:
1101 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1102 except(requests.exceptions.Timeout):
1103 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001104 return res.text
1105 elif(args.powcmd == 'status'):
1106 url="https://"+host+"/xyz/openbmc_project/state/chassis0/attr/CurrentPowerState"
1107 httpHeader = {'Content-Type':'application/json'}
1108# print(url)
Justin Thalere412dc22018-01-12 16:28:24 -06001109 try:
1110 res = session.get(url, headers=httpHeader, verify=False, timeout=30)
1111 except(requests.exceptions.Timeout):
1112 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001113 chassisState = json.loads(res.text)['data'].split('.')[-1]
1114 url="https://"+host+"/xyz/openbmc_project/state/host0/attr/CurrentHostState"
Justin Thalere412dc22018-01-12 16:28:24 -06001115 try:
1116 res = session.get(url, headers=httpHeader, verify=False, timeout=30)
1117 except(requests.exceptions.Timeout):
1118 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001119 hostState = json.loads(res.text)['data'].split('.')[-1]
1120 url="https://"+host+"/xyz/openbmc_project/state/bmc0/attr/CurrentBMCState"
Justin Thalere412dc22018-01-12 16:28:24 -06001121 try:
1122 res = session.get(url, headers=httpHeader, verify=False, timeout=30)
1123 except(requests.exceptions.Timeout):
1124 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001125 bmcState = json.loads(res.text)['data'].split('.')[-1]
1126 if(args.json):
1127 outDict = {"Chassis Power State" : chassisState, "Host Power State" : hostState, "BMC Power State":bmcState}
1128 return json.dumps(outDict, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False)
1129 else:
1130 return "Chassis Power State: " +chassisState + "\nHost Power State: " + hostState + "\nBMC Power State: " + bmcState
1131 else:
1132 return "Invalid chassis power command"
1133
Justin Thalere412dc22018-01-12 16:28:24 -06001134
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001135def chassisIdent(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001136 """
1137 called by the chassis function. Controls the identify led of the chassis. Sets or gets the state
1138
1139 @param host: string, the hostname or IP address of the bmc
1140 @param args: contains additional arguments used by the fru sub command
1141 @param session: the active session to use
1142 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1143 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001144 if(args.identcmd == 'on'):
1145 print("Attempting to turn identify light on...:")
1146 url="https://"+host+"/xyz/openbmc_project/led/groups/enclosure_identify/attr/Asserted"
1147 httpHeader = {'Content-Type':'application/json',}
1148 data = '{"data":true}'
Justin Thalere412dc22018-01-12 16:28:24 -06001149 try:
1150 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1151 except(requests.exceptions.Timeout):
1152 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001153 return res.text
1154 elif(args.identcmd == 'off'):
1155 print("Attempting to turn identify light off...:")
1156 url="https://"+host+"/xyz/openbmc_project/led/groups/enclosure_identify/attr/Asserted"
1157 httpHeader = {'Content-Type':'application/json'}
1158 data = '{"data":false}'
Justin Thalere412dc22018-01-12 16:28:24 -06001159 try:
1160 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1161 except(requests.exceptions.Timeout):
1162 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001163 return res.text
1164 elif(args.identcmd == 'status'):
1165 url="https://"+host+"/xyz/openbmc_project/led/groups/enclosure_identify"
1166 httpHeader = {'Content-Type':'application/json'}
Justin Thalere412dc22018-01-12 16:28:24 -06001167 try:
1168 res = session.get(url, headers=httpHeader, verify=False, timeout=30)
1169 except(requests.exceptions.Timeout):
1170 return(connectionErrHandler(args.json, "Timeout", None))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001171 status = json.loads(res.text)['data']
1172 if(args.json):
1173 return status
1174 else:
1175 if status['Asserted'] == 0:
1176 return "Identify light is off"
1177 else:
1178 return "Identify light is blinking"
1179 else:
1180 return "Invalid chassis identify command"
1181
Justin Thalere412dc22018-01-12 16:28:24 -06001182
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001183def chassis(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001184 """
1185 controls the different chassis commands
1186
1187 @param host: string, the hostname or IP address of the bmc
1188 @param args: contains additional arguments used by the fru sub command
1189 @param session: the active session to use
1190 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1191 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001192 if(hasattr(args, 'powcmd')):
1193 result = chassisPower(host,args,session)
1194 elif(hasattr(args, 'identcmd')):
1195 result = chassisIdent(host, args, session)
1196 else:
Justin Thaler22b1bb52018-03-15 13:31:32 -05001197 return "This feature is not yet implemented"
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001198 return result
Justin Thalere412dc22018-01-12 16:28:24 -06001199
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001200def bmcDumpRetrieve(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001201 """
1202 Downloads a dump file from the bmc
1203
1204 @param host: string, the hostname or IP address of the bmc
1205 @param args: contains additional arguments used by the collectServiceData sub command
1206 @param session: the active session to use
1207 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1208 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001209 httpHeader = {'Content-Type':'application/json'}
1210 dumpNum = args.dumpNum
1211 if (args.dumpSaveLoc is not None):
1212 saveLoc = args.dumpSaveLoc
1213 else:
1214 saveLoc = '/tmp'
1215 url ='https://'+host+'/download/dump/' + str(dumpNum)
1216 try:
Justin Thalere412dc22018-01-12 16:28:24 -06001217 r = session.get(url, headers=httpHeader, stream=True, verify=False, timeout=30)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001218 if (args.dumpSaveLoc is not None):
1219 if os.path.exists(saveLoc):
1220 if saveLoc[-1] != os.path.sep:
1221 saveLoc = saveLoc + os.path.sep
1222 filename = saveLoc + host+'-dump' + str(dumpNum) + '.tar.xz'
1223
1224 else:
1225 return 'Invalid save location specified'
1226 else:
1227 filename = '/tmp/' + host+'-dump' + str(dumpNum) + '.tar.xz'
1228
1229 with open(filename, 'wb') as f:
1230 for chunk in r.iter_content(chunk_size =1024):
1231 if chunk:
1232 f.write(chunk)
1233 return 'Saved as ' + filename
1234
1235 except(requests.exceptions.Timeout):
1236 return connectionErrHandler(args.json, "Timeout", None)
Justin Thalere412dc22018-01-12 16:28:24 -06001237
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001238 except(requests.exceptions.ConnectionError) as err:
1239 return connectionErrHandler(args.json, "ConnectionError", err)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001240
Justin Thalere412dc22018-01-12 16:28:24 -06001241def bmcDumpList(host, args, session):
1242 """
1243 Lists the number of dump files on the bmc
1244
1245 @param host: string, the hostname or IP address of the bmc
1246 @param args: contains additional arguments used by the collectServiceData sub command
1247 @param session: the active session to use
1248 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1249 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001250 httpHeader = {'Content-Type':'application/json'}
1251 url ='https://'+host+'/xyz/openbmc_project/dump/list'
1252 try:
1253 r = session.get(url, headers=httpHeader, verify=False, timeout=20)
1254 dumpList = json.loads(r.text)
1255 return r.text
1256 except(requests.exceptions.Timeout):
1257 return connectionErrHandler(args.json, "Timeout", None)
Justin Thalere412dc22018-01-12 16:28:24 -06001258
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001259 except(requests.exceptions.ConnectionError) as err:
Justin Thalere412dc22018-01-12 16:28:24 -06001260 return connectionErrHandler(args.json, "ConnectionError", err)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001261
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001262def bmcDumpDelete(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001263 """
1264 Deletes BMC dump files from the bmc
1265
1266 @param host: string, the hostname or IP address of the bmc
1267 @param args: contains additional arguments used by the collectServiceData sub command
1268 @param session: the active session to use
1269 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1270 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001271 httpHeader = {'Content-Type':'application/json'}
1272 dumpList = []
1273 successList = []
1274 failedList = []
1275 if args.dumpNum is not None:
1276 if isinstance(args.dumpNum, list):
1277 dumpList = args.dumpNum
1278 else:
1279 dumpList.append(args.dumpNum)
1280 for dumpNum in dumpList:
1281 url ='https://'+host+'/xyz/openbmc_project/dump/entry/'+str(dumpNum)+'/action/Delete'
1282 try:
1283 r = session.post(url, headers=httpHeader, json = {"data": []}, verify=False, timeout=30)
1284 if r.status_code == 200:
1285 successList.append(str(dumpNum))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001286 else:
1287 failedList.append(str(dumpNum))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001288 except(requests.exceptions.Timeout):
1289 return connectionErrHandler(args.json, "Timeout", None)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001290 except(requests.exceptions.ConnectionError) as err:
1291 return connectionErrHandler(args.json, "ConnectionError", err)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001292 output = "Successfully deleted dumps: " + ', '.join(successList)
1293 if(len(failedList)>0):
1294 output+= '\nFailed to delete dumps: ' + ', '.join(failedList)
1295 return output
1296 else:
1297 return 'You must specify an entry number to delete'
1298
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001299def bmcDumpDeleteAll(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001300 """
1301 Deletes All BMC dump files from the bmc
1302
1303 @param host: string, the hostname or IP address of the bmc
1304 @param args: contains additional arguments used by the collectServiceData sub command
1305 @param session: the active session to use
1306 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1307 """
1308 dumpResp = bmcDumpList(host, args, session)
1309 if 'FQPSPIN0000M' in dumpResp or 'FQPSPIN0001M'in dumpResp:
1310 return dumpResp
1311 dumpList = json.loads(dumpResp)['data']
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001312 d = vars(args)
1313 dumpNums = []
1314 for dump in dumpList:
1315 if '/xyz/openbmc_project/dump/internal/manager' not in dump:
1316 dumpNums.append(int(dump.strip().split('/')[-1]))
1317 d['dumpNum'] = dumpNums
1318
1319 return bmcDumpDelete(host, args, session)
1320
Justin Thalere412dc22018-01-12 16:28:24 -06001321
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001322def bmcDumpCreate(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001323 """
1324 Creates a bmc dump file
1325
1326 @param host: string, the hostname or IP address of the bmc
1327 @param args: contains additional arguments used by the collectServiceData sub command
1328 @param session: the active session to use
1329 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1330 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001331 httpHeader = {'Content-Type':'application/json'}
1332 url = 'https://'+host+'/xyz/openbmc_project/dump/action/CreateDump'
1333 try:
1334 r = session.post(url, headers=httpHeader, json = {"data": []}, verify=False, timeout=30)
1335 if('"message": "200 OK"' in r.text and not args.json):
1336 return ('Dump successfully created')
1337 else:
1338 return ('Failed to create dump')
1339 except(requests.exceptions.Timeout):
1340 return connectionErrHandler(args.json, "Timeout", None)
1341 except(requests.exceptions.ConnectionError) as err:
1342 return connectionErrHandler(args.json, "ConnectionError", err)
1343
1344
1345
Justin Thalere412dc22018-01-12 16:28:24 -06001346
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001347def collectServiceData(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001348 """
1349 Collects all data needed for service from the BMC
1350
1351 @param host: string, the hostname or IP address of the bmc
1352 @param args: contains additional arguments used by the collectServiceData sub command
1353 @param session: the active session to use
1354 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1355 """
Justin Thaler22b1bb52018-03-15 13:31:32 -05001356
1357 global toolVersion
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001358 #create a bmc dump
1359 dumpcount = len(json.loads(bmcDumpList(host, args, session))['data'])
1360 try:
1361 dumpcreated = bmcDumpCreate(host, args, session)
1362 except Exception as e:
1363 print('failed to create a bmc dump')
1364
1365
1366 #Collect Inventory
1367 try:
1368 args.silent = True
1369 myDir = '/tmp/' + host + "--" + datetime.datetime.now().strftime("%Y-%m-%d_%H.%M.%S")
1370 os.makedirs(myDir)
1371 filelist = []
1372 frulist = fruPrint(host, args, session)
1373 with open(myDir +'/inventory.txt', 'w') as f:
1374 f.write(frulist)
1375 print("Inventory collected and stored in " + myDir + "/inventory.txt")
1376 filelist.append(myDir+'/inventory.txt')
1377 except Exception as e:
1378 print("Failed to collect inventory")
1379
1380 #Read all the sensor and OCC status
1381 try:
1382 sensorReadings = sensor(host, args, session)
1383 with open(myDir +'/sensorReadings.txt', 'w') as f:
1384 f.write(sensorReadings)
1385 print("Sensor readings collected and stored in " +myDir + "/sensorReadings.txt")
1386 filelist.append(myDir+'/sensorReadings.txt')
1387 except Exception as e:
1388 print("Failed to collect sensor readings")
1389
1390 #Collect all of the LEDs status
1391 try:
1392 url="https://"+host+"/xyz/openbmc_project/led/enumerate"
1393 httpHeader = {'Content-Type':'application/json'}
1394 leds = session.get(url, headers=httpHeader, verify=False, timeout=20)
1395 with open(myDir +'/ledStatus.txt', 'w') as f:
1396 f.write(leds.text)
1397 print("System LED status collected and stored in "+myDir +"/ledStatus.txt")
1398 filelist.append(myDir+'/ledStatus.txt')
1399 except Exception as e:
1400 print("Failed to collect LED status")
1401
1402 #Collect the bmc logs
1403 try:
1404 sels = selPrint(host,args,session)
1405 with open(myDir +'/SELshortlist.txt', 'w') as f:
1406 f.write(str(sels))
1407 print("sel short list collected and stored in "+myDir +"/SELshortlist.txt")
1408 filelist.append(myDir+'/SELshortlist.txt')
1409 time.sleep(2)
1410
1411 d = vars(args)
1412 d['json'] = True
1413 d['fullSel'] = True
1414 parsedfullsels = json.loads(selPrint(host, args, session))
1415 d['fullEsel'] = True
1416 sortedSELs = sortSELs(parsedfullsels)
1417 with open(myDir +'/parsedSELs.txt', 'w') as f:
1418 for log in sortedSELs[0]:
1419 esel = ""
1420 parsedfullsels[sortedSELs[1][str(log)]]['timestamp'] = datetime.datetime.fromtimestamp(int(parsedfullsels[sortedSELs[1][str(log)]]['timestamp']/1000)).strftime("%Y-%m-%d %H:%M:%S")
1421 if ('raweSEL' in parsedfullsels[sortedSELs[1][str(log)]] and args.devdebug):
1422 esel = parsedfullsels[sortedSELs[1][str(log)]]['raweSEL']
1423 del parsedfullsels[sortedSELs[1][str(log)]]['raweSEL']
1424 f.write(json.dumps(parsedfullsels[sortedSELs[1][str(log)]],sort_keys=True, indent=4, separators=(',', ': ')))
1425 if(args.devdebug and esel != ""):
1426 f.write(parseESEL(args, esel))
1427 print("fully parsed sels collected and stored in "+myDir +"/parsedSELs.txt")
1428 filelist.append(myDir+'/parsedSELs.txt')
1429 except Exception as e:
1430 print("Failed to collect system event logs")
1431 print(e)
1432
1433 #collect RAW bmc enumeration
1434 try:
1435 url="https://"+host+"/xyz/openbmc_project/enumerate"
1436 print("Attempting to get a full BMC enumeration")
1437 fullDump = session.get(url, headers=httpHeader, verify=False, timeout=120)
1438 with open(myDir +'/bmcFullRaw.txt', 'w') as f:
1439 f.write(fullDump.text)
1440 print("RAW BMC data collected and saved into "+myDir +"/bmcFullRaw.txt")
1441 filelist.append(myDir+'/bmcFullRaw.txt')
1442 except Exception as e:
1443 print("Failed to collect bmc full enumeration")
1444
1445 #collect the dump files
1446 waitingForNewDump = True
1447 count = 0;
1448 while(waitingForNewDump):
1449 dumpList = json.loads(bmcDumpList(host, args, session))['data']
1450 if len(dumpList) > dumpcount:
1451 waitingForNewDump = False
1452 break;
1453 elif(count>30):
1454 print("Timed out waiting for bmc to make a new dump file. Dump space may be full.")
1455 break;
1456 else:
1457 time.sleep(2)
1458 count += 1
1459 try:
1460 print('Collecting bmc dump files')
1461 d['dumpSaveLoc'] = myDir
1462 dumpList = json.loads(bmcDumpList(host, args, session))['data']
1463 for dump in dumpList:
1464 if '/xyz/openbmc_project/dump/internal/manager' not in dump:
1465 d['dumpNum'] = int(dump.strip().split('/')[-1])
1466 print('retrieving dump file ' + str(d['dumpNum']))
1467 filename = bmcDumpRetrieve(host, args, session).split('Saved as ')[-1]
1468 filelist.append(filename)
1469 time.sleep(2)
1470 except Exception as e:
1471 print("Failed to collect bmc dump files")
1472 print(e)
1473
1474 #create the zip file
1475 try:
Justin Thaler22b1bb52018-03-15 13:31:32 -05001476 filename = myDir.split('/tmp/')[-1] + "_" + toolVersion + '_openbmc.zip'
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001477 zf = zipfile.ZipFile(myDir+'/' + filename, 'w')
1478 for myfile in filelist:
1479 zf.write(myfile, os.path.basename(myfile))
1480 zf.close()
1481 except Exception as e:
1482 print("Failed to create zip file with collected information")
1483 return "data collection complete"
1484
Justin Thalere412dc22018-01-12 16:28:24 -06001485
1486def healthCheck(host, args, session):
1487 """
1488 runs a health check on the platform
1489
1490 @param host: string, the hostname or IP address of the bmc
1491 @param args: contains additional arguments used by the bmc sub command
1492 @param session: the active session to use
1493 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1494 """
1495 #check fru status and get as json to easily work through
1496 d = vars(args)
1497 useJson = d['json']
1498 d['json'] = True
1499 d['verbose']= False
1500
1501 frus = json.loads(fruStatus(host, args, session))
1502
1503 hwStatus= "OK"
1504 performanceStatus = "OK"
1505 for key in frus:
1506 if frus[key]["Functional"] == "No" and frus[key]["Present"] == "Yes":
1507 hwStatus= "Degraded"
1508 if("power_supply" in key):
1509 gpuCount =0;
1510 frulist = json.loads(fruList(host, args, session))
1511 for comp in frulist:
1512 if "gv100card" in comp:
1513 gpuCount +=1
1514 if gpuCount > 4:
1515 hwStatus = "Critical"
1516 performanceStatus="Degraded"
1517 break;
1518 elif("fan" in key):
1519 hwStatus = "Degraded"
1520 else:
1521 performanceStatus = "Degraded"
1522 if useJson:
1523 output = {"Hardware Status": hwStatus, "Performance": performanceStatus}
1524 output = json.dumps(output, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False)
1525 else:
1526 output = ("Hardware Status: " + hwStatus +
1527 "\nPerformance: " +performanceStatus )
1528
1529
1530 #SW407886: Clear the duplicate entries
1531 #collect the dups
1532 d['devdebug'] = False
1533 sels = json.loads(selPrint(host, args, session))
1534 logNums2Clr = []
1535 oldestLogNum={"logNum": "bogus" ,"key" : ""}
1536 count = 0
1537 if sels['numAlerts'] > 0:
1538 for key in sels:
1539 if "numAlerts" in key:
1540 continue
1541 try:
1542 if "slave@00:00/00:00:00:06/sbefifo1-dev0/occ1-dev0" in sels[key]['Message']:
1543 count += 1
1544 if count > 1:
1545 #preserve first occurrence
1546 if sels[key]['timestamp'] < sels[oldestLogNum['key']]['timestamp']:
1547 oldestLogNum['key']=key
1548 oldestLogNum['logNum'] = sels[key]['logNum']
1549 else:
1550 oldestLogNum['key']=key
1551 oldestLogNum['logNum'] = sels[key]['logNum']
1552 logNums2Clr.append(sels[key]['logNum'])
1553 except KeyError:
1554 continue
1555 if(count >0):
1556 logNums2Clr.remove(oldestLogNum['logNum'])
1557 #delete the dups
1558 if count >1:
1559 httpHeader = {'Content-Type':'application/json'}
1560 data = "{\"data\": [] }"
1561 for logNum in logNums2Clr:
1562 url = "https://"+ host+ "/xyz/openbmc_project/logging/entry/"+logNum+"/action/Delete"
1563 try:
1564 session.post(url, headers=httpHeader, data=data, verify=False, timeout=30)
1565 except(requests.exceptions.Timeout):
1566 deleteFailed = True
1567 except(requests.exceptions.ConnectionError) as err:
1568 deleteFailed = True
1569 #End of defect resolve code
1570 d['json'] = useJson
1571 return output
1572
1573
1574
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001575def bmc(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001576 """
1577 handles various bmc level commands, currently bmc rebooting
1578
1579 @param host: string, the hostname or IP address of the bmc
1580 @param args: contains additional arguments used by the bmc sub command
1581 @param session: the active session to use
1582 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1583 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001584 if(args.type is not None):
1585 return bmcReset(host, args, session)
Justin Thalere412dc22018-01-12 16:28:24 -06001586 if(args.info):
1587 return "Not implemented at this time"
1588
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001589
Justin Thalere412dc22018-01-12 16:28:24 -06001590
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001591def bmcReset(host, args, session):
Justin Thalere412dc22018-01-12 16:28:24 -06001592 """
1593 controls resetting the bmc. warm reset reboots the bmc, cold reset removes the configuration and reboots.
1594
1595 @param host: string, the hostname or IP address of the bmc
1596 @param args: contains additional arguments used by the bmcReset sub command
1597 @param session: the active session to use
1598 @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1599 """
Justin Thaler22b1bb52018-03-15 13:31:32 -05001600 if checkFWactivation(host, args, session):
1601 return ("BMC reset control disabled during firmware activation")
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001602 if(args.type == "warm"):
1603 print("\nAttempting to reboot the BMC...:")
1604 url="https://"+host+"/xyz/openbmc_project/state/bmc0/attr/RequestedBMCTransition"
1605 httpHeader = {'Content-Type':'application/json'}
Justin Thalere412dc22018-01-12 16:28:24 -06001606 data = '{"data":"xyz.openbmc_project.State.BMC.Transition.Reboot"}'
1607 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=20)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001608 return res.text
1609 elif(args.type =="cold"):
Justin Thalere412dc22018-01-12 16:28:24 -06001610 print("\nAttempting to reboot the BMC...:")
1611 url="https://"+host+"/xyz/openbmc_project/state/bmc0/attr/RequestedBMCTransition"
1612 httpHeader = {'Content-Type':'application/json'}
1613 data = '{"data":"xyz.openbmc_project.State.BMC.Transition.Reboot"}'
1614 res = session.put(url, headers=httpHeader, data=data, verify=False, timeout=20)
1615 return res.text
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001616 else:
1617 return "invalid command"
Justin Thalere412dc22018-01-12 16:28:24 -06001618
1619def gardClear(host, args, session):
1620 """
1621 clears the gard records from the bmc
1622
1623 @param host: string, the hostname or IP address of the bmc
1624 @param args: contains additional arguments used by the gardClear sub command
1625 @param session: the active session to use
1626 """
1627 url="https://"+host+"/org/open_power/control/gard/action/Reset"
1628 httpHeader = {'Content-Type':'application/json'}
1629 data = '{"data":[]}'
1630 try:
1631
1632 res = session.post(url, headers=httpHeader, data=data, verify=False, timeout=30)
1633 if res.status_code == 404:
1634 return "Command not supported by this firmware version"
1635 else:
1636 return res.text
1637 except(requests.exceptions.Timeout):
1638 return connectionErrHandler(args.json, "Timeout", None)
1639 except(requests.exceptions.ConnectionError) as err:
1640 return connectionErrHandler(args.json, "ConnectionError", err)
1641
1642def activateFWImage(host, args, session):
1643 """
1644 activates a firmware image on the bmc
1645
1646 @param host: string, the hostname or IP address of the bmc
1647 @param args: contains additional arguments used by the fwflash sub command
1648 @param session: the active session to use
1649 @param fwID: the unique ID of the fw image to activate
1650 """
1651 fwID = args.imageID
1652
1653 #determine the existing versions
1654 httpHeader = {'Content-Type':'application/json'}
1655 url="https://"+host+"/xyz/openbmc_project/software/enumerate"
1656 try:
1657 resp = session.get(url, headers=httpHeader, verify=False, timeout=30)
1658 except(requests.exceptions.Timeout):
1659 return connectionErrHandler(args.json, "Timeout", None)
1660 except(requests.exceptions.ConnectionError) as err:
1661 return connectionErrHandler(args.json, "ConnectionError", err)
1662 existingSoftware = json.loads(resp.text)['data']
1663 altVersionID = ''
1664 versionType = ''
1665 imageKey = '/xyz/openbmc_project/software/'+fwID
1666 if imageKey in existingSoftware:
1667 versionType = existingSoftware[imageKey]['Purpose']
1668 for key in existingSoftware:
1669 if imageKey == key:
1670 continue
1671 if 'Purpose' in existingSoftware[key]:
1672 if versionType == existingSoftware[key]['Purpose']:
1673 altVersionID = key.split('/')[-1]
1674
1675
1676
1677
1678 url="https://"+host+"/xyz/openbmc_project/software/"+ fwID + "/attr/Priority"
1679 url1="https://"+host+"/xyz/openbmc_project/software/"+ altVersionID + "/attr/Priority"
1680 data = "{\"data\": 0}"
1681 data1 = "{\"data\": 1 }"
1682 try:
1683 resp = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1684 resp1 = session.put(url1, headers=httpHeader, data=data1, verify=False, timeout=30)
1685 except(requests.exceptions.Timeout):
1686 return connectionErrHandler(args.json, "Timeout", None)
1687 except(requests.exceptions.ConnectionError) as err:
1688 return connectionErrHandler(args.json, "ConnectionError", err)
1689 if(not args.json):
1690 if resp.status_code == 200 and resp1.status_code == 200:
Justin Thaler22b1bb52018-03-15 13:31:32 -05001691 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 -06001692 else:
1693 return "Firmware activation failed."
1694 else:
1695 return resp.text + resp1.text
Justin Thaler22b1bb52018-03-15 13:31:32 -05001696
1697def activateStatus(host, args, session):
1698 if checkFWactivation(host, args, session):
1699 return("Firmware is currently being activated. Do not reboot the BMC or start the Host OS")
1700 else:
1701 return("No firmware activations are pending")
1702
1703def extractFWimage(path, imageType):
1704 """
1705 extracts the bmc image and returns information about the package
1706
1707 @param path: the path and file name of the firmware image
1708 @param imageType: The type of image the user is trying to flash. Host or BMC
1709 @return: the image id associated with the package. returns an empty string on error.
1710 """
1711 f = tempfile.TemporaryFile()
1712 tmpDir = tempfile.gettempdir()
1713 newImageID = ""
1714 if os.path.exists(path):
1715 try:
1716 imageFile = tarfile.open(path,'r')
1717 contents = imageFile.getmembers()
1718 for tf in contents:
1719 if 'MANIFEST' in tf.name:
1720 imageFile.extract(tf.name, path=tmpDir)
1721 with open(tempfile.gettempdir() +os.sep+ tf.name, 'r') as imageInfo:
1722 for line in imageInfo:
1723 if 'purpose' in line:
1724 purpose = line.split('=')[1]
1725 if imageType not in purpose.split('.')[-1]:
1726 print('The specified image is not for ' + imageType)
1727 print('Please try again with the image for ' + imageType)
1728 return ""
1729 if 'version' == line.split('=')[0]:
1730 version = line.split('=')[1].strip().encode('utf-8')
1731 m = hashlib.sha512()
1732 m.update(version)
1733 newImageID = m.hexdigest()[:8]
1734 break
1735 try:
1736 os.remove(tempfile.gettempdir() +os.sep+ tf.name)
1737 except OSError:
1738 pass
1739 return newImageID
1740 except tarfile.ExtractError as e:
1741 print('Unable to extract information from the firmware file.')
1742 print('Ensure you have write access to the directory: ' + tmpDir)
1743 return newImageID
1744 except tarfile.TarError as e:
1745 print('This is not a valid firmware file.')
1746 return newImageID
1747 print("This is not a valid firmware file.")
1748 return newImageID
1749 else:
1750 print('The filename and path provided are not valid.')
1751 return newImageID
1752
1753def getAllFWImageIDs(fwInvDict):
1754 """
1755 gets a list of all the firmware image IDs
1756
1757 @param fwInvDict: the dictionary to search for FW image IDs
1758 @return: list containing string representation of the found image ids
1759 """
1760 idList = []
1761 for key in fwInvDict:
1762 if 'Version' in fwInvDict[key]:
1763 idList.append(key.split('/')[-1])
1764 return idList
1765
Justin Thalere412dc22018-01-12 16:28:24 -06001766def fwFlash(host, args, session):
1767 """
1768 updates the bmc firmware and pnor firmware
1769
1770 @param host: string, the hostname or IP address of the bmc
1771 @param args: contains additional arguments used by the fwflash sub command
1772 @param session: the active session to use
1773 """
Justin Thaler22b1bb52018-03-15 13:31:32 -05001774 d = vars(args)
Justin Thalere412dc22018-01-12 16:28:24 -06001775 if(args.type == 'bmc'):
1776 purp = 'BMC'
1777 else:
1778 purp = 'Host'
Justin Thaler22b1bb52018-03-15 13:31:32 -05001779
1780 #check power state of the machine. No concurrent FW updates allowed
1781 d['powcmd'] = 'status'
1782 powerstate = chassisPower(host, args, session)
1783 if 'Chassis Power State: On' in powerstate:
1784 return("Aborting firmware update. Host is powered on. Please turn off the host and try again.")
1785
1786 #determine the existing images on the bmc
Justin Thalere412dc22018-01-12 16:28:24 -06001787 httpHeader = {'Content-Type':'application/json'}
1788 url="https://"+host+"/xyz/openbmc_project/software/enumerate"
1789 try:
1790 resp = session.get(url, headers=httpHeader, verify=False, timeout=30)
1791 except(requests.exceptions.Timeout):
1792 return connectionErrHandler(args.json, "Timeout", None)
1793 except(requests.exceptions.ConnectionError) as err:
1794 return connectionErrHandler(args.json, "ConnectionError", err)
1795 oldsoftware = json.loads(resp.text)['data']
1796
Justin Thaler22b1bb52018-03-15 13:31:32 -05001797 #Extract the tar and get information from the manifest file
1798 newversionID = extractFWimage(args.fileloc, purp)
1799 if newversionID == "":
1800 return "Unable to verify FW image."
1801
1802
1803 #check if the new image is already on the bmc
1804 if newversionID not in getAllFWImageIDs(oldsoftware):
1805
1806 #upload the file
1807 httpHeader = {'Content-Type':'application/octet-stream'}
1808 url="https://"+host+"/upload/image"
1809 data=open(args.fileloc,'rb').read()
1810 print("Uploading file to BMC")
Justin Thalere412dc22018-01-12 16:28:24 -06001811 try:
Justin Thaler22b1bb52018-03-15 13:31:32 -05001812 resp = session.post(url, headers=httpHeader, data=data, verify=False)
Justin Thalere412dc22018-01-12 16:28:24 -06001813 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)
Justin Thaler22b1bb52018-03-15 13:31:32 -05001817 if resp.status_code != 200:
1818 return "Failed to upload the file to the bmc"
Justin Thalere412dc22018-01-12 16:28:24 -06001819 else:
Justin Thaler22b1bb52018-03-15 13:31:32 -05001820 print("Upload complete.")
1821
1822 #verify bmc processed the image
1823 software ={}
1824 for i in range(0, 5):
1825 httpHeader = {'Content-Type':'application/json'}
1826 url="https://"+host+"/xyz/openbmc_project/software/enumerate"
1827 try:
1828 resp = session.get(url, headers=httpHeader, verify=False, timeout=30)
1829 except(requests.exceptions.Timeout):
1830 return connectionErrHandler(args.json, "Timeout", None)
1831 except(requests.exceptions.ConnectionError) as err:
1832 return connectionErrHandler(args.json, "ConnectionError", err)
1833 software = json.loads(resp.text)['data']
1834 #check if bmc is done processing the new image
1835 if (newversionID in getAllFWImageIDs(software)):
Justin Thalere412dc22018-01-12 16:28:24 -06001836 break
Justin Thaler22b1bb52018-03-15 13:31:32 -05001837 else:
1838 time.sleep(15)
1839
1840 #activate the new image
1841 print("Activating new image: "+newversionID)
1842 url="https://"+host+"/xyz/openbmc_project/software/"+ newversionID + "/attr/RequestedActivation"
1843 data = '{"data":"xyz.openbmc_project.Software.Activation.RequestedActivations.Active"}'
1844 try:
1845 resp = session.put(url, headers=httpHeader, data=data, verify=False, timeout=30)
1846 except(requests.exceptions.Timeout):
1847 return connectionErrHandler(args.json, "Timeout", None)
1848 except(requests.exceptions.ConnectionError) as err:
1849 return connectionErrHandler(args.json, "ConnectionError", err)
1850
1851 #wait for the activation to complete, timeout after ~1 hour
1852 i=0
1853 while i < 360:
1854 url="https://"+host+"/xyz/openbmc_project/software/"+ newversionID
1855 data = '{"data":"xyz.openbmc_project.Software.Activation.RequestedActivations.Active"}'
1856 try:
1857 resp = session.get(url, headers=httpHeader, verify=False, timeout=30)
1858 except(requests.exceptions.Timeout):
1859 return connectionErrHandler(args.json, "Timeout", None)
1860 except(requests.exceptions.ConnectionError) as err:
1861 return connectionErrHandler(args.json, "ConnectionError", err)
1862 fwInfo = json.loads(resp.text)['data']
1863 if 'Activating' not in fwInfo['Activation'] and 'Activating' not in fwInfo['RequestedActivation']:
1864 print('')
1865 break
1866 else:
1867 sys.stdout.write('.')
1868 sys.stdout.flush()
1869 time.sleep(10) #check every 10 seconds
1870 return "Firmware flash and activation completed. Please reboot the bmc and then boot the host OS for the changes to take effect. "
1871 else:
1872 print("This image has been found on the bmc. Activating image: " + newversionID)
1873
1874 d['imageID'] = newversionID
1875 return activateFWImage(host, args, session)
Justin Thalere412dc22018-01-12 16:28:24 -06001876
Justin Thaler22b1bb52018-03-15 13:31:32 -05001877
Justin Thalere412dc22018-01-12 16:28:24 -06001878
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001879def createCommandParser():
Justin Thalere412dc22018-01-12 16:28:24 -06001880 """
1881 creates the parser for the command line along with help for each command and subcommand
1882
1883 @return: returns the parser for the command line
1884 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001885 parser = argparse.ArgumentParser(description='Process arguments')
Justin Thalere412dc22018-01-12 16:28:24 -06001886 parser.add_argument("-H", "--host", help='A hostname or IP for the BMC')
1887 parser.add_argument("-U", "--user", help='The username to login with')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001888 group = parser.add_mutually_exclusive_group()
1889 group.add_argument("-A", "--askpw", action='store_true', help='prompt for password')
1890 group.add_argument("-P", "--PW", help='Provide the password in-line')
1891 parser.add_argument('-j', '--json', action='store_true', help='output json data only')
1892 parser.add_argument('-t', '--policyTableLoc', help='The location of the policy table to parse alerts')
1893 parser.add_argument('-c', '--CerFormat', action='store_true', help=argparse.SUPPRESS)
1894 parser.add_argument('-T', '--procTime', action='store_true', help= argparse.SUPPRESS)
Justin Thalere412dc22018-01-12 16:28:24 -06001895 parser.add_argument('-V', '--version', action='store_true', help='Display the version number of the openbmctool')
1896 subparsers = parser.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001897
1898 #fru command
1899 parser_inv = subparsers.add_parser("fru", help='Work with platform inventory')
1900 #fru print
Justin Thalere412dc22018-01-12 16:28:24 -06001901 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 -06001902 inv_print = inv_subparser.add_parser("print", help="prints out a list of all FRUs")
1903 inv_print.set_defaults(func=fruPrint)
1904 #fru list [0....n]
1905 inv_list = inv_subparser.add_parser("list", help="print out details on selected FRUs. Specifying no items will list the entire inventory")
1906 inv_list.add_argument('items', nargs='?', help="print out details on selected FRUs. Specifying no items will list the entire inventory")
1907 inv_list.set_defaults(func=fruList)
1908 #fru status
1909 inv_status = inv_subparser.add_parser("status", help="prints out the status of all FRUs")
Justin Thalere412dc22018-01-12 16:28:24 -06001910 inv_status.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001911 inv_status.set_defaults(func=fruStatus)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001912
1913 #sensors command
1914 parser_sens = subparsers.add_parser("sensors", help="Work with platform sensors")
Justin Thalere412dc22018-01-12 16:28:24 -06001915 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 -06001916 #sensor print
1917 sens_print= sens_subparser.add_parser('print', help="prints out a list of all Sensors.")
1918 sens_print.set_defaults(func=sensor)
1919 #sensor list[0...n]
1920 sens_list=sens_subparser.add_parser("list", help="Lists all Sensors in the platform. Specify a sensor for full details. ")
1921 sens_list.add_argument("sensNum", nargs='?', help="The Sensor number to get full details on" )
1922 sens_list.set_defaults(func=sensor)
1923
1924
1925 #sel command
1926 parser_sel = subparsers.add_parser("sel", help="Work with platform alerts")
Justin Thalere412dc22018-01-12 16:28:24 -06001927 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 -06001928
1929 #sel print
1930 sel_print = sel_subparser.add_parser("print", help="prints out a list of all sels in a condensed list")
1931 sel_print.add_argument('-d', '--devdebug', action='store_true', help=argparse.SUPPRESS)
1932 sel_print.add_argument('-v', '--verbose', action='store_true', help="Changes the output to being very verbose")
1933 sel_print.add_argument('-f', '--fileloc', help='Parse a file instead of the BMC output')
1934 sel_print.set_defaults(func=selPrint)
Justin Thaler22b1bb52018-03-15 13:31:32 -05001935
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001936 #sel list
1937 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")
1938 sel_list.add_argument("selNum", nargs='?', type=int, help="The SEL entry to get details on")
1939 sel_list.set_defaults(func=selList)
1940
1941 sel_get = sel_subparser.add_parser("get", help="Gets the verbose details of a specified SEL entry")
1942 sel_get.add_argument('selNum', type=int, help="the number of the SEL entry to get")
1943 sel_get.set_defaults(func=selList)
1944
1945 sel_clear = sel_subparser.add_parser("clear", help="Clears all entries from the SEL")
1946 sel_clear.set_defaults(func=selClear)
1947
1948 sel_setResolved = sel_subparser.add_parser("resolve", help="Sets the sel entry to resolved")
Justin Thalere412dc22018-01-12 16:28:24 -06001949 sel_setResolved.add_argument('-n', '--selNum', type=int, help="the number of the SEL entry to resolve")
1950 sel_ResolveAll_sub = sel_setResolved.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
1951 sel_ResolveAll = sel_ResolveAll_sub.add_parser('all', help='Resolve all SEL entries')
1952 sel_ResolveAll.set_defaults(func=selResolveAll)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001953 sel_setResolved.set_defaults(func=selSetResolved)
1954
1955 parser_chassis = subparsers.add_parser("chassis", help="Work with chassis power and status")
Justin Thalere412dc22018-01-12 16:28:24 -06001956 chas_sub = parser_chassis.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001957
1958 parser_chassis.add_argument('status', action='store_true', help='Returns the current status of the platform')
1959 parser_chassis.set_defaults(func=chassis)
1960
1961 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 -06001962 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 -06001963 parser_chasPower.set_defaults(func=chassisPower)
1964
1965 #control the chassis identify led
1966 parser_chasIdent = chas_sub.add_parser("identify", help="Control the chassis identify led")
1967 parser_chasIdent.add_argument('identcmd', choices=['on', 'off', 'status'], help='The control option for the led: on, off, blink, status')
1968 parser_chasIdent.set_defaults(func=chassisIdent)
1969
1970 #collect service data
1971 parser_servData = subparsers.add_parser("collect_service_data", help="Collect all bmc data needed for service")
1972 parser_servData.add_argument('-d', '--devdebug', action='store_true', help=argparse.SUPPRESS)
1973 parser_servData.set_defaults(func=collectServiceData)
1974
Justin Thalere412dc22018-01-12 16:28:24 -06001975 #system quick health check
1976 parser_healthChk = subparsers.add_parser("health_check", help="Work with platform sensors")
1977 parser_healthChk.set_defaults(func=healthCheck)
1978
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001979 #work with bmc dumps
1980 parser_bmcdump = subparsers.add_parser("dump", help="Work with bmc dump files")
Justin Thalere412dc22018-01-12 16:28:24 -06001981 bmcDump_sub = parser_bmcdump.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001982 dump_Create = bmcDump_sub.add_parser('create', help="Create a bmc dump")
1983 dump_Create.set_defaults(func=bmcDumpCreate)
1984
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001985 dump_list = bmcDump_sub.add_parser('list', help="list all bmc dump files")
1986 dump_list.set_defaults(func=bmcDumpList)
1987
1988 parserdumpdelete = bmcDump_sub.add_parser('delete', help="Delete bmc dump files")
1989 parserdumpdelete.add_argument("-n", "--dumpNum", nargs='*', type=int, help="The Dump entry to delete")
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001990 parserdumpdelete.set_defaults(func=bmcDumpDelete)
1991
Justin Thalere412dc22018-01-12 16:28:24 -06001992 bmcDumpDelsub = parserdumpdelete.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06001993 deleteAllDumps = bmcDumpDelsub.add_parser('all', help='Delete all bmc dump files')
1994 deleteAllDumps.set_defaults(func=bmcDumpDeleteAll)
1995
1996 parser_dumpretrieve = bmcDump_sub.add_parser('retrieve', help='Retrieve a dump file')
1997 parser_dumpretrieve.add_argument("dumpNum", type=int, help="The Dump entry to delete")
1998 parser_dumpretrieve.add_argument("-s", "--dumpSaveLoc", help="The location to save the bmc dump file")
1999 parser_dumpretrieve.set_defaults(func=bmcDumpRetrieve)
2000
Justin Thaler22b1bb52018-03-15 13:31:32 -05002001 #bmc command for reseting the bmc
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002002 parser_bmc = subparsers.add_parser('bmc', help="Work with the bmc")
Justin Thalere412dc22018-01-12 16:28:24 -06002003 bmc_sub = parser_bmc.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002004 parser_BMCReset = bmc_sub.add_parser('reset', help='Reset the bmc' )
2005 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 -06002006 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.")
2007 parser_bmc.set_defaults(func=bmc)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002008
2009 #add alias to the bmc command
2010 parser_mc = subparsers.add_parser('mc', help="Work with the management controller")
Justin Thalere412dc22018-01-12 16:28:24 -06002011 mc_sub = parser_mc.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002012 parser_MCReset = mc_sub.add_parser('reset', help='Reset the bmc' )
2013 parser_MCReset.add_argument('type', choices=['warm','cold'], help="Reboot the BMC")
2014 #parser_MCReset.add_argument('cold', action='store_true', help="Reboot the BMC and CLEAR the configuration")
2015 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 -06002016 parser_MCReset.set_defaults(func=bmcReset)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002017 parser_mc.set_defaults(func=bmc)
Justin Thalere412dc22018-01-12 16:28:24 -06002018
2019 #gard clear
2020 parser_gc = subparsers.add_parser("gardclear", help="Used to clear gard records")
2021 parser_gc.set_defaults(func=gardClear)
2022
2023 #firmware_flash
2024 parser_fw = subparsers.add_parser("firmware", help="Work with the system firmware")
2025 fwflash_subproc = parser_fw.add_subparsers(title='subcommands', description='valid firmware commands', help='sub-command help', dest='command')
2026 fwflash = fwflash_subproc.add_parser('flash', help="Flash the system firmware")
2027 fwflash.add_argument('type', choices=['bmc', 'pnor'], help="image type to flash")
2028 fwflash.add_argument('-f', '--fileloc', required=True, help="The absolute path to the firmware image")
2029 fwflash.set_defaults(func=fwFlash)
2030
Justin Thaler22b1bb52018-03-15 13:31:32 -05002031 fwActivate = fwflash_subproc.add_parser('activate', help="Activate existing image on the bmc")
Justin Thalere412dc22018-01-12 16:28:24 -06002032 fwActivate.add_argument('imageID', help="The image ID to activate from the firmware list. Ex: 63c95399")
2033 fwActivate.set_defaults(func=activateFWImage)
2034
Justin Thaler22b1bb52018-03-15 13:31:32 -05002035 fwActivateStatus = fwflash_subproc.add_parser('activation_status', help="Check Status of activations")
2036 fwActivateStatus.set_defaults(func=activateStatus)
2037
2038
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002039 return parser
2040
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002041def main(argv=None):
Justin Thalere412dc22018-01-12 16:28:24 -06002042 """
2043 main function for running the command line utility as a sub application
2044 """
Justin Thaler22b1bb52018-03-15 13:31:32 -05002045 global toolVersion
2046 toolVersion = "1.03"
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002047 parser = createCommandParser()
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002048 args = parser.parse_args(argv)
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002049
2050 totTimeStart = int(round(time.time()*1000))
2051
2052 if(sys.version_info < (3,0)):
2053 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
2054 if sys.version_info >= (3,0):
2055 requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
Justin Thalere412dc22018-01-12 16:28:24 -06002056 if (args.version):
Justin Thaler22b1bb52018-03-15 13:31:32 -05002057 print("Version: "+ toolVersion)
Justin Thalere412dc22018-01-12 16:28:24 -06002058 sys.exit(0)
2059 if (hasattr(args, 'fileloc') and args.fileloc is not None and 'print' in args.command):
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002060 mysess = None
Justin Thalere412dc22018-01-12 16:28:24 -06002061 print(selPrint('N/A', args, mysess))
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002062 else:
Justin Thalere412dc22018-01-12 16:28:24 -06002063 if(hasattr(args, 'host') and hasattr(args,'user')):
2064 if (args.askpw):
2065 pw = getpass.getpass()
2066 elif(args.PW is not None):
2067 pw = args.PW
2068 else:
2069 print("You must specify a password")
2070 sys.exit()
2071 logintimeStart = int(round(time.time()*1000))
2072 mysess = login(args.host, args.user, pw, args.json)
2073 logintimeStop = int(round(time.time()*1000))
2074
2075 commandTimeStart = int(round(time.time()*1000))
2076 output = args.func(args.host, args, mysess)
2077 commandTimeStop = int(round(time.time()*1000))
2078 print(output)
2079 if (mysess is not None):
2080 logout(args.host, args.user, pw, mysess, args.json)
2081 if(args.procTime):
2082 print("Total time: " + str(int(round(time.time()*1000))- totTimeStart))
2083 print("loginTime: " + str(logintimeStop - logintimeStart))
2084 print("command Time: " + str(commandTimeStop - commandTimeStart))
2085 else:
2086 print("usage: openbmctool.py [-h] -H HOST -U USER [-A | -P PW] [-j]\n" +
2087 "\t[-t POLICYTABLELOC] [-V]\n" +
2088 "\t{fru,sensors,sel,chassis,collect_service_data,health_check,dump,bmc,mc,gardclear,firmware}\n" +
2089 "\t...\n" +
2090 "openbmctool.py: error: the following arguments are required: -H/--host, -U/--user")
2091 sys.exit()
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002092
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002093if __name__ == '__main__':
Justin Thalere412dc22018-01-12 16:28:24 -06002094 """
2095 main function when called from the command line
2096
2097 """
Justin Thalerf9aee3e2017-12-05 12:11:09 -06002098 import sys
2099
2100 isTTY = sys.stdout.isatty()
2101 assert sys.version_info >= (2,7)
2102 main()