xref: /openbmc/openbmc-tools/openbmctool/openbmctool.py (revision d6125fd143a0d1ad524902e0fed17c8ff60f981f)
1 #!/usr/bin/python3
2 """
3  Copyright 2017,2019 IBM Corporation
4 
5    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 """
17 import argparse
18 import requests
19 import getpass
20 import json
21 import os
22 import urllib3
23 import time, datetime
24 import binascii
25 import subprocess
26 import platform
27 import zipfile
28 import tarfile
29 import tempfile
30 import hashlib
31 import re
32 import uuid
33 import ssl
34 import socket
35 import select
36 import http.client
37 from subprocess import check_output
38 import traceback
39 
40 
41 MAX_NBD_PACKET_SIZE = 131088
42 jsonHeader = {'Content-Type' : 'application/json'}
43 xAuthHeader = {}
44 baseTimeout = 60
45 serverTypeMap = {
46         'ActiveDirectory' : 'active_directory',
47         'OpenLDAP' : 'openldap'
48         }
49 
50 class NBDPipe:
51 
52     def openHTTPSocket(self,args):
53 
54         try:
55             _create_unverified_https_context = ssl._create_unverified_context
56         except AttributeError:
57             # Legacy Python that doesn't verify HTTPS certificates by default
58             pass
59         else:
60             # Handle target environment that doesn't support HTTPS verification
61             ssl._create_default_https_context = _create_unverified_https_context
62 
63 
64         token = gettoken(args)
65         self.conn = http.client.HTTPSConnection(args.host,port=443)
66 
67         uri = "/redfish/v1/Systems/system/LogServices/Dump/attachment/"+args.dumpNum
68 
69         self.conn.request("GET",uri, headers={"X-Auth-Token":token})
70 
71     def openTCPSocket(self):
72         # Create a TCP/IP socket
73         self.tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
74         # Connect the socket to the port where the server is listening
75         server_address = ('localhost', 1043)
76         self.tcp.connect(server_address)
77 
78     def waitformessage(self):
79         inputs = [self.conn.sock,self.tcp]
80         outputs = []
81         message_queues = {}
82         while True:
83             readable, writable, exceptional = select.select(
84                     inputs, outputs, inputs)
85 
86             for s in readable:
87                 if s is self.conn.sock:
88 
89                     data = self.conn.sock.recv(MAX_NBD_PACKET_SIZE)
90                     print("<<HTTP")
91                     if data:
92                         self.tcp.send(data)
93                     else:
94                         print ("BMC Closed the connection")
95                         self.conn.close()
96                         self.tcp.close()
97                         sys.exit(1)
98                 elif s is self.tcp:
99                     data = self.tcp.recv(MAX_NBD_PACKET_SIZE)
100                     print(">>TCP")
101                     if data:
102                         self.conn.sock.send(data)
103                     else:
104                         print("NBD server closed the connection")
105                         self.conn.sock.close()
106                         self.tcp.close()
107                         sys.exit(1)
108             for s in exceptional:
109                 inputs.remove(s)
110                 print("Exceptional closing the socket")
111                 s.close()
112 
113 def getsize(host,args,session):
114     url = "https://"+host+"/redfish/v1/Systems/system/LogServices/Dump/Entries/"+str(args.dumpNum)
115     try:
116         resp = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
117         if resp.status_code==200:
118             size = resp.json()['AdditionalDataSizeBytes']
119             return size
120         else:
121             return "Failed get Size"
122     except(requests.exceptions.Timeout):
123         return connectionErrHandler(args.json, "Timeout", None)
124 
125     except(requests.exceptions.ConnectionError) as err:
126         return connectionErrHandler(args.json, "ConnectionError", err)
127 
128 def gettoken(args):
129    mysess = requests.session()
130    resp = mysess.post('https://'+args.host+'/login', headers=jsonHeader,json={"data":[args.user,args.PW]},verify=False)
131    if resp.status_code == 200:
132        cookie = resp.headers['Set-Cookie']
133        match = re.search('BMCWEB-SESSION=(\w+);', cookie)
134        return match.group(1)
135 
136 
137 
138 def get_pid(name):
139     try:
140         pid = map(int, check_output(["pidof", "-s",name]))
141     except Exception:
142         pid = 0
143 
144     return pid
145 
146 def findThisProcess( process_name ):
147   ps     = subprocess.Popen("ps -eaf | grep "+process_name, shell=True, stdout=subprocess.PIPE)
148   output = ps.stdout.read()
149   ps.stdout.close()
150   ps.wait()
151   pid = get_pid(process_name)
152   return output
153 
154 def isThisProcessRunning( process_name ):
155   pid = get_pid(process_name)
156   if (pid == 0 ):
157     return False
158   else:
159     return True
160 
161 def NBDSetup(host,args,session):
162     user=os.getenv("SUDO_USER")
163     if user is None:
164         path = os.getcwd()
165         nbdServerPath = path + "/nbd-server"
166         if not os.path.exists(nbdServerPath):
167             print("Error: this program did not run as sudo!\nplease copy nbd-server to  current directory and run script again")
168             exit()
169 
170     if isThisProcessRunning('nbd-server') == True:
171         print("nbd-server already Running! killing the nbd-server")
172         os.system('killall nbd-server')
173 
174     if (args.dumpSaveLoc is not None):
175         if(os.path.exists(args.dumpSaveLoc)):
176             print("Error: File already exists.")
177             exit()
178 
179     fp= open(args.dumpSaveLoc,"w")
180     sizeInBytes = getsize(host,args,session)
181     #Round off size to mutiples of 1024
182     size = int(sizeInBytes)
183     mod = size % 1024
184     if mod :
185         roundoff = 1024 - mod
186         size = size + roundoff
187 
188     cmd = 'chmod 777 ' + args.dumpSaveLoc
189     os.system(cmd)
190 
191     #Run truncate to create file with given size
192     cmd = 'truncate -s ' + str(size) + ' '+ args.dumpSaveLoc
193     os.system(cmd)
194 
195     if user is None:
196         cmd = './nbd-server 1043 '+ args.dumpSaveLoc
197     else:
198         cmd = 'nbd-server 1043 '+ args.dumpSaveLoc
199     os.system(cmd)
200 
201 
202 def hilight(textToColor, color, bold):
203     """
204          Used to add highlights to various text for displaying in a terminal
205 
206          @param textToColor: string, the text to be colored
207          @param color: string, used to color the text red or green
208          @param bold: boolean, used to bold the textToColor
209          @return: Buffered reader containing the modified string.
210     """
211     if(sys.platform.__contains__("win")):
212         if(color == "red"):
213             os.system('color 04')
214         elif(color == "green"):
215             os.system('color 02')
216         else:
217             os.system('color') #reset to default
218         return textToColor
219     else:
220         attr = []
221         if(color == "red"):
222             attr.append('31')
223         elif(color == "green"):
224             attr.append('32')
225         else:
226             attr.append('0')
227         if bold:
228             attr.append('1')
229         else:
230             attr.append('0')
231         return '\x1b[%sm%s\x1b[0m' % (';'.join(attr),textToColor)
232 
233 def connectionErrHandler(jsonFormat, errorStr, err):
234     """
235          Error handler various connection errors to bmcs
236 
237          @param jsonFormat: boolean, used to output in json format with an error code.
238          @param errorStr: string, used to color the text red or green
239          @param err: string, the text from the exception
240     """
241     if errorStr == "Timeout":
242         if not jsonFormat:
243             return("FQPSPIN0000M: Connection timed out. Ensure you have network connectivity to the bmc")
244         else:
245             conerror = {}
246             conerror['CommonEventID'] = 'FQPSPIN0000M'
247             conerror['sensor']="N/A"
248             conerror['state']="N/A"
249             conerror['additionalDetails'] = "N/A"
250             conerror['Message']="Connection timed out. Ensure you have network connectivity to the BMC"
251             conerror['LengthyDescription'] = "While trying to establish a connection with the specified BMC, the BMC failed to respond in adequate time. Verify the BMC is functioning properly, and the network connectivity to the BMC is stable."
252             conerror['Serviceable']="Yes"
253             conerror['CallHomeCandidate']= "No"
254             conerror['Severity'] = "Critical"
255             conerror['EventType'] = "Communication Failure/Timeout"
256             conerror['VMMigrationFlag'] = "Yes"
257             conerror["AffectedSubsystem"] = "Interconnect (Networking)"
258             conerror["timestamp"] = str(int(time.time()))
259             conerror["UserAction"] = "Verify network connectivity between the two systems and the bmc is functional."
260             eventdict = {}
261             eventdict['event0'] = conerror
262             eventdict['numAlerts'] = '1'
263             errorMessageStr = errorMessageStr = json.dumps(eventdict, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False)
264             return(errorMessageStr)
265     elif errorStr == "ConnectionError":
266         if not jsonFormat:
267             return("FQPSPIN0001M: " + str(err))
268         else:
269             conerror = {}
270             conerror['CommonEventID'] = 'FQPSPIN0001M'
271             conerror['sensor']="N/A"
272             conerror['state']="N/A"
273             conerror['additionalDetails'] = str(err)
274             conerror['Message']="Connection Error. View additional details for more information"
275             conerror['LengthyDescription'] = "A connection error to the specified BMC occurred and additional details are provided. Review these details to resolve the issue."
276             conerror['Serviceable']="Yes"
277             conerror['CallHomeCandidate']= "No"
278             conerror['Severity'] = "Critical"
279             conerror['EventType'] = "Communication Failure/Timeout"
280             conerror['VMMigrationFlag'] = "Yes"
281             conerror["AffectedSubsystem"] = "Interconnect (Networking)"
282             conerror["timestamp"] = str(int(time.time()))
283             conerror["UserAction"] = "Correct the issue highlighted in additional details and try again"
284             eventdict = {}
285             eventdict['event0'] = conerror
286             eventdict['numAlerts'] = '1'
287             errorMessageStr = json.dumps(eventdict, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False)
288             return(errorMessageStr)
289 
290     else:
291         return("Unknown Error: "+ str(err))
292 
293 
294 def setColWidth(keylist, numCols, dictForOutput, colNames):
295     """
296          Sets the output width of the columns to display
297 
298          @param keylist: list, list of strings representing the keys for the dictForOutput
299          @param numcols: the total number of columns in the final output
300          @param dictForOutput: dictionary, contains the information to print to the screen
301          @param colNames: list, The strings to use for the column headings, in order of the keylist
302          @return: A list of the column widths for each respective column.
303     """
304     colWidths = []
305     for x in range(0, numCols):
306         colWidths.append(0)
307     for key in dictForOutput:
308         for x in range(0, numCols):
309             colWidths[x] = max(colWidths[x], len(str(dictForOutput[key][keylist[x]])))
310 
311     for x in range(0, numCols):
312         colWidths[x] = max(colWidths[x], len(colNames[x])) +2
313 
314     return colWidths
315 
316 def loadPolicyTable(pathToPolicyTable):
317     """
318          loads a json based policy table into a dictionary
319 
320          @param value: boolean, the value to convert
321          @return: A string of "Yes" or "No"
322     """
323     policyTable = {}
324     if(os.path.exists(pathToPolicyTable)):
325         with open(pathToPolicyTable, 'r') as stream:
326             try:
327                 contents =json.load(stream)
328                 policyTable = contents['events']
329             except Exception as err:
330                 print(err)
331     return policyTable
332 
333 
334 def boolToString(value):
335     """
336          converts a boolean value to a human readable string value
337 
338          @param value: boolean, the value to convert
339          @return: A string of "Yes" or "No"
340     """
341     if(value):
342         return "Yes"
343     else:
344         return "No"
345 
346 def stringToInt(text):
347     """
348         returns an integer if the string can be converted, otherwise returns the string
349 
350         @param text: the string to try to convert to an integer
351     """
352     if text.isdigit():
353         return int(text)
354     else:
355         return text
356 
357 def naturalSort(text):
358     """
359         provides a way to naturally sort a list
360 
361         @param text: the key to convert for sorting
362         @return list containing the broken up string parts by integers and strings
363     """
364     stringPartList = []
365     for c in re.split('(\d+)', text):
366         stringPartList.append(stringToInt(c))
367     return stringPartList
368 
369 def tableDisplay(keylist, colNames, output):
370     """
371          Logs into the BMC and creates a session
372 
373          @param keylist: list, keys for the output dictionary, ordered by colNames
374          @param colNames: Names for the Table of the columns
375          @param output: The dictionary of data to display
376          @return: Session object
377     """
378     colWidth = setColWidth(keylist, len(colNames), output, colNames)
379     row = ""
380     outputText = ""
381     for i in range(len(colNames)):
382         if (i != 0): row = row + "| "
383         row = row + colNames[i].ljust(colWidth[i])
384     outputText += row + "\n"
385 
386     output_keys = list(output.keys())
387     output_keys.sort(key=naturalSort)
388     for key in output_keys:
389         row = ""
390         for i in range(len(keylist)):
391             if (i != 0): row = row + "| "
392             row = row + output[key][keylist[i]].ljust(colWidth[i])
393         outputText += row + "\n"
394 
395     return outputText
396 
397 def checkFWactivation(host, args, session):
398     """
399         Checks the software inventory for an image that is being activated.
400 
401         @return: True if an image is being activated, false is no activations are happening
402     """
403     url="https://"+host+"/xyz/openbmc_project/software/enumerate"
404     try:
405         resp = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
406     except(requests.exceptions.Timeout):
407         print(connectionErrHandler(args.json, "Timeout", None))
408         return(True)
409     except(requests.exceptions.ConnectionError) as err:
410         print( connectionErrHandler(args.json, "ConnectionError", err))
411         return True
412     fwInfo = resp.json()['data']
413     for key in fwInfo:
414         if 'Activation' in fwInfo[key]:
415             if 'Activating' in fwInfo[key]['Activation'] or 'Activating' in fwInfo[key]['RequestedActivation']:
416                 return True
417     return False
418 
419 def login(host, username, pw,jsonFormat, allowExpiredPassword):
420     """
421          Logs into the BMC and creates a session
422 
423          @param host: string, the hostname or IP address of the bmc to log into
424          @param username: The user name for the bmc to log into
425          @param pw: The password for the BMC to log into
426          @param jsonFormat: boolean, flag that will only allow relevant data from user command to be display. This function becomes silent when set to true.
427          @param allowExpiredPassword: true, if the requested operation should
428                 be allowed when the password is expired
429          @return: Session object
430     """
431     if(jsonFormat==False):
432         print("Attempting login...")
433     mysess = requests.session()
434     try:
435         r = mysess.post('https://'+host+'/login', headers=jsonHeader, json = {"data": [username, pw]}, verify=False, timeout=baseTimeout)
436         if r.status_code == 200:
437             cookie = r.headers['Set-Cookie']
438             match = re.search('BMCWEB-SESSION=(\w+);', cookie)
439             if match:
440                 xAuthHeader['X-Auth-Token'] = match.group(1)
441                 jsonHeader.update(xAuthHeader)
442             loginMessage = json.loads(r.text)
443             if (loginMessage['status'] != "ok"):
444                 print(loginMessage["data"]["description"].encode('utf-8'))
445                 sys.exit(1)
446             if (('extendedMessage' in r.json()) and
447                 ('The password for this account must be changed' in r.json()['extendedMessage'])):
448                 if not allowExpiredPassword:
449                     print("The password for this system has expired and must be changed"+
450                             "\nsee openbmctool.py set_password --help")
451                     logout(host, username, pw, mysess, jsonFormat)
452                     sys.exit(1)
453 #         if(sys.version_info < (3,0)):
454 #             urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
455 #         if sys.version_info >= (3,0):
456 #             requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
457             return mysess
458         else:
459             return None
460     except(requests.exceptions.Timeout):
461         return (connectionErrHandler(jsonFormat, "Timeout", None))
462     except(requests.exceptions.ConnectionError) as err:
463         return (connectionErrHandler(jsonFormat, "ConnectionError", err))
464 
465 
466 def logout(host, username, pw, session, jsonFormat):
467     """
468          Logs out of the bmc and terminates the session
469 
470          @param host: string, the hostname or IP address of the bmc to log out of
471          @param username: The user name for the bmc to log out of
472          @param pw: The password for the BMC to log out of
473          @param session: the active session to use
474          @param jsonFormat: boolean, flag that will only allow relevant data from user command to be display. This function becomes silent when set to true.
475     """
476     try:
477         r = session.post('https://'+host+'/logout', headers=jsonHeader,json = {"data": [username, pw]}, verify=False, timeout=baseTimeout)
478     except(requests.exceptions.Timeout):
479         print(connectionErrHandler(jsonFormat, "Timeout", None))
480 
481     if(jsonFormat==False):
482         if r.status_code == 200:
483             print('User ' +username + ' has been logged out')
484 
485 
486 def fru(host, args, session):
487     """
488          prints out the system inventory. deprecated see fruPrint and fruList
489 
490          @param host: string, the hostname or IP address of the bmc
491          @param args: contains additional arguments used by the fru sub command
492          @param session: the active session to use
493          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
494     """
495     #url="https://"+host+"/org/openbmc/inventory/system/chassis/enumerate"
496 
497     #print(url)
498     #res = session.get(url, headers=httpHeader, verify=False)
499     #print(res.text)
500     #sample = res.text
501 
502     #inv_list = json.loads(sample)["data"]
503 
504     url="https://"+host+"/xyz/openbmc_project/inventory/enumerate"
505     try:
506         res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
507     except(requests.exceptions.Timeout):
508         return(connectionErrHandler(args.json, "Timeout", None))
509 
510     sample = res.text
511 #     inv_list.update(json.loads(sample)["data"])
512 #
513 #     #determine column width's
514 #     colNames = ["FRU Name", "FRU Type", "Has Fault", "Is FRU", "Present", "Version"]
515 #     colWidths = setColWidth(["FRU Name", "fru_type", "fault", "is_fru", "present", "version"], 6, inv_list, colNames)
516 #
517 #     print("FRU Name".ljust(colWidths[0])+ "FRU Type".ljust(colWidths[1]) + "Has Fault".ljust(colWidths[2]) + "Is FRU".ljust(colWidths[3])+
518 #           "Present".ljust(colWidths[4]) + "Version".ljust(colWidths[5]))
519 #     format the output
520 #     for key in sorted(inv_list.keys()):
521 #         keyParts = key.split("/")
522 #         isFRU = "True" if (inv_list[key]["is_fru"]==1) else "False"
523 #
524 #         fruEntry = (keyParts[len(keyParts) - 1].ljust(colWidths[0]) + inv_list[key]["fru_type"].ljust(colWidths[1])+
525 #                inv_list[key]["fault"].ljust(colWidths[2])+isFRU.ljust(colWidths[3])+
526 #                inv_list[key]["present"].ljust(colWidths[4])+ inv_list[key]["version"].ljust(colWidths[5]))
527 #         if(isTTY):
528 #             if(inv_list[key]["is_fru"] == 1):
529 #                 color = "green"
530 #                 bold = True
531 #             else:
532 #                 color='black'
533 #                 bold = False
534 #             fruEntry = hilight(fruEntry, color, bold)
535 #         print (fruEntry)
536     return sample
537 
538 def fruPrint(host, args, session):
539     """
540          prints out all inventory
541 
542          @param host: string, the hostname or IP address of the bmc
543          @param args: contains additional arguments used by the fru sub command
544          @param session: the active session to use
545          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
546          @return returns the total fru list.
547     """
548     url="https://"+host+"/xyz/openbmc_project/inventory/enumerate"
549     try:
550         res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
551     except(requests.exceptions.Timeout):
552         return(connectionErrHandler(args.json, "Timeout", None))
553 
554     frulist={}
555 #     print(res.text)
556     if res.status_code==200:
557         frulist['Hardware'] = res.json()['data']
558     else:
559         if not args.json:
560             return "Error retrieving the system inventory. BMC message: {msg}".format(msg=res.json()['message'])
561         else:
562             return res.json()
563     url="https://"+host+"/xyz/openbmc_project/software/enumerate"
564     try:
565         res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
566     except(requests.exceptions.Timeout):
567         return(connectionErrHandler(args.json, "Timeout", None))
568 #     print(res.text)
569     if res.status_code==200:
570         frulist['Software'] = res.json()['data']
571     else:
572         if not args.json():
573             return "Error retrieving the system inventory. BMC message: {msg}".format(msg=res.json()['message'])
574         else:
575             return res.json()
576     return frulist
577 
578 
579 def fruList(host, args, session):
580     """
581          prints out all inventory or only a specific specified item
582 
583          @param host: string, the hostname or IP address of the bmc
584          @param args: contains additional arguments used by the fru sub command
585          @param session: the active session to use
586          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
587     """
588     if(args.items==True):
589         return fruPrint(host, args, session)
590     else:
591         return fruPrint(host, args, session)
592 
593 
594 
595 def fruStatus(host, args, session):
596     """
597          prints out the status of all FRUs
598 
599          @param host: string, the hostname or IP address of the bmc
600          @param args: contains additional arguments used by the fru sub command
601          @param session: the active session to use
602          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
603     """
604     url="https://"+host+"/xyz/openbmc_project/inventory/enumerate"
605     try:
606         res = session.get(url, headers=jsonHeader, verify=False)
607     except(requests.exceptions.Timeout):
608         return(connectionErrHandler(args.json, "Timeout", None))
609 #     print(res.text)
610     frulist = res.json()['data']
611     frus = {}
612     for key in frulist:
613         component = frulist[key]
614         isFru = False
615         present = False
616         func = False
617         hasSels = False
618         keyPieces = key.split('/')
619         fruName = keyPieces[-1]
620         if 'core' in fruName: #associate cores to cpus
621             fruName = keyPieces[-2] + '-' + keyPieces[-1]
622         if 'Functional' in component:
623             if('Present' in component):
624                 if 'FieldReplaceable' in component:
625                     if component['FieldReplaceable'] == 1:
626                         isFru = True
627                 if "fan" in fruName:
628                     isFru = True;
629                 if component['Present'] == 1:
630                     present = True
631                 if component['Functional'] == 1:
632                     func = True
633                 if ((key + "/fault") in frulist):
634                     hasSels = True;
635                 if args.verbose:
636                     if hasSels:
637                         loglist = []
638                         faults = frulist[key+"/fault"]['endpoints']
639                         for item in faults:
640                             loglist.append(item.split('/')[-1])
641                         frus[fruName] = {"compName": fruName, "Functional": boolToString(func), "Present":boolToString(present), "IsFru": boolToString(isFru), "selList": ', '.join(loglist).strip() }
642                     else:
643                         frus[fruName] = {"compName": fruName, "Functional": boolToString(func), "Present":boolToString(present), "IsFru": boolToString(isFru), "selList": "None" }
644                 else:
645                     frus[fruName] = {"compName": fruName, "Functional": boolToString(func), "Present":boolToString(present), "IsFru": boolToString(isFru), "hasSEL": boolToString(hasSels) }
646         elif "power_supply" in fruName or "powersupply" in fruName:
647             if component['Present'] ==1:
648                 present = True
649             isFru = True
650             if ((key + "/fault") in frulist):
651                 hasSels = True;
652             if args.verbose:
653                 if hasSels:
654                     loglist = []
655                     faults = frulist[key+"/fault"]['endpoints']
656                     for item in faults:
657                         loglist.append(item.split('/')[-1])
658                     frus[fruName] = {"compName": fruName, "Functional": "No", "Present":boolToString(present), "IsFru": boolToString(isFru), "selList": ', '.join(loglist).strip() }
659                 else:
660                     frus[fruName] = {"compName": fruName, "Functional": "Yes", "Present":boolToString(present), "IsFru": boolToString(isFru), "selList": "None" }
661             else:
662                 frus[fruName] = {"compName": fruName, "Functional": boolToString(not hasSels), "Present":boolToString(present), "IsFru": boolToString(isFru), "hasSEL": boolToString(hasSels) }
663     if not args.json:
664         if not args.verbose:
665             colNames = ["Component", "Is a FRU", "Present", "Functional", "Has Logs"]
666             keylist = ["compName", "IsFru", "Present", "Functional", "hasSEL"]
667         else:
668             colNames = ["Component", "Is a FRU", "Present", "Functional", "Assoc. Log Number(s)"]
669             keylist = ["compName", "IsFru", "Present", "Functional", "selList"]
670         return tableDisplay(keylist, colNames, frus)
671     else:
672         return str(json.dumps(frus, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False))
673 
674 def sensor(host, args, session):
675     """
676          prints out all sensors
677 
678          @param host: string, the hostname or IP address of the bmc
679          @param args: contains additional arguments used by the sensor sub command
680          @param session: the active session to use
681          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
682     """
683     url="https://"+host+"/xyz/openbmc_project/sensors/enumerate"
684     try:
685         res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
686     except(requests.exceptions.Timeout):
687         return(connectionErrHandler(args.json, "Timeout", None))
688 
689     #Get OCC status
690     url="https://"+host+"/org/open_power/control/enumerate"
691     try:
692         occres = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
693     except(requests.exceptions.Timeout):
694         return(connectionErrHandler(args.json, "Timeout", None))
695     if not args.json:
696         colNames = ['sensor', 'type', 'units', 'value', 'target']
697         sensors = res.json()["data"]
698         output = {}
699         for key in sensors:
700             senDict = {}
701             keyparts = key.split("/")
702 
703             # Associations like the following also show up here:
704             # /xyz/openbmc_project/sensors/<type>/<name>/<assoc-name>
705             # Skip them.
706             # Note:  keyparts[0] = '' which is why there are 7 segments.
707             if len(keyparts) > 6:
708                 continue
709 
710             senDict['sensorName'] = keyparts[-1]
711             senDict['type'] = keyparts[-2]
712             try:
713                 senDict['units'] = sensors[key]['Unit'].split('.')[-1]
714             except KeyError:
715                 senDict['units'] = "N/A"
716             if('Scale' in sensors[key]):
717                 scale = 10 ** sensors[key]['Scale']
718             else:
719                 scale = 1
720             try:
721                 senDict['value'] = str(sensors[key]['Value'] * scale)
722             except KeyError:
723                 if 'value' in sensors[key]:
724                     senDict['value'] = sensors[key]['value']
725                 else:
726                     senDict['value'] = "N/A"
727             if 'Target' in sensors[key]:
728                 senDict['target'] = str(sensors[key]['Target'])
729             else:
730                 senDict['target'] = 'N/A'
731             output[senDict['sensorName']] = senDict
732 
733         occstatus = occres.json()["data"]
734         if '/org/open_power/control/occ0' in occstatus:
735             occ0 = occstatus["/org/open_power/control/occ0"]['OccActive']
736             if occ0 == 1:
737                 occ0 = 'Active'
738             else:
739                 occ0 = 'Inactive'
740             output['OCC0'] = {'sensorName':'OCC0', 'type': 'Discrete', 'units': 'N/A', 'value': occ0, 'target': 'Active'}
741             occ1 = occstatus["/org/open_power/control/occ1"]['OccActive']
742             if occ1 == 1:
743                 occ1 = 'Active'
744             else:
745                 occ1 = 'Inactive'
746             output['OCC1'] = {'sensorName':'OCC1', 'type': 'Discrete', 'units': 'N/A', 'value': occ0, 'target': 'Active'}
747         else:
748             output['OCC0'] = {'sensorName':'OCC0', 'type': 'Discrete', 'units': 'N/A', 'value': 'Inactive', 'target': 'Inactive'}
749             output['OCC1'] = {'sensorName':'OCC1', 'type': 'Discrete', 'units': 'N/A', 'value': 'Inactive', 'target': 'Inactive'}
750         keylist = ['sensorName', 'type', 'units', 'value', 'target']
751 
752         return tableDisplay(keylist, colNames, output)
753     else:
754         return res.text + occres.text
755 
756 def sel(host, args, session):
757     """
758          prints out the bmc alerts
759 
760          @param host: string, the hostname or IP address of the bmc
761          @param args: contains additional arguments used by the sel sub command
762          @param session: the active session to use
763          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
764     """
765 
766     url="https://"+host+"/xyz/openbmc_project/logging/entry/enumerate"
767     try:
768         res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
769     except(requests.exceptions.Timeout):
770         return(connectionErrHandler(args.json, "Timeout", None))
771     return res.text
772 
773 
774 def parseESEL(args, eselRAW):
775     """
776          parses the esel data and gets predetermined search terms
777 
778          @param eselRAW: string, the raw esel string from the bmc
779          @return: A dictionary containing the quick snapshot data unless args.fullEsel is listed then a full PEL log is returned
780     """
781     eselParts = {}
782     esel_bin = binascii.unhexlify(''.join(eselRAW.split()[16:]))
783     #search terms contains the search term as the key and the return dictionary key as it's value
784     searchTerms = { 'Signature Description':'signatureDescription', 'devdesc':'devdesc',
785                     'Callout type': 'calloutType', 'Procedure':'procedure', 'Sensor Type': 'sensorType'}
786     uniqueID = str(uuid.uuid4())
787     eselBinPath = tempfile.gettempdir() + os.sep + uniqueID + 'esel.bin'
788     with open(eselBinPath, 'wb') as f:
789         f.write(esel_bin)
790     errlPath = ""
791     #use the right errl file for the machine architecture
792     arch = platform.machine()
793     if(arch =='x86_64' or arch =='AMD64'):
794         if os.path.exists('/opt/ibm/ras/bin/x86_64/errl'):
795             errlPath = '/opt/ibm/ras/bin/x86_64/errl'
796         elif os.path.exists('errl/x86_64/errl'):
797             errlPath = 'errl/x86_64/errl'
798         else:
799             errlPath = 'x86_64/errl'
800     elif (platform.machine()=='ppc64le'):
801         if os.path.exists('/opt/ibm/ras/bin/ppc64le/errl'):
802             errlPath = '/opt/ibm/ras/bin/ppc64le/errl'
803         elif os.path.exists('errl/ppc64le/errl'):
804             errlPath = 'errl/ppc64le/errl'
805         else:
806             errlPath = 'ppc64le/errl'
807     else:
808         print("machine architecture not supported for parsing eSELs")
809         return eselParts
810 
811     if(os.path.exists(errlPath)):
812         output= subprocess.check_output([errlPath, '-d', '--file='+eselBinPath]).decode('utf-8')
813 #         output = proc.communicate()[0]
814         lines = output.split('\n')
815 
816         if(hasattr(args, 'fullEsel')):
817             return output
818 
819         for i in range(0, len(lines)):
820             lineParts = lines[i].split(':')
821             if(len(lineParts)>1): #ignore multi lines, output formatting lines, and other information
822                 for term in searchTerms:
823                     if(term in lineParts[0]):
824                         temp = lines[i][lines[i].find(':')+1:].strip()[:-1].strip()
825                         if lines[i+1].find(':') != -1:
826                             if (len(lines[i+1].split(':')[0][1:].strip())==0):
827                                 while(len(lines[i][:lines[i].find(':')].strip())>2):
828                                     #has multiple lines, process and update line counter
829                                     if((i+1) <= len(lines)):
830                                         i+=1
831                                     else:
832                                         i=i-1
833                                         break
834                                     #Append the content from the next line removing the pretty display characters
835                                     #Finds the first colon then starts 2 characters after, then removes all whitespace
836                                     temp = temp + lines[i][lines[i].find(':')+2:].strip()[:-1].strip()[:-1].strip()
837                         if(searchTerms[term] in eselParts):
838                             eselParts[searchTerms[term]] = eselParts[searchTerms[term]] + ", " + temp
839                         else:
840                             eselParts[searchTerms[term]] = temp
841         os.remove(eselBinPath)
842     else:
843         print("errl file cannot be found")
844 
845     return eselParts
846 
847 
848 def getESELSeverity(esel):
849     """
850         Finds the severity type in an eSEL from the User Header section.
851         @param esel - the eSEL data
852         @return severity - e.g. 'Critical'
853     """
854 
855     # everything but 1 and 2 are Critical
856     # '1': 'recovered',
857     # '2': 'predictive',
858     # '4': 'unrecoverable',
859     # '5': 'critical',
860     # '6': 'diagnostic',
861     # '7': 'symptom'
862     severities = {
863         '1': 'Informational',
864         '2': 'Warning'
865     }
866 
867     try:
868         headerPosition = esel.index('55 48') # 'UH'
869         # The severity is the last byte in the 8 byte section (a byte is '  bb')
870         severity = esel[headerPosition:headerPosition+32].split(' ')[-1]
871         type = severity[0]
872     except ValueError:
873         print("Could not find severity value in UH section in eSEL")
874         type = 'x';
875 
876     return severities.get(type, 'Critical')
877 
878 
879 def sortSELs(events):
880     """
881          sorts the sels by timestamp, then log entry number
882 
883          @param events: Dictionary containing events
884          @return: list containing a list of the ordered log entries, and dictionary of keys
885     """
886     logNumList = []
887     timestampList = []
888     eventKeyDict = {}
889     eventsWithTimestamp = {}
890     logNum2events = {}
891     for key in events:
892         if key == 'numAlerts': continue
893         if 'callout' in key: continue
894         timestamp = (events[key]['timestamp'])
895         if timestamp not in timestampList:
896             eventsWithTimestamp[timestamp] = [events[key]['logNum']]
897         else:
898             eventsWithTimestamp[timestamp].append(events[key]['logNum'])
899         #map logNumbers to the event dictionary keys
900         eventKeyDict[str(events[key]['logNum'])] = key
901 
902     timestampList = list(eventsWithTimestamp.keys())
903     timestampList.sort()
904     for ts in timestampList:
905         if len(eventsWithTimestamp[ts]) > 1:
906             tmplist = eventsWithTimestamp[ts]
907             tmplist.sort()
908             logNumList = logNumList + tmplist
909         else:
910             logNumList = logNumList + eventsWithTimestamp[ts]
911 
912     return [logNumList, eventKeyDict]
913 
914 
915 def parseAlerts(policyTable, selEntries, args):
916     """
917          parses alerts in the IBM CER format, using an IBM policy Table
918 
919          @param policyTable: dictionary, the policy table entries
920          @param selEntries: dictionary, the alerts retrieved from the bmc
921          @return: A dictionary of the parsed entries, in chronological order
922     """
923     eventDict = {}
924     eventNum =""
925     count = 0
926     esel = ""
927     eselParts = {}
928     i2cdevice= ""
929     eselSeverity = None
930 
931     'prepare and sort the event entries'
932     sels = {}
933     for key in selEntries:
934         if '/xyz/openbmc_project/logging/entry/' not in key: continue
935         if 'callout' not in key:
936             sels[key] = selEntries[key]
937             sels[key]['logNum'] = key.split('/')[-1]
938             sels[key]['timestamp'] = selEntries[key]['Timestamp']
939     sortedEntries = sortSELs(sels)
940     logNumList = sortedEntries[0]
941     eventKeyDict = sortedEntries[1]
942 
943     for logNum in logNumList:
944         key = eventKeyDict[logNum]
945         hasEsel=False
946         i2creadFail = False
947         if 'callout' in key:
948             continue
949         else:
950             messageID = str(selEntries[key]['Message'])
951             addDataPiece = selEntries[key]['AdditionalData']
952             calloutIndex = 0
953             calloutFound = False
954             for i in range(len(addDataPiece)):
955                 if("CALLOUT_INVENTORY_PATH" in addDataPiece[i]):
956                     calloutIndex = i
957                     calloutFound = True
958                     fruCallout = str(addDataPiece[calloutIndex]).split('=')[1]
959                 if("CALLOUT_DEVICE_PATH" in addDataPiece[i]):
960                     i2creadFail = True
961 
962                     fruCallout = str(addDataPiece[calloutIndex]).split('=')[1]
963 
964                     # Fall back to "I2C"/"FSI" if dev path isn't in policy table
965                     if (messageID + '||' + fruCallout) not in policyTable:
966                         i2cdevice = str(addDataPiece[i]).strip().split('=')[1]
967                         i2cdevice = '/'.join(i2cdevice.split('/')[-4:])
968                         if 'fsi' in str(addDataPiece[calloutIndex]).split('=')[1]:
969                             fruCallout = 'FSI'
970                         else:
971                             fruCallout = 'I2C'
972                     calloutFound = True
973                 if("CALLOUT_GPIO_NUM" in addDataPiece[i]):
974                     if not calloutFound:
975                         fruCallout = 'GPIO'
976                     calloutFound = True
977                 if("CALLOUT_IIC_BUS" in addDataPiece[i]):
978                     if not calloutFound:
979                         fruCallout = "I2C"
980                     calloutFound = True
981                 if("CALLOUT_IPMI_SENSOR_NUM" in addDataPiece[i]):
982                     if not calloutFound:
983                         fruCallout = "IPMI"
984                     calloutFound = True
985                 if("ESEL" in addDataPiece[i]):
986                     esel = str(addDataPiece[i]).strip().split('=')[1]
987                     eselSeverity = getESELSeverity(esel)
988                     if args.devdebug:
989                         eselParts = parseESEL(args, esel)
990                     hasEsel=True
991                 if("GPU" in addDataPiece[i]):
992                     fruCallout = '/xyz/openbmc_project/inventory/system/chassis/motherboard/gpu' + str(addDataPiece[i]).strip()[-1]
993                     calloutFound = True
994                 if("PROCEDURE" in addDataPiece[i]):
995                     fruCallout = str(hex(int(str(addDataPiece[i]).split('=')[1])))[2:]
996                     calloutFound = True
997                 if("RAIL_NAME" in addDataPiece[i]):
998                     calloutFound=True
999                     fruCallout = str(addDataPiece[i]).split('=')[1].strip()
1000                 if("INPUT_NAME" in addDataPiece[i]):
1001                     calloutFound=True
1002                     fruCallout = str(addDataPiece[i]).split('=')[1].strip()
1003                 if("SENSOR_TYPE" in addDataPiece[i]):
1004                     calloutFound=True
1005                     fruCallout = str(addDataPiece[i]).split('=')[1].strip()
1006 
1007             if(calloutFound):
1008                 if fruCallout.strip() != "":
1009                     policyKey = messageID +"||" +  fruCallout
1010 
1011                     # Also use the severity for hostboot errors
1012                     if eselSeverity and messageID == 'org.open_power.Host.Error.Event':
1013                         policyKey += '||' + eselSeverity
1014 
1015                         # if not in the table, fall back to the original key
1016                         if policyKey not in policyTable:
1017                             policyKey = policyKey.replace('||'+eselSeverity, '')
1018 
1019                     if policyKey not in policyTable:
1020                         policyKey = messageID
1021                 else:
1022                     policyKey = messageID
1023             else:
1024                 policyKey = messageID
1025             event = {}
1026             eventNum = str(count)
1027             if policyKey in policyTable:
1028                 for pkey in policyTable[policyKey]:
1029                     if(type(policyTable[policyKey][pkey])== bool):
1030                         event[pkey] = boolToString(policyTable[policyKey][pkey])
1031                     else:
1032                         if (i2creadFail and pkey == 'Message'):
1033                             event[pkey] = policyTable[policyKey][pkey] + ' ' +i2cdevice
1034                         else:
1035                             event[pkey] = policyTable[policyKey][pkey]
1036                 event['timestamp'] = selEntries[key]['Timestamp']
1037                 event['resolved'] = bool(selEntries[key]['Resolved'])
1038                 if(hasEsel):
1039                     if args.devdebug:
1040                         event['eselParts'] = eselParts
1041                     event['raweSEL'] = esel
1042                 event['logNum'] = key.split('/')[-1]
1043                 eventDict['event' + eventNum] = event
1044 
1045             else:
1046                 severity = str(selEntries[key]['Severity']).split('.')[-1]
1047                 if severity == 'Error':
1048                     severity = 'Critical'
1049                 eventDict['event'+eventNum] = {}
1050                 eventDict['event' + eventNum]['error'] = "error: Not found in policy table: " + policyKey
1051                 eventDict['event' + eventNum]['timestamp'] = selEntries[key]['Timestamp']
1052                 eventDict['event' + eventNum]['Severity'] = severity
1053                 if(hasEsel):
1054                     if args.devdebug:
1055                         eventDict['event' +eventNum]['eselParts'] = eselParts
1056                     eventDict['event' +eventNum]['raweSEL'] = esel
1057                 eventDict['event' +eventNum]['logNum'] = key.split('/')[-1]
1058                 eventDict['event' +eventNum]['resolved'] = bool(selEntries[key]['Resolved'])
1059             count += 1
1060     return eventDict
1061 
1062 
1063 def selDisplay(events, args):
1064     """
1065          displays alerts in human readable format
1066 
1067          @param events: Dictionary containing events
1068          @return:
1069     """
1070     activeAlerts = []
1071     historyAlerts = []
1072     sortedEntries = sortSELs(events)
1073     logNumList = sortedEntries[0]
1074     eventKeyDict = sortedEntries[1]
1075     keylist = ['Entry', 'ID', 'Timestamp', 'Serviceable', 'Severity','Message']
1076     if(args.devdebug):
1077         colNames = ['Entry', 'ID', 'Timestamp', 'Serviceable', 'Severity','Message',  'eSEL contents']
1078         keylist.append('eSEL')
1079     else:
1080         colNames = ['Entry', 'ID', 'Timestamp', 'Serviceable', 'Severity', 'Message']
1081     for log in logNumList:
1082         selDict = {}
1083         alert = events[eventKeyDict[str(log)]]
1084         if('error' in alert):
1085             selDict['Entry'] = alert['logNum']
1086             selDict['ID'] = 'Unknown'
1087             selDict['Timestamp'] = datetime.datetime.fromtimestamp(int(alert['timestamp']/1000)).strftime("%Y-%m-%d %H:%M:%S")
1088             msg = alert['error']
1089             polMsg = msg.split("policy table:")[0]
1090             msg = msg.split("policy table:")[1]
1091             msgPieces = msg.split("||")
1092             err = msgPieces[0]
1093             if(err.find("org.open_power.")!=-1):
1094                 err = err.split("org.open_power.")[1]
1095             elif(err.find("xyz.openbmc_project.")!=-1):
1096                 err = err.split("xyz.openbmc_project.")[1]
1097             else:
1098                 err = msgPieces[0]
1099             callout = ""
1100             if len(msgPieces) >1:
1101                 callout = msgPieces[1]
1102                 if(callout.find("/org/open_power/")!=-1):
1103                     callout = callout.split("/org/open_power/")[1]
1104                 elif(callout.find("/xyz/openbmc_project/")!=-1):
1105                     callout = callout.split("/xyz/openbmc_project/")[1]
1106                 else:
1107                     callout = msgPieces[1]
1108             selDict['Message'] = polMsg +"policy table: "+ err +  "||" + callout
1109             selDict['Serviceable'] = 'Unknown'
1110             selDict['Severity'] = alert['Severity']
1111         else:
1112             selDict['Entry'] = alert['logNum']
1113             selDict['ID'] = alert['CommonEventID']
1114             selDict['Timestamp'] = datetime.datetime.fromtimestamp(int(alert['timestamp']/1000)).strftime("%Y-%m-%d %H:%M:%S")
1115             selDict['Message'] = alert['Message']
1116             selDict['Serviceable'] = alert['Serviceable']
1117             selDict['Severity'] = alert['Severity']
1118 
1119 
1120         eselOrder = ['refCode','signatureDescription', 'eselType', 'devdesc', 'calloutType', 'procedure']
1121         if ('eselParts' in alert and args.devdebug):
1122             eselOutput = ""
1123             for item in eselOrder:
1124                 if item in alert['eselParts']:
1125                     eselOutput = eselOutput + item + ": " + alert['eselParts'][item] + " | "
1126             selDict['eSEL'] = eselOutput
1127         else:
1128             if args.devdebug:
1129                 selDict['eSEL'] = "None"
1130 
1131         if not alert['resolved']:
1132             activeAlerts.append(selDict)
1133         else:
1134             historyAlerts.append(selDict)
1135     mergedOutput = activeAlerts + historyAlerts
1136     colWidth = setColWidth(keylist, len(colNames), dict(enumerate(mergedOutput)), colNames)
1137 
1138     output = ""
1139     if(len(activeAlerts)>0):
1140         row = ""
1141         output +="----Active Alerts----\n"
1142         for i in range(0, len(colNames)):
1143             if i!=0: row =row + "| "
1144             row = row + colNames[i].ljust(colWidth[i])
1145         output += row + "\n"
1146 
1147         for i in range(0,len(activeAlerts)):
1148             row = ""
1149             for j in range(len(activeAlerts[i])):
1150                 if (j != 0): row = row + "| "
1151                 row = row + activeAlerts[i][keylist[j]].ljust(colWidth[j])
1152             output += row + "\n"
1153 
1154     if(len(historyAlerts)>0):
1155         row = ""
1156         output+= "----Historical Alerts----\n"
1157         for i in range(len(colNames)):
1158             if i!=0: row =row + "| "
1159             row = row + colNames[i].ljust(colWidth[i])
1160         output += row + "\n"
1161 
1162         for i in range(0, len(historyAlerts)):
1163             row = ""
1164             for j in range(len(historyAlerts[i])):
1165                 if (j != 0): row = row + "| "
1166                 row = row + historyAlerts[i][keylist[j]].ljust(colWidth[j])
1167             output += row + "\n"
1168 #         print(events[eventKeyDict[str(log)]])
1169     return output
1170 
1171 
1172 def selPrint(host, args, session):
1173     """
1174          prints out all bmc alerts
1175 
1176          @param host: string, the hostname or IP address of the bmc
1177          @param args: contains additional arguments used by the fru sub command
1178          @param session: the active session to use
1179          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1180     """
1181     if(args.policyTableLoc is None):
1182         if os.path.exists('policyTable.json'):
1183             ptableLoc = "policyTable.json"
1184         elif os.path.exists('/opt/ibm/ras/lib/policyTable.json'):
1185             ptableLoc = '/opt/ibm/ras/lib/policyTable.json'
1186         else:
1187             ptableLoc = 'lib/policyTable.json'
1188     else:
1189         ptableLoc = args.policyTableLoc
1190     policyTable = loadPolicyTable(ptableLoc)
1191     rawselEntries = ""
1192     if(hasattr(args, 'fileloc') and args.fileloc is not None):
1193         if os.path.exists(args.fileloc):
1194             with open(args.fileloc, 'r') as selFile:
1195                 selLines = selFile.readlines()
1196             rawselEntries = ''.join(selLines)
1197         else:
1198             print("Error: File not found")
1199             sys.exit(1)
1200     else:
1201         rawselEntries = sel(host, args, session)
1202     loadFailed = False
1203     try:
1204         selEntries = json.loads(rawselEntries)
1205     except ValueError:
1206         loadFailed = True
1207     if loadFailed:
1208         cleanSels = json.dumps(rawselEntries).replace('\\n', '')
1209         #need to load json twice as original content was string escaped a second time
1210         selEntries = json.loads(json.loads(cleanSels))
1211     selEntries = selEntries['data']
1212 
1213     if 'description' in selEntries:
1214         if(args.json):
1215             return("{\n\t\"numAlerts\": 0\n}")
1216         else:
1217             return("No log entries found")
1218 
1219     else:
1220         if(len(policyTable)>0):
1221             events = parseAlerts(policyTable, selEntries, args)
1222             if(args.json):
1223                 events["numAlerts"] = len(events)
1224                 retValue = str(json.dumps(events, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False))
1225                 return retValue
1226             elif(hasattr(args, 'fullSel')):
1227                 return events
1228             else:
1229                 #get log numbers to order event entries sequentially
1230                 return selDisplay(events, args)
1231         else:
1232             if(args.json):
1233                 return selEntries
1234             else:
1235                 print("error: Policy Table not found.")
1236                 return selEntries
1237 
1238 def selList(host, args, session):
1239     """
1240          prints out all all bmc alerts, or only prints out the specified alerts
1241 
1242          @param host: string, the hostname or IP address of the bmc
1243          @param args: contains additional arguments used by the fru sub command
1244          @param session: the active session to use
1245          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1246     """
1247     return(sel(host, args, session))
1248 
1249 
1250 def selClear(host, args, session):
1251     """
1252          clears all alerts
1253 
1254          @param host: string, the hostname or IP address of the bmc
1255          @param args: contains additional arguments used by the fru sub command
1256          @param session: the active session to use
1257          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1258     """
1259     url="https://"+host+"/xyz/openbmc_project/logging/action/DeleteAll"
1260     data = "{\"data\": [] }"
1261 
1262     try:
1263         res = session.post(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
1264     except(requests.exceptions.Timeout):
1265         return(connectionErrHandler(args.json, "Timeout", None))
1266     if res.status_code == 200:
1267         return "The Alert Log has been cleared. Please allow a few minutes for the action to complete."
1268     else:
1269         print("Unable to clear the logs, trying to clear 1 at a time")
1270         sels = json.loads(sel(host, args, session))['data']
1271         for key in sels:
1272             if 'callout' not in key:
1273                 logNum = key.split('/')[-1]
1274                 url = "https://"+ host+ "/xyz/openbmc_project/logging/entry/"+logNum+"/action/Delete"
1275                 try:
1276                     session.post(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
1277                 except(requests.exceptions.Timeout):
1278                     return connectionErrHandler(args.json, "Timeout", None)
1279                     sys.exit(1)
1280                 except(requests.exceptions.ConnectionError) as err:
1281                     return connectionErrHandler(args.json, "ConnectionError", err)
1282                     sys.exit(1)
1283         return ('Sel clearing complete')
1284 
1285 def selSetResolved(host, args, session):
1286     """
1287          sets a sel entry to resolved
1288 
1289          @param host: string, the hostname or IP address of the bmc
1290          @param args: contains additional arguments used by the fru sub command
1291          @param session: the active session to use
1292          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1293     """
1294     url="https://"+host+"/xyz/openbmc_project/logging/entry/" + str(args.selNum) + "/attr/Resolved"
1295     data = "{\"data\": 1 }"
1296     try:
1297         res = session.put(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
1298     except(requests.exceptions.Timeout):
1299         return(connectionErrHandler(args.json, "Timeout", None))
1300     if res.status_code == 200:
1301         return "Sel entry "+ str(args.selNum) +" is now set to resolved"
1302     else:
1303         return "Unable to set the alert to resolved"
1304 
1305 def selResolveAll(host, args, session):
1306     """
1307          sets a sel entry to resolved
1308 
1309          @param host: string, the hostname or IP address of the bmc
1310          @param args: contains additional arguments used by the fru sub command
1311          @param session: the active session to use
1312          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1313     """
1314     rawselEntries = sel(host, args, session)
1315     loadFailed = False
1316     try:
1317         selEntries = json.loads(rawselEntries)
1318     except ValueError:
1319         loadFailed = True
1320     if loadFailed:
1321         cleanSels = json.dumps(rawselEntries).replace('\\n', '')
1322         #need to load json twice as original content was string escaped a second time
1323         selEntries = json.loads(json.loads(cleanSels))
1324     selEntries = selEntries['data']
1325 
1326     if 'description' in selEntries:
1327         if(args.json):
1328             return("{\n\t\"selsResolved\": 0\n}")
1329         else:
1330             return("No log entries found")
1331     else:
1332         d = vars(args)
1333         successlist = []
1334         failedlist = []
1335         for key in selEntries:
1336             if 'callout' not in key:
1337                 d['selNum'] = key.split('/')[-1]
1338                 resolved = selSetResolved(host,args,session)
1339                 if 'Sel entry' in resolved:
1340                     successlist.append(d['selNum'])
1341                 else:
1342                     failedlist.append(d['selNum'])
1343         output = ""
1344         successlist.sort()
1345         failedlist.sort()
1346         if len(successlist)>0:
1347             output = "Successfully resolved: " +', '.join(successlist) +"\n"
1348         if len(failedlist)>0:
1349             output += "Failed to resolve: " + ', '.join(failedlist) + "\n"
1350         return output
1351 
1352 def chassisPower(host, args, session):
1353     """
1354          called by the chassis function. Controls the power state of the chassis, or gets the status
1355 
1356          @param host: string, the hostname or IP address of the bmc
1357          @param args: contains additional arguments used by the fru sub command
1358          @param session: the active session to use
1359          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1360     """
1361     if(args.powcmd == 'on'):
1362         if checkFWactivation(host, args, session):
1363             return ("Chassis Power control disabled during firmware activation")
1364         print("Attempting to Power on...:")
1365         url="https://"+host+"/xyz/openbmc_project/state/host0/attr/RequestedHostTransition"
1366         data = '{"data":"xyz.openbmc_project.State.Host.Transition.On"}'
1367         try:
1368             res = session.put(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
1369         except(requests.exceptions.Timeout):
1370             return(connectionErrHandler(args.json, "Timeout", None))
1371         return res.text
1372     elif(args.powcmd == 'softoff'):
1373         if checkFWactivation(host, args, session):
1374             return ("Chassis Power control disabled during firmware activation")
1375         print("Attempting to Power off gracefully...:")
1376         url="https://"+host+"/xyz/openbmc_project/state/host0/attr/RequestedHostTransition"
1377         data = '{"data":"xyz.openbmc_project.State.Host.Transition.Off"}'
1378         try:
1379             res = session.put(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
1380         except(requests.exceptions.Timeout):
1381             return(connectionErrHandler(args.json, "Timeout", None))
1382         return res.text
1383     elif(args.powcmd == 'hardoff'):
1384         if checkFWactivation(host, args, session):
1385             return ("Chassis Power control disabled during firmware activation")
1386         print("Attempting to Power off immediately...:")
1387         url="https://"+host+"/xyz/openbmc_project/state/chassis0/attr/RequestedPowerTransition"
1388         data = '{"data":"xyz.openbmc_project.State.Chassis.Transition.Off"}'
1389         try:
1390             res = session.put(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
1391         except(requests.exceptions.Timeout):
1392             return(connectionErrHandler(args.json, "Timeout", None))
1393         return res.text
1394     elif(args.powcmd == 'status'):
1395         url="https://"+host+"/xyz/openbmc_project/state/chassis0/attr/CurrentPowerState"
1396         try:
1397             res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
1398         except(requests.exceptions.Timeout):
1399             return(connectionErrHandler(args.json, "Timeout", None))
1400         chassisState = json.loads(res.text)['data'].split('.')[-1]
1401         url="https://"+host+"/xyz/openbmc_project/state/host0/attr/CurrentHostState"
1402         try:
1403             res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
1404         except(requests.exceptions.Timeout):
1405             return(connectionErrHandler(args.json, "Timeout", None))
1406         hostState = json.loads(res.text)['data'].split('.')[-1]
1407         url="https://"+host+"/xyz/openbmc_project/state/bmc0/attr/CurrentBMCState"
1408         try:
1409             res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
1410         except(requests.exceptions.Timeout):
1411             return(connectionErrHandler(args.json, "Timeout", None))
1412         bmcState = json.loads(res.text)['data'].split('.')[-1]
1413         if(args.json):
1414             outDict = {"Chassis Power State" : chassisState, "Host Power State" : hostState, "BMC Power State":bmcState}
1415             return json.dumps(outDict, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False)
1416         else:
1417             return "Chassis Power State: " +chassisState + "\nHost Power State: " + hostState + "\nBMC Power State: " + bmcState
1418     else:
1419         return "Invalid chassis power command"
1420 
1421 
1422 def chassisIdent(host, args, session):
1423     """
1424          called by the chassis function. Controls the identify led of the chassis. Sets or gets the state
1425 
1426          @param host: string, the hostname or IP address of the bmc
1427          @param args: contains additional arguments used by the fru sub command
1428          @param session: the active session to use
1429          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1430     """
1431     if(args.identcmd == 'on'):
1432         print("Attempting to turn identify light on...:")
1433         url="https://"+host+"/xyz/openbmc_project/led/groups/enclosure_identify/attr/Asserted"
1434         data = '{"data":true}'
1435         try:
1436             res = session.put(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
1437         except(requests.exceptions.Timeout):
1438             return(connectionErrHandler(args.json, "Timeout", None))
1439         return res.text
1440     elif(args.identcmd == 'off'):
1441         print("Attempting to turn identify light off...:")
1442         url="https://"+host+"/xyz/openbmc_project/led/groups/enclosure_identify/attr/Asserted"
1443         data = '{"data":false}'
1444         try:
1445             res = session.put(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
1446         except(requests.exceptions.Timeout):
1447             return(connectionErrHandler(args.json, "Timeout", None))
1448         return res.text
1449     elif(args.identcmd == 'status'):
1450         url="https://"+host+"/xyz/openbmc_project/led/groups/enclosure_identify"
1451         try:
1452             res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
1453         except(requests.exceptions.Timeout):
1454             return(connectionErrHandler(args.json, "Timeout", None))
1455         status = json.loads(res.text)['data']
1456         if(args.json):
1457             return status
1458         else:
1459             if status['Asserted'] == 0:
1460                 return "Identify light is off"
1461             else:
1462                 return "Identify light is blinking"
1463     else:
1464         return "Invalid chassis identify command"
1465 
1466 
1467 def chassis(host, args, session):
1468     """
1469          controls the different chassis commands
1470 
1471          @param host: string, the hostname or IP address of the bmc
1472          @param args: contains additional arguments used by the fru sub command
1473          @param session: the active session to use
1474          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1475     """
1476     if(hasattr(args, 'powcmd')):
1477         result = chassisPower(host,args,session)
1478     elif(hasattr(args, 'identcmd')):
1479         result = chassisIdent(host, args, session)
1480     else:
1481         return "This feature is not yet implemented"
1482     return result
1483 
1484 def getTask(host, args, session):
1485     """
1486          Get operation on the Task Monitor URI
1487 
1488          @param host: string, the hostname or IP address of the bmc
1489          @param args: contains additional arguments used by the task sub command
1490          @param session: the active session to use
1491          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1492     """
1493     if args.taskURI is not None:
1494         url ='https://'+host+str(args.taskURI)
1495         try:
1496             r = session.post(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
1497             if (r.status_code == 200 and not args.json):
1498                 return r.text
1499             elif (r.status_code == 200 and args.json):
1500                 return r.json()
1501             else:
1502                 return ('Failed to retrieve the data on Task Monitor URI')
1503         except(requests.exceptions.Timeout):
1504             return connectionErrHandler(args.json, "Timeout", None)
1505         except(requests.exceptions.ConnectionError) as err:
1506             return connectionErrHandler(args.json, "ConnectionError", err)
1507     else:
1508         return 'You must specify the Task Monitor URI'
1509 
1510 
1511 def dumpRetrieve(host, args, session):
1512     """
1513          Downloads dump of given dump type
1514 
1515          @param host: string, the hostname or IP address of the bmc
1516          @param args: contains additional arguments used by the collectServiceData sub command
1517          @param session: the active session to use
1518          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1519     """
1520     dumpType = args.dumpType
1521     if (args.dumpType=="SystemDump"):
1522         dumpResp=systemDumpRetrieve(host,args,session)
1523     elif(args.dumpType=="bmc"):
1524         dumpResp=bmcDumpRetrieve(host,args,session)
1525     return dumpResp
1526 
1527 def dumpList(host, args, session):
1528     """
1529          Lists dump of the given dump type
1530 
1531          @param host: string, the hostname or IP address of the bmc
1532          @param args: contains additional arguments used by the collectServiceData sub command
1533          @param session: the active session to use
1534          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1535     """
1536     if (args.dumpType=="SystemDump"):
1537         dumpResp=systemDumpList(host,args,session)
1538     elif(args.dumpType=="bmc"):
1539         dumpResp=bmcDumpList(host,args,session)
1540     return dumpResp
1541 
1542 def dumpDelete(host, args, session):
1543     """
1544          Deletes dump of the given dump type
1545 
1546          @param host: string, the hostname or IP address of the bmc
1547          @param args: contains additional arguments used by the collectServiceData sub command
1548          @param session: the active session to use
1549          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1550     """
1551     if (args.dumpType=="SystemDump"):
1552         dumpResp=systemDumpDelete(host,args,session)
1553     elif(args.dumpType=="bmc"):
1554         dumpResp=bmcDumpDelete(host,args,session)
1555     return dumpResp
1556 
1557 def dumpDeleteAll(host, args, session):
1558     """
1559          Deletes all dumps of the given dump type
1560 
1561          @param host: string, the hostname or IP address of the bmc
1562          @param args: contains additional arguments used by the collectServiceData sub command
1563          @param session: the active session to use
1564          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1565     """
1566     if (args.dumpType=="SystemDump"):
1567         dumpResp=systemDumpDeleteAll(host,args,session)
1568     elif(args.dumpType=="bmc"):
1569         dumpResp=bmcDumpDeleteAll(host,args,session)
1570     return dumpResp
1571 
1572 def dumpCreate(host, args, session):
1573     """
1574          Creates dump for the given dump type
1575 
1576          @param host: string, the hostname or IP address of the bmc
1577          @param args: contains additional arguments used by the collectServiceData sub command
1578          @param session: the active session to use
1579          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1580     """
1581     if (args.dumpType=="SystemDump"):
1582         dumpResp=systemDumpCreate(host,args,session)
1583     elif(args.dumpType=="bmc"):
1584         dumpResp=bmcDumpCreate(host,args,session)
1585     return dumpResp
1586 
1587 
1588 def bmcDumpRetrieve(host, args, session):
1589     """
1590          Downloads a dump file from the bmc
1591 
1592          @param host: string, the hostname or IP address of the bmc
1593          @param args: contains additional arguments used by the collectServiceData sub command
1594          @param session: the active session to use
1595          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1596     """
1597     dumpNum = args.dumpNum
1598     if (args.dumpSaveLoc is not None):
1599         saveLoc = args.dumpSaveLoc
1600     else:
1601         saveLoc = tempfile.gettempdir()
1602     url ='https://'+host+'/download/dump/' + str(dumpNum)
1603     try:
1604         r = session.get(url, headers=jsonHeader, stream=True, verify=False, timeout=baseTimeout)
1605         if (args.dumpSaveLoc is not None):
1606             if os.path.exists(saveLoc):
1607                 if saveLoc[-1] != os.path.sep:
1608                     saveLoc = saveLoc + os.path.sep
1609                 filename = saveLoc + host+'-dump' + str(dumpNum) + '.tar.xz'
1610 
1611             else:
1612                 return 'Invalid save location specified'
1613         else:
1614             filename = tempfile.gettempdir()+os.sep + host+'-dump' + str(dumpNum) + '.tar.xz'
1615 
1616         with open(filename, 'wb') as f:
1617                     for chunk in r.iter_content(chunk_size =1024):
1618                         if chunk:
1619                             f.write(chunk)
1620         return 'Saved as ' + filename
1621 
1622     except(requests.exceptions.Timeout):
1623         return connectionErrHandler(args.json, "Timeout", None)
1624 
1625     except(requests.exceptions.ConnectionError) as err:
1626         return connectionErrHandler(args.json, "ConnectionError", err)
1627 
1628 def bmcDumpList(host, args, session):
1629     """
1630          Lists the number of dump files on the bmc
1631 
1632          @param host: string, the hostname or IP address of the bmc
1633          @param args: contains additional arguments used by the collectServiceData sub command
1634          @param session: the active session to use
1635          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1636     """
1637     url ='https://'+host+'/xyz/openbmc_project/dump/list'
1638     try:
1639         r = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
1640         dumpList = r.json()
1641         formattedList = []
1642         #remove items that aren't dump entries 'entry, internal, manager endpoints'
1643         if 'data' in dumpList:
1644             for entry in dumpList['data']:
1645                 if 'entry' in entry:
1646                     if entry.split('/')[-1].isnumeric():
1647                         formattedList.append(entry)
1648             dumpList['data']= formattedList
1649         return dumpList
1650     except(requests.exceptions.Timeout):
1651         return connectionErrHandler(args.json, "Timeout", None)
1652 
1653     except(requests.exceptions.ConnectionError) as err:
1654         return connectionErrHandler(args.json, "ConnectionError", err)
1655 
1656 def bmcDumpDelete(host, args, session):
1657     """
1658          Deletes BMC dump files from the bmc
1659 
1660          @param host: string, the hostname or IP address of the bmc
1661          @param args: contains additional arguments used by the collectServiceData sub command
1662          @param session: the active session to use
1663          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1664     """
1665     dumpList = []
1666     successList = []
1667     failedList = []
1668     if args.dumpNum is not None:
1669         if isinstance(args.dumpNum, list):
1670             dumpList = args.dumpNum
1671         else:
1672             dumpList.append(args.dumpNum)
1673         for dumpNum in dumpList:
1674             url ='https://'+host+'/xyz/openbmc_project/dump/entry/'+str(dumpNum)+'/action/Delete'
1675             try:
1676                 r = session.post(url, headers=jsonHeader, json = {"data": []}, verify=False, timeout=baseTimeout)
1677                 if r.status_code == 200:
1678                     successList.append(str(dumpNum))
1679                 else:
1680                     failedList.append(str(dumpNum))
1681             except(requests.exceptions.Timeout):
1682                 return connectionErrHandler(args.json, "Timeout", None)
1683             except(requests.exceptions.ConnectionError) as err:
1684                 return connectionErrHandler(args.json, "ConnectionError", err)
1685         output = "Successfully deleted dumps: " + ', '.join(successList)
1686         if(len(failedList)>0):
1687             output+= '\nFailed to delete dumps: ' + ', '.join(failedList)
1688         return output
1689     else:
1690         return 'You must specify an entry number to delete'
1691 
1692 def bmcDumpDeleteAll(host, args, session):
1693     """
1694          Deletes All BMC dump files from the bmc
1695 
1696          @param host: string, the hostname or IP address of the bmc
1697          @param args: contains additional arguments used by the collectServiceData sub command
1698          @param session: the active session to use
1699          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1700     """
1701     dumpResp = bmcDumpList(host, args, session)
1702     if 'FQPSPIN0000M' in dumpResp or 'FQPSPIN0001M'in dumpResp:
1703         return dumpResp
1704     dumpList = dumpResp['data']
1705     d = vars(args)
1706     dumpNums = []
1707     for dump in dumpList:
1708         dumpNum = dump.strip().split('/')[-1]
1709         if dumpNum.isdigit():
1710             dumpNums.append(int(dumpNum))
1711     d['dumpNum'] = dumpNums
1712 
1713     return bmcDumpDelete(host, args, session)
1714 
1715 
1716 def bmcDumpCreate(host, args, session):
1717     """
1718          Creates a bmc dump file
1719 
1720          @param host: string, the hostname or IP address of the bmc
1721          @param args: contains additional arguments used by the collectServiceData sub command
1722          @param session: the active session to use
1723          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1724     """
1725     url = 'https://'+host+'/xyz/openbmc_project/dump/action/CreateDump'
1726     try:
1727         r = session.post(url, headers=jsonHeader, json = {"data": []}, verify=False, timeout=baseTimeout)
1728         info = r.json()
1729         if(r.status_code == 200 and not args.json):
1730             return ('Dump successfully created')
1731         elif(args.json):
1732             return info
1733         elif 'data' in info:
1734             if 'QuotaExceeded' in info['data']['description']:
1735                 return 'BMC dump space is full. Please delete at least one existing dump entry and try again.'
1736             else:
1737                 return "Failed to create a BMC dump. BMC Response:\n {resp}".format(resp=info)
1738         else:
1739             return "Failed to create a BMC dump. BMC Response:\n {resp}".format(resp=info)
1740     except(requests.exceptions.Timeout):
1741         return connectionErrHandler(args.json, "Timeout", None)
1742     except(requests.exceptions.ConnectionError) as err:
1743         return connectionErrHandler(args.json, "ConnectionError", err)
1744 
1745 
1746 def systemDumpRetrieve(host, args, session):
1747     """
1748          Downloads system dump
1749 
1750          @param host: string, the hostname or IP address of the bmc
1751          @param args: contains additional arguments used by the collectServiceData sub command
1752          @param session: the active session to use
1753          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1754     """
1755     NBDSetup(host,args,session)
1756     pipe = NBDPipe()
1757     pipe.openHTTPSocket(args)
1758     pipe.openTCPSocket()
1759     pipe.waitformessage()
1760 
1761 def systemDumpList(host, args, session):
1762     """
1763          Lists system dumps
1764 
1765          @param host: string, the hostname or IP address of the bmc
1766          @param args: contains additional arguments used by the collectServiceData sub command
1767          @param session: the active session to use
1768          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1769     """
1770     url = "https://"+host+"/redfish/v1/Systems/system/LogServices/Dump/Entries"
1771     try:
1772         r = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
1773         dumpList = r.json()
1774         return dumpList
1775     except(requests.exceptions.Timeout):
1776         return connectionErrHandler(args.json, "Timeout", None)
1777 
1778     except(requests.exceptions.ConnectionError) as err:
1779         return connectionErrHandler(args.json, "ConnectionError", err)
1780 
1781 
1782 def systemDumpDelete(host, args, session):
1783     """
1784          Deletes system dump
1785 
1786          @param host: string, the hostname or IP address of the bmc
1787          @param args: contains additional arguments used by the collectServiceData sub command
1788          @param session: the active session to use
1789          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1790     """
1791     dumpList = []
1792     successList = []
1793     failedList = []
1794     if args.dumpNum is not None:
1795         if isinstance(args.dumpNum, list):
1796             dumpList = args.dumpNum
1797         else:
1798             dumpList.append(args.dumpNum)
1799         for dumpNum in dumpList:
1800             url = 'https://'+host+'/redfish/v1/Systems/system/LogServices/Dump/Entries/'+ str(dumpNum)
1801             try:
1802                 r = session.delete(url, headers=jsonHeader, json = {"data": []}, verify=False, timeout=baseTimeout)
1803                 if r.status_code == 200:
1804                     successList.append(str(dumpNum))
1805                 else:
1806                     failedList.append(str(dumpNum))
1807             except(requests.exceptions.Timeout):
1808                 return connectionErrHandler(args.json, "Timeout", None)
1809             except(requests.exceptions.ConnectionError) as err:
1810                 return connectionErrHandler(args.json, "ConnectionError", err)
1811         output = "Successfully deleted dumps: " + ', '.join(successList)
1812         if(len(failedList)>0):
1813             output+= '\nFailed to delete dumps: ' + ', '.join(failedList)
1814         return output
1815     else:
1816         return 'You must specify an entry number to delete'
1817 
1818 def systemDumpDeleteAll(host, args, session):
1819     """
1820          Deletes All system dumps
1821 
1822          @param host: string, the hostname or IP address of the bmc
1823          @param args: contains additional arguments used by the collectServiceData sub command
1824          @param session: the active session to use
1825          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1826     """
1827     url = 'https://'+host+'/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.ClearLog'
1828     try:
1829         r = session.post(url, headers=jsonHeader, json = {"data": []}, verify=False, timeout=baseTimeout)
1830         if(r.status_code == 200 and not args.json):
1831             return ('Dumps successfully cleared')
1832         elif(args.json):
1833             return r.json()
1834         else:
1835             return ('Failed to clear dumps')
1836     except(requests.exceptions.Timeout):
1837         return connectionErrHandler(args.json, "Timeout", None)
1838     except(requests.exceptions.ConnectionError) as err:
1839         return connectionErrHandler(args.json, "ConnectionError", err)
1840 
1841 def systemDumpCreate(host, args, session):
1842     """
1843          Creates a system dump
1844 
1845          @param host: string, the hostname or IP address of the bmc
1846          @param args: contains additional arguments used by the collectServiceData sub command
1847          @param session: the active session to use
1848          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1849     """
1850     url =  'https://'+host+'/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.CollectDiagnosticData'
1851     params = {'DiagnosticDataType':'OEM', 'OEMDiagnosticDataType':'System'}
1852     try:
1853         r = session.post(url, headers=jsonHeader, params=params, data = json.dumps(params), verify=False, timeout=baseTimeout)
1854         if(r.status_code == 200):
1855             return r.json()
1856         else:
1857             return ('Failed to create dump')
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 
1863 def csdDumpInitiate(host, args, session):
1864     """
1865         Starts the process of getting the current list of dumps then initiates the creation of one.
1866 
1867          @param host: string, the hostname or IP address of the bmc
1868          @param args: contains additional arguments used by the collectServiceData sub command
1869          @param session: the active session to use
1870          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1871     """
1872     errorInfo = ""
1873     dumpcount = 0
1874     try:
1875         d = vars(args)
1876         d['json'] = True
1877     except Exception as e:
1878         errorInfo += "Failed to set the json flag to True \n Exception: {eInfo}\n".format(eInfo=e)
1879         exc_type, exc_obj, exc_tb = sys.exc_info()
1880         fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
1881         errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
1882         errorInfo += traceback.format_exc()
1883 
1884     try:
1885         for i in range(3):
1886             dumpInfo = bmcDumpList(host, args, session)
1887             if 'data' in dumpInfo:
1888                 dumpcount = len(dumpInfo['data'])
1889                 break
1890             else:
1891                 errorInfo+= "Dump List Message returned: " + json.dumps(dumpInfo,indent=0, separators=(',', ':')).replace('\n','') +"\n"
1892     except Exception as e:
1893         errorInfo+= "Failed to collect the list of dumps.\nException: {eInfo}\n".format(eInfo=e)
1894         exc_type, exc_obj, exc_tb = sys.exc_info()
1895         fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
1896         errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
1897         errorInfo += traceback.format_exc()
1898 
1899     #Create a user initiated dump
1900     dumpFailure = True
1901     try:
1902         for i in range(3):
1903             dumpcreated = bmcDumpCreate(host, args, session)
1904             if 'message' in dumpcreated:
1905                 if 'ok' in dumpcreated['message'].lower():
1906                     dumpFailure = False
1907                     break
1908                 elif 'data' in dumpcreated:
1909                     if 'QuotaExceeded' in dumpcreated['data']['description']:
1910                         print('Not enough dump space on the BMC to create a new dump. Please delete the oldest entry (lowest number) and rerun the collect_service_data command.')
1911                         errorInfo+='Dump Space is full. No new dump was created with this collection'
1912                         break
1913                     else:
1914                         errorInfo+= "Dump create message returned: " + json.dumps(dumpcreated,indent=0, separators=(',', ':')).replace('\n','') +"\n"
1915                 else:
1916                     errorInfo+= "Dump create message returned: " + json.dumps(dumpcreated,indent=0, separators=(',', ':')).replace('\n','') +"\n"
1917             else:
1918                 errorInfo+= "Dump create message returned: " + json.dumps(dumpcreated,indent=0, separators=(',', ':')).replace('\n','') +"\n"
1919     except Exception as e:
1920         errorInfo+= "Dump create exception encountered: {eInfo}\n".format(eInfo=e)
1921         exc_type, exc_obj, exc_tb = sys.exc_info()
1922         fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
1923         errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
1924         errorInfo += traceback.format_exc()
1925 
1926     output = {}
1927     output['errors'] = errorInfo
1928     output['dumpcount'] = dumpcount
1929     if dumpFailure: output['dumpFailure'] = True
1930     return output
1931 
1932 def csdInventory(host, args,session, fileDir):
1933     """
1934         Collects the BMC inventory, retrying if necessary
1935 
1936          @param host: string, the hostname or IP address of the bmc
1937          @param args: contains additional arguments used by the collectServiceData sub command
1938          @param session: the active session to use
1939          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1940          @param fileDir: string representation of the path to use for putting files created
1941     """
1942     errorInfo = "===========Inventory =============\n"
1943     output={}
1944     inventoryCollected = False
1945     try:
1946         for i in range(3):
1947             frulist = fruPrint(host, args, session)
1948             if 'Hardware' in frulist:
1949                 inventoryCollected = True
1950                 break
1951             else:
1952                 errorInfo += json.dumps(frulist, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False) + '\n'
1953     except Exception as e:
1954         errorInfo += "Inventory collection exception: {eInfo}\n".format(eInfo=e)
1955         exc_type, exc_obj, exc_tb = sys.exc_info()
1956         fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
1957         errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
1958         errorInfo += traceback.format_exc()
1959     if inventoryCollected:
1960         try:
1961             with open(fileDir +os.sep+'inventory.txt', 'w') as f:
1962                 f.write(json.dumps(frulist, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False) + '\n')
1963             print("Inventory collected and stored in " + fileDir + os.sep + "inventory.txt")
1964             output['fileLoc'] = fileDir+os.sep+'inventory.txt'
1965         except Exception as e:
1966             print("Failed to write inventory to file.")
1967             errorInfo += "Error writing inventory to the file. Exception: {eInfo}\n".format(eInfo=e)
1968             exc_type, exc_obj, exc_tb = sys.exc_info()
1969             fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
1970             errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
1971             errorInfo += traceback.format_exc()
1972 
1973     output['errors'] = errorInfo
1974 
1975     return output
1976 
1977 def csdSensors(host, args,session, fileDir):
1978     """
1979         Collects the BMC sensor readings, retrying if necessary
1980 
1981          @param host: string, the hostname or IP address of the bmc
1982          @param args: contains additional arguments used by the collectServiceData sub command
1983          @param session: the active session to use
1984          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1985          @param fileDir: string representation of the path to use for putting files created
1986     """
1987     errorInfo = "===========Sensors =============\n"
1988     sensorsCollected = False
1989     output={}
1990     try:
1991         d = vars(args)
1992         d['json'] = False
1993     except Exception as e:
1994         errorInfo += "Failed to set the json flag to False \n Exception: {eInfo}\n".format(eInfo=e)
1995         exc_type, exc_obj, exc_tb = sys.exc_info()
1996         fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
1997         errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
1998         errorInfo += traceback.format_exc()
1999 
2000     try:
2001         for i in range(3):
2002             sensorReadings = sensor(host, args, session)
2003             if 'OCC0' in sensorReadings:
2004                 sensorsCollected = True
2005                 break
2006             else:
2007                 errorInfo += sensorReadings
2008     except Exception as e:
2009         errorInfo += "Sensor reading collection exception: {eInfo}\n".format(eInfo=e)
2010         exc_type, exc_obj, exc_tb = sys.exc_info()
2011         fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2012         errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2013         errorInfo += traceback.format_exc()
2014     if sensorsCollected:
2015         try:
2016             with open(fileDir +os.sep+'sensorReadings.txt', 'w') as f:
2017                 f.write(sensorReadings)
2018             print("Sensor readings collected and stored in " + fileDir + os.sep+ "sensorReadings.txt")
2019             output['fileLoc'] = fileDir+os.sep+'sensorReadings.txt'
2020         except Exception as e:
2021             print("Failed to write sensor readings to file system.")
2022             errorInfo += "Error writing sensor readings to the file. Exception: {eInfo}\n".format(eInfo=e)
2023             exc_type, exc_obj, exc_tb = sys.exc_info()
2024             fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2025             errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2026             errorInfo += traceback.format_exc()
2027 
2028     output['errors'] = errorInfo
2029     return output
2030 
2031 def csdLEDs(host,args, session, fileDir):
2032     """
2033         Collects the BMC LED status, retrying if necessary
2034 
2035          @param host: string, the hostname or IP address of the bmc
2036          @param args: contains additional arguments used by the collectServiceData sub command
2037          @param session: the active session to use
2038          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
2039          @param fileDir: string representation of the path to use for putting files created
2040     """
2041     errorInfo = "===========LEDs =============\n"
2042     ledsCollected = False
2043     output={}
2044     try:
2045         d = vars(args)
2046         d['json'] = True
2047     except Exception as e:
2048         errorInfo += "Failed to set the json flag to False \n Exception: {eInfo}\n".format(eInfo=e)
2049         exc_type, exc_obj, exc_tb = sys.exc_info()
2050         fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2051         errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2052         errorInfo += traceback.format_exc()
2053     try:
2054         url="https://"+host+"/xyz/openbmc_project/led/enumerate"
2055         httpHeader = {'Content-Type':'application/json'}
2056         for i in range(3):
2057             try:
2058                 ledRes = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
2059                 if ledRes.status_code == 200:
2060                     ledsCollected = True
2061                     leds = ledRes.json()['data']
2062                     break
2063                 else:
2064                     errorInfo += ledRes.text
2065             except(requests.exceptions.Timeout):
2066                 errorInfo+=json.dumps( connectionErrHandler(args.json, "Timeout", None), sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False) + '\n'
2067             except(requests.exceptions.ConnectionError) as err:
2068                 errorInfo += json.dumps(connectionErrHandler(args.json, "ConnectionError", err), sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False) + '\n'
2069                 exc_type, exc_obj, exc_tb = sys.exc_info()
2070                 fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2071                 errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2072                 errorInfo += traceback.format_exc()
2073     except Exception as e:
2074         errorInfo += "LED status collection exception: {eInfo}\n".format(eInfo=e)
2075         exc_type, exc_obj, exc_tb = sys.exc_info()
2076         fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2077         errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2078         errorInfo += traceback.format_exc()
2079 
2080     if ledsCollected:
2081         try:
2082             with open(fileDir +os.sep+'ledStatus.txt', 'w') as f:
2083                 f.write(json.dumps(leds, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False) + '\n')
2084             print("LED status collected and stored in " + fileDir + os.sep+ "ledStatus.txt")
2085             output['fileLoc'] = fileDir+os.sep+'ledStatus.txt'
2086         except Exception as e:
2087             print("Failed to write LED status to file system.")
2088             errorInfo += "Error writing LED status to the file. Exception: {eInfo}\n".format(eInfo=e)
2089             exc_type, exc_obj, exc_tb = sys.exc_info()
2090             fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2091             errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2092             errorInfo += traceback.format_exc()
2093 
2094     output['errors'] = errorInfo
2095     return output
2096 
2097 def csdSelShortList(host, args, session, fileDir):
2098     """
2099         Collects the BMC log entries, retrying if necessary
2100 
2101          @param host: string, the hostname or IP address of the bmc
2102          @param args: contains additional arguments used by the collectServiceData sub command
2103          @param session: the active session to use
2104          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
2105          @param fileDir: string representation of the path to use for putting files created
2106     """
2107     errorInfo = "===========SEL Short List =============\n"
2108     selsCollected = False
2109     output={}
2110     try:
2111         d = vars(args)
2112         d['json'] = False
2113     except Exception as e:
2114         errorInfo += "Failed to set the json flag to False \n Exception: {eInfo}\n".format(eInfo=e)
2115         exc_type, exc_obj, exc_tb = sys.exc_info()
2116         fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2117         errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2118         errorInfo += traceback.format_exc()
2119 
2120     try:
2121         for i in range(3):
2122             sels = selPrint(host,args,session)
2123             if '----Active Alerts----' in sels or 'No log entries found' in sels or '----Historical Alerts----' in sels:
2124                 selsCollected = True
2125                 break
2126             else:
2127                 errorInfo += sels + '\n'
2128     except Exception as e:
2129         errorInfo += "SEL short list collection exception: {eInfo}\n".format(eInfo=e)
2130         exc_type, exc_obj, exc_tb = sys.exc_info()
2131         fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2132         errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2133         errorInfo += traceback.format_exc()
2134 
2135     if selsCollected:
2136         try:
2137             with open(fileDir +os.sep+'SELshortlist.txt', 'w') as f:
2138                 f.write(sels)
2139             print("SEL short list collected and stored in " + fileDir + os.sep+ "SELshortlist.txt")
2140             output['fileLoc'] = fileDir+os.sep+'SELshortlist.txt'
2141         except Exception as e:
2142             print("Failed to write SEL short list to file system.")
2143             errorInfo += "Error writing SEL short list to the file. Exception: {eInfo}\n".format(eInfo=e)
2144             exc_type, exc_obj, exc_tb = sys.exc_info()
2145             fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2146             errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2147             errorInfo += traceback.format_exc()
2148 
2149     output['errors'] = errorInfo
2150     return output
2151 
2152 def csdParsedSels(host, args, session, fileDir):
2153     """
2154         Collects the BMC log entries, retrying if necessary
2155 
2156          @param host: string, the hostname or IP address of the bmc
2157          @param args: contains additional arguments used by the collectServiceData sub command
2158          @param session: the active session to use
2159          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
2160          @param fileDir: string representation of the path to use for putting files created
2161     """
2162     errorInfo = "===========SEL Parsed List =============\n"
2163     selsCollected = False
2164     output={}
2165     try:
2166         d = vars(args)
2167         d['json'] = True
2168         d['fullEsel'] = True
2169     except Exception as e:
2170         errorInfo += "Failed to set the json flag to True \n Exception: {eInfo}\n".format(eInfo=e)
2171         exc_type, exc_obj, exc_tb = sys.exc_info()
2172         fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2173         errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2174         errorInfo += traceback.format_exc()
2175 
2176     try:
2177         for i in range(3):
2178             parsedfullsels = json.loads(selPrint(host,args,session))
2179             if 'numAlerts' in parsedfullsels:
2180                 selsCollected = True
2181                 break
2182             else:
2183                 errorInfo += parsedfullsels + '\n'
2184     except Exception as e:
2185         errorInfo += "Parsed full SELs collection exception: {eInfo}\n".format(eInfo=e)
2186         exc_type, exc_obj, exc_tb = sys.exc_info()
2187         fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2188         errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2189         errorInfo += traceback.format_exc()
2190 
2191     if selsCollected:
2192         try:
2193             sortedSELs = sortSELs(parsedfullsels)
2194             with open(fileDir +os.sep+'parsedSELs.txt', 'w') as f:
2195                 for log in sortedSELs[0]:
2196                     esel = ""
2197                     parsedfullsels[sortedSELs[1][str(log)]]['timestamp'] = datetime.datetime.fromtimestamp(int(parsedfullsels[sortedSELs[1][str(log)]]['timestamp']/1000)).strftime("%Y-%m-%d %H:%M:%S")
2198                     if ('raweSEL' in parsedfullsels[sortedSELs[1][str(log)]] and args.devdebug):
2199                         esel = parsedfullsels[sortedSELs[1][str(log)]]['raweSEL']
2200                         del parsedfullsels[sortedSELs[1][str(log)]]['raweSEL']
2201                     f.write(json.dumps(parsedfullsels[sortedSELs[1][str(log)]],sort_keys=True, indent=4, separators=(',', ': ')))
2202                     if(args.devdebug and esel != ""):
2203                         f.write(parseESEL(args, esel))
2204             print("Parsed SELs collected and stored in " + fileDir + os.sep+ "parsedSELs.txt")
2205             output['fileLoc'] = fileDir+os.sep+'parsedSELs.txt'
2206         except Exception as e:
2207             print("Failed to write fully parsed SELs to file system.")
2208             errorInfo += "Error writing fully parsed SELs to the file. Exception: {eInfo}\n".format(eInfo=e)
2209             exc_type, exc_obj, exc_tb = sys.exc_info()
2210             fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2211             errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2212             errorInfo += traceback.format_exc()
2213 
2214     output['errors'] = errorInfo
2215     return output
2216 
2217 def csdFullEnumeration(host, args, session, fileDir):
2218     """
2219         Collects a full enumeration of /xyz/openbmc_project/, retrying if necessary
2220 
2221          @param host: string, the hostname or IP address of the bmc
2222          @param args: contains additional arguments used by the collectServiceData sub command
2223          @param session: the active session to use
2224          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
2225          @param fileDir: string representation of the path to use for putting files created
2226     """
2227     errorInfo = "===========BMC Full Enumeration =============\n"
2228     bmcFullCollected = False
2229     output={}
2230     try:
2231         d = vars(args)
2232         d['json'] = True
2233     except Exception as e:
2234         errorInfo += "Failed to set the json flag to False \n Exception: {eInfo}\n".format(eInfo=e)
2235         exc_type, exc_obj, exc_tb = sys.exc_info()
2236         fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2237         errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2238         errorInfo += traceback.format_exc()
2239     try:
2240         print("Attempting to get a full BMC enumeration")
2241         url="https://"+host+"/xyz/openbmc_project/enumerate"
2242         httpHeader = {'Content-Type':'application/json'}
2243         for i in range(3):
2244             try:
2245                 bmcRes = session.get(url, headers=jsonHeader, verify=False, timeout=180)
2246                 if bmcRes.status_code == 200:
2247                     bmcFullCollected = True
2248                     fullEnumeration = bmcRes.json()
2249                     break
2250                 else:
2251                     errorInfo += bmcRes.text
2252             except(requests.exceptions.Timeout):
2253                 errorInfo+=json.dumps( connectionErrHandler(args.json, "Timeout", None), sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False) + '\n'
2254             except(requests.exceptions.ConnectionError) as err:
2255                 errorInfo += json.dumps(connectionErrHandler(args.json, "ConnectionError", err), sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False) + '\n'
2256                 exc_type, exc_obj, exc_tb = sys.exc_info()
2257                 fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2258                 errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2259                 errorInfo += traceback.format_exc()
2260     except Exception as e:
2261         errorInfo += "RAW BMC data collection exception: {eInfo}\n".format(eInfo=e)
2262         exc_type, exc_obj, exc_tb = sys.exc_info()
2263         fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2264         errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2265         errorInfo += traceback.format_exc()
2266 
2267     if bmcFullCollected:
2268         try:
2269             with open(fileDir +os.sep+'bmcFullRaw.txt', 'w') as f:
2270                 f.write(json.dumps(fullEnumeration, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False) + '\n')
2271             print("RAW BMC data collected and saved into " + fileDir + os.sep+ "bmcFullRaw.txt")
2272             output['fileLoc'] = fileDir+os.sep+'bmcFullRaw.txt'
2273         except Exception as e:
2274             print("Failed to write RAW BMC data  to file system.")
2275             errorInfo += "Error writing RAW BMC data collection to the file. Exception: {eInfo}\n".format(eInfo=e)
2276             exc_type, exc_obj, exc_tb = sys.exc_info()
2277             fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2278             errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2279             errorInfo += traceback.format_exc()
2280 
2281     output['errors'] = errorInfo
2282     return output
2283 
2284 def csdCollectAllDumps(host, args, session, fileDir):
2285     """
2286         Collects all of the bmc dump files and stores them in fileDir
2287 
2288         @param host: string, the hostname or IP address of the bmc
2289         @param args: contains additional arguments used by the collectServiceData sub command
2290         @param session: the active session to use
2291         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
2292         @param fileDir: string representation of the path to use for putting files created
2293     """
2294 
2295     errorInfo = "===========BMC Dump Collection =============\n"
2296     dumpListCollected = False
2297     output={}
2298     dumpList = {}
2299     try:
2300         d = vars(args)
2301         d['json'] = True
2302         d['dumpSaveLoc'] = fileDir
2303     except Exception as e:
2304         errorInfo += "Failed to set the json flag to True, or failed to set the dumpSave Location \n Exception: {eInfo}\n".format(eInfo=e)
2305         exc_type, exc_obj, exc_tb = sys.exc_info()
2306         fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2307         errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2308         errorInfo += traceback.format_exc()
2309 
2310     print('Collecting bmc dump files')
2311 
2312     try:
2313         for i in range(3):
2314             dumpResp = bmcDumpList(host, args, session)
2315             if 'message' in dumpResp:
2316                 if 'ok' in dumpResp['message'].lower():
2317                     dumpList = dumpResp['data']
2318                     dumpListCollected = True
2319                     break
2320                 else:
2321                     errorInfo += "Status was not OK when retrieving the list of dumps available. \n Response: \n{resp}\n".format(resp=dumpResp)
2322             else:
2323                 errorInfo += "Invalid response received from the BMC while retrieving the list of dumps available.\n {resp}\n".format(resp=dumpResp)
2324     except Exception as e:
2325         errorInfo += "BMC dump list exception: {eInfo}\n".format(eInfo=e)
2326         exc_type, exc_obj, exc_tb = sys.exc_info()
2327         fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2328         errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2329         errorInfo += traceback.format_exc()
2330 
2331     if dumpListCollected:
2332         output['fileList'] = []
2333         for dump in dumpList:
2334             try:
2335                 if '/xyz/openbmc_project/dump/internal/manager' not in dump:
2336                     d['dumpNum'] = int(dump.strip().split('/')[-1])
2337                     print('retrieving dump file ' + str(d['dumpNum']))
2338                     filename = bmcDumpRetrieve(host, args, session).split('Saved as ')[-1]
2339                     output['fileList'].append(filename)
2340             except Exception as e:
2341                 print("Unable to collect dump: {dumpInfo}".format(dumpInfo=dump))
2342                 errorInfo += "Exception collecting a bmc dump {dumpInfo}\n {eInfo}\n".format(dumpInfo=dump, eInfo=e)
2343                 exc_type, exc_obj, exc_tb = sys.exc_info()
2344                 fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2345                 errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2346                 errorInfo += traceback.format_exc()
2347     output['errors'] = errorInfo
2348     return output
2349 
2350 def collectServiceData(host, args, session):
2351     """
2352          Collects all data needed for service from the BMC
2353 
2354          @param host: string, the hostname or IP address of the bmc
2355          @param args: contains additional arguments used by the collectServiceData sub command
2356          @param session: the active session to use
2357          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
2358     """
2359 
2360     global toolVersion
2361     filelist = []
2362     errorInfo = ""
2363 
2364     #get current number of bmc dumps and create a new bmc dump
2365     dumpInitdata = csdDumpInitiate(host, args, session)
2366     if 'dumpFailure' in dumpInitdata:
2367         return 'Collect service data is stopping due to not being able to create a new dump. No service data was collected.'
2368     dumpcount = dumpInitdata['dumpcount']
2369     errorInfo += dumpInitdata['errors']
2370     #create the directory to put files
2371     try:
2372         args.silent = True
2373         myDir = tempfile.gettempdir()+os.sep + host + "--" + datetime.datetime.now().strftime("%Y-%m-%d_%H.%M.%S")
2374         os.makedirs(myDir)
2375 
2376     except Exception as e:
2377         print('Unable to create the temporary directory for data collection. Ensure sufficient privileges to create temporary directory. Aborting.')
2378         exc_type, exc_obj, exc_tb = sys.exc_info()
2379         fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2380         errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2381         errorInfo += traceback.format_exc()
2382         return("Python exception: {eInfo}".format(eInfo = e))
2383 
2384     #Collect Inventory
2385     inventoryData = csdInventory(host, args, session, myDir)
2386     if 'fileLoc' in inventoryData:
2387         filelist.append(inventoryData['fileLoc'])
2388     errorInfo += inventoryData['errors']
2389     #Read all the sensor and OCC status
2390     sensorData = csdSensors(host,args,session,myDir)
2391     if 'fileLoc' in sensorData:
2392         filelist.append(sensorData['fileLoc'])
2393     errorInfo += sensorData['errors']
2394     #Collect all of the LEDs status
2395     ledStatus = csdLEDs(host, args, session, myDir)
2396     if 'fileLoc' in ledStatus:
2397         filelist.append(ledStatus['fileLoc'])
2398     errorInfo += ledStatus['errors']
2399 
2400     #Collect the bmc logs
2401     selShort = csdSelShortList(host, args, session, myDir)
2402     if 'fileLoc' in selShort:
2403         filelist.append(selShort['fileLoc'])
2404     errorInfo += selShort['errors']
2405 
2406     parsedSELs = csdParsedSels(host, args, session, myDir)
2407     if 'fileLoc' in parsedSELs:
2408         filelist.append(parsedSELs['fileLoc'])
2409     errorInfo += parsedSELs['errors']
2410 
2411     #collect RAW bmc enumeration
2412     bmcRaw = csdFullEnumeration(host, args, session, myDir)
2413     if 'fileLoc' in bmcRaw:
2414         filelist.append(bmcRaw['fileLoc'])
2415     errorInfo += bmcRaw['errors']
2416 
2417     #wait for new dump to finish being created
2418     waitingForNewDump = True
2419     count = 0;
2420     print("Waiting for new BMC dump to finish being created. Wait time could be up to 5 minutes")
2421     while(waitingForNewDump):
2422         dumpList = bmcDumpList(host, args, session)['data']
2423         if len(dumpList) > dumpcount:
2424             waitingForNewDump = False
2425             break;
2426         elif(count>150):
2427             print("Timed out waiting for bmc to make a new dump file. Continuing without it.")
2428             break;
2429         else:
2430             time.sleep(2)
2431         count += 1
2432 
2433     #collect all of the dump files
2434     getBMCDumps = csdCollectAllDumps(host, args, session, myDir)
2435     if 'fileList' in getBMCDumps:
2436         filelist+= getBMCDumps['fileList']
2437     errorInfo += getBMCDumps['errors']
2438 
2439     #write the runtime errors to a file
2440     try:
2441         with open(myDir +os.sep+'openbmctoolRuntimeErrors.txt', 'w') as f:
2442             f.write(errorInfo)
2443         print("OpenBMC tool runtime errors collected and stored in " + myDir + os.sep+ "openbmctoolRuntimeErrors.txt")
2444         filelist.append(myDir+os.sep+'openbmctoolRuntimeErrors.txt')
2445     except Exception as e:
2446         print("Failed to write OpenBMC tool runtime errors to file system.")
2447 
2448     #create the zip file
2449     try:
2450         filename = myDir.split(tempfile.gettempdir()+os.sep)[-1] + "_" + toolVersion + '_openbmc.zip'
2451         zf = zipfile.ZipFile(myDir+os.sep + filename, 'w')
2452         for myfile in filelist:
2453             zf.write(myfile, os.path.basename(myfile))
2454         zf.close()
2455         print("Zip file with all collected data created and stored in: {fileInfo}".format(fileInfo=myDir+os.sep+filename))
2456     except Exception as e:
2457         print("Failed to create zip file with collected information")
2458     return "data collection finished"
2459 
2460 
2461 def healthCheck(host, args, session):
2462     """
2463          runs a health check on the platform
2464 
2465          @param host: string, the hostname or IP address of the bmc
2466          @param args: contains additional arguments used by the bmc sub command
2467          @param session: the active session to use
2468          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
2469     """
2470     #check fru status and get as json to easily work through
2471     d = vars(args)
2472     useJson = d['json']
2473     d['json'] = True
2474     d['verbose']= False
2475 
2476     frus = json.loads(fruStatus(host, args, session))
2477 
2478     hwStatus= "OK"
2479     performanceStatus = "OK"
2480     for key in frus:
2481         if frus[key]["Functional"] == "No" and frus[key]["Present"] == "Yes":
2482             hwStatus= "Degraded"
2483             if("power_supply" in key or "powersupply" in key):
2484                 gpuCount =0
2485                 for comp in frus:
2486                     if "gv100card" in comp:
2487                         gpuCount +=1
2488                 if gpuCount > 4:
2489                     hwStatus = "Critical"
2490                     performanceStatus="Degraded"
2491                     break;
2492             elif("fan" in key):
2493                 hwStatus = "Degraded"
2494             else:
2495                 performanceStatus = "Degraded"
2496     if useJson:
2497         output = {"Hardware Status": hwStatus, "Performance": performanceStatus}
2498         output = json.dumps(output, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False)
2499     else:
2500         output = ("Hardware Status: " + hwStatus +
2501                   "\nPerformance: " +performanceStatus )
2502 
2503 
2504     #SW407886: Clear the duplicate entries
2505     #collect the dups
2506     d['devdebug'] = False
2507     sels = json.loads(selPrint(host, args, session))
2508     logNums2Clr = []
2509     oldestLogNum={"logNum": "bogus" ,"key" : ""}
2510     count = 0
2511     if sels['numAlerts'] > 0:
2512         for key in sels:
2513             if "numAlerts" in key:
2514                 continue
2515             try:
2516                 if "slave@00:00/00:00:00:06/sbefifo1-dev0/occ1-dev0" in sels[key]['Message']:
2517                     count += 1
2518                     if count > 1:
2519                         #preserve first occurrence
2520                         if sels[key]['timestamp'] < sels[oldestLogNum['key']]['timestamp']:
2521                             oldestLogNum['key']=key
2522                             oldestLogNum['logNum'] = sels[key]['logNum']
2523                     else:
2524                         oldestLogNum['key']=key
2525                         oldestLogNum['logNum'] = sels[key]['logNum']
2526                     logNums2Clr.append(sels[key]['logNum'])
2527             except KeyError:
2528                 continue
2529         if(count >0):
2530             logNums2Clr.remove(oldestLogNum['logNum'])
2531         #delete the dups
2532         if count >1:
2533             data = "{\"data\": [] }"
2534             for logNum in logNums2Clr:
2535                     url = "https://"+ host+ "/xyz/openbmc_project/logging/entry/"+logNum+"/action/Delete"
2536                     try:
2537                         session.post(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
2538                     except(requests.exceptions.Timeout):
2539                         deleteFailed = True
2540                     except(requests.exceptions.ConnectionError) as err:
2541                         deleteFailed = True
2542     #End of defect resolve code
2543     d['json'] = useJson
2544     return output
2545 
2546 
2547 
2548 def bmc(host, args, session):
2549     """
2550          handles various bmc level commands, currently bmc rebooting
2551 
2552          @param host: string, the hostname or IP address of the bmc
2553          @param args: contains additional arguments used by the bmc sub command
2554          @param session: the active session to use
2555          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
2556     """
2557     if(args.type is not None):
2558         return bmcReset(host, args, session)
2559     if(args.info):
2560         return "Not implemented at this time"
2561 
2562 
2563 
2564 def bmcReset(host, args, session):
2565     """
2566          controls resetting the bmc. warm reset reboots the bmc, cold reset removes the configuration and reboots.
2567 
2568          @param host: string, the hostname or IP address of the bmc
2569          @param args: contains additional arguments used by the bmcReset sub command
2570          @param session: the active session to use
2571          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
2572     """
2573     if checkFWactivation(host, args, session):
2574         return ("BMC reset control disabled during firmware activation")
2575     if(args.type == "warm"):
2576         print("\nAttempting to reboot the BMC...:")
2577         url="https://"+host+"/xyz/openbmc_project/state/bmc0/attr/RequestedBMCTransition"
2578         data = '{"data":"xyz.openbmc_project.State.BMC.Transition.Reboot"}'
2579         res = session.put(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
2580         return res.text
2581     elif(args.type =="cold"):
2582         print("\nAttempting to reboot the BMC...:")
2583         url="https://"+host+"/xyz/openbmc_project/state/bmc0/attr/RequestedBMCTransition"
2584         data = '{"data":"xyz.openbmc_project.State.BMC.Transition.Reboot"}'
2585         res = session.put(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
2586         return res.text
2587     else:
2588         return "invalid command"
2589 
2590 def gardClear(host, args, session):
2591     """
2592          clears the gard records from the bmc
2593 
2594          @param host: string, the hostname or IP address of the bmc
2595          @param args: contains additional arguments used by the gardClear sub command
2596          @param session: the active session to use
2597     """
2598     url="https://"+host+"/org/open_power/control/gard/action/Reset"
2599     data = '{"data":[]}'
2600     try:
2601 
2602         res = session.post(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
2603         if res.status_code == 404:
2604             return "Command not supported by this firmware version"
2605         else:
2606             return res.text
2607     except(requests.exceptions.Timeout):
2608         return connectionErrHandler(args.json, "Timeout", None)
2609     except(requests.exceptions.ConnectionError) as err:
2610         return connectionErrHandler(args.json, "ConnectionError", err)
2611 
2612 def activateFWImage(host, args, session):
2613     """
2614          activates a firmware image on the bmc
2615 
2616          @param host: string, the hostname or IP address of the bmc
2617          @param args: contains additional arguments used by the fwflash sub command
2618          @param session: the active session to use
2619          @param fwID: the unique ID of the fw image to activate
2620     """
2621     fwID = args.imageID
2622 
2623     #determine the existing versions
2624     url="https://"+host+"/xyz/openbmc_project/software/enumerate"
2625     try:
2626         resp = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
2627     except(requests.exceptions.Timeout):
2628         return connectionErrHandler(args.json, "Timeout", None)
2629     except(requests.exceptions.ConnectionError) as err:
2630         return connectionErrHandler(args.json, "ConnectionError", err)
2631     existingSoftware = json.loads(resp.text)['data']
2632     altVersionID = ''
2633     versionType = ''
2634     imageKey = '/xyz/openbmc_project/software/'+fwID
2635     if imageKey in existingSoftware:
2636         versionType = existingSoftware[imageKey]['Purpose']
2637     for key in existingSoftware:
2638         if imageKey == key:
2639             continue
2640         if 'Purpose' in existingSoftware[key]:
2641             if versionType == existingSoftware[key]['Purpose']:
2642                 altVersionID = key.split('/')[-1]
2643 
2644 
2645 
2646 
2647     url="https://"+host+"/xyz/openbmc_project/software/"+ fwID + "/attr/Priority"
2648     url1="https://"+host+"/xyz/openbmc_project/software/"+ altVersionID + "/attr/Priority"
2649     data = "{\"data\": 0}"
2650     data1 = "{\"data\": 1 }"
2651     try:
2652         resp = session.put(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
2653         resp1 = session.put(url1, headers=jsonHeader, data=data1, verify=False, timeout=baseTimeout)
2654     except(requests.exceptions.Timeout):
2655         return connectionErrHandler(args.json, "Timeout", None)
2656     except(requests.exceptions.ConnectionError) as err:
2657         return connectionErrHandler(args.json, "ConnectionError", err)
2658     if(not args.json):
2659         if resp.status_code == 200 and resp1.status_code == 200:
2660             return 'Firmware flash and activation completed. Please reboot the bmc and then boot the host OS for the changes to take effect. '
2661         else:
2662             return "Firmware activation failed."
2663     else:
2664         return resp.text + resp1.text
2665 
2666 def activateStatus(host, args, session):
2667     if checkFWactivation(host, args, session):
2668         return("Firmware is currently being activated. Do not reboot the BMC or start the Host OS")
2669     else:
2670         return("No firmware activations are pending")
2671 
2672 def extractFWimage(path, imageType):
2673     """
2674          extracts the bmc image and returns information about the package
2675 
2676          @param path: the path and file name of the firmware image
2677          @param imageType: The type of image the user is trying to flash. Host or BMC
2678          @return: the image id associated with the package. returns an empty string on error.
2679     """
2680     f = tempfile.TemporaryFile()
2681     tmpDir = tempfile.gettempdir()
2682     newImageID = ""
2683     if os.path.exists(path):
2684         try:
2685             imageFile = tarfile.open(path,'r')
2686             contents = imageFile.getmembers()
2687             for tf in contents:
2688                 if 'MANIFEST' in tf.name:
2689                     imageFile.extract(tf.name, path=tmpDir)
2690                     with open(tempfile.gettempdir() +os.sep+ tf.name, 'r') as imageInfo:
2691                         for line in imageInfo:
2692                             if 'purpose' in line:
2693                                 purpose = line.split('=')[1]
2694                                 if imageType not in purpose.split('.')[-1]:
2695                                     print('The specified image is not for ' + imageType)
2696                                     print('Please try again with the image for ' + imageType)
2697                                     return ""
2698                             if 'version' == line.split('=')[0]:
2699                                 version = line.split('=')[1].strip().encode('utf-8')
2700                                 m = hashlib.sha512()
2701                                 m.update(version)
2702                                 newImageID = m.hexdigest()[:8]
2703                                 break
2704                     try:
2705                         os.remove(tempfile.gettempdir() +os.sep+ tf.name)
2706                     except OSError:
2707                         pass
2708                     return newImageID
2709         except tarfile.ExtractError as e:
2710             print('Unable to extract information from the firmware file.')
2711             print('Ensure you have write access to the directory: ' + tmpDir)
2712             return newImageID
2713         except tarfile.TarError as e:
2714             print('This is not a valid firmware file.')
2715             return newImageID
2716         print("This is not a valid firmware file.")
2717         return newImageID
2718     else:
2719         print('The filename and path provided are not valid.')
2720         return newImageID
2721 
2722 def getAllFWImageIDs(fwInvDict):
2723     """
2724          gets a list of all the firmware image IDs
2725 
2726          @param fwInvDict: the dictionary to search for FW image IDs
2727          @return: list containing string representation of the found image ids
2728     """
2729     idList = []
2730     for key in fwInvDict:
2731         if 'Version' in fwInvDict[key]:
2732             idList.append(key.split('/')[-1])
2733     return idList
2734 
2735 def fwFlash(host, args, session):
2736     """
2737          updates the bmc firmware and pnor firmware
2738 
2739          @param host: string, the hostname or IP address of the bmc
2740          @param args: contains additional arguments used by the fwflash sub command
2741          @param session: the active session to use
2742     """
2743     d = vars(args)
2744     if(args.type == 'bmc'):
2745         purp = 'BMC'
2746     else:
2747         purp = 'Host'
2748 
2749     #check power state of the machine. No concurrent FW updates allowed
2750     d['powcmd'] = 'status'
2751     powerstate = chassisPower(host, args, session)
2752     if 'Chassis Power State: On' in powerstate:
2753         return("Aborting firmware update. Host is powered on. Please turn off the host and try again.")
2754 
2755     #determine the existing images on the bmc
2756     url="https://"+host+"/xyz/openbmc_project/software/enumerate"
2757     try:
2758         resp = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
2759     except(requests.exceptions.Timeout):
2760         return connectionErrHandler(args.json, "Timeout", None)
2761     except(requests.exceptions.ConnectionError) as err:
2762         return connectionErrHandler(args.json, "ConnectionError", err)
2763     oldsoftware = json.loads(resp.text)['data']
2764 
2765     #Extract the tar and get information from the manifest file
2766     newversionID = extractFWimage(args.fileloc, purp)
2767     if  newversionID == "":
2768         return "Unable to verify FW image."
2769 
2770 
2771     #check if the new image is already on the bmc
2772     if newversionID not in getAllFWImageIDs(oldsoftware):
2773 
2774         #upload the file
2775         httpHeader = {'Content-Type':'application/octet-stream'}
2776         httpHeader.update(xAuthHeader)
2777         url="https://"+host+"/upload/image"
2778         data=open(args.fileloc,'rb').read()
2779         print("Uploading file to BMC")
2780         try:
2781             resp = session.post(url, headers=httpHeader, data=data, verify=False)
2782         except(requests.exceptions.Timeout):
2783             return connectionErrHandler(args.json, "Timeout", None)
2784         except(requests.exceptions.ConnectionError) as err:
2785             return connectionErrHandler(args.json, "ConnectionError", err)
2786         if resp.status_code != 200:
2787             return "Failed to upload the file to the bmc"
2788         else:
2789             print("Upload complete.")
2790 
2791         #verify bmc processed the image
2792         software ={}
2793         for i in range(0, 5):
2794             url="https://"+host+"/xyz/openbmc_project/software/enumerate"
2795             try:
2796                 resp = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
2797             except(requests.exceptions.Timeout):
2798                 return connectionErrHandler(args.json, "Timeout", None)
2799             except(requests.exceptions.ConnectionError) as err:
2800                 return connectionErrHandler(args.json, "ConnectionError", err)
2801             software = json.loads(resp.text)['data']
2802             #check if bmc is done processing the new image
2803             if (newversionID in getAllFWImageIDs(software)):
2804                 break
2805             else:
2806                 time.sleep(15)
2807 
2808         #activate the new image
2809         print("Activating new image: "+newversionID)
2810         url="https://"+host+"/xyz/openbmc_project/software/"+ newversionID + "/attr/RequestedActivation"
2811         data = '{"data":"xyz.openbmc_project.Software.Activation.RequestedActivations.Active"}'
2812         try:
2813             resp = session.put(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
2814         except(requests.exceptions.Timeout):
2815             return connectionErrHandler(args.json, "Timeout", None)
2816         except(requests.exceptions.ConnectionError) as err:
2817             return connectionErrHandler(args.json, "ConnectionError", err)
2818 
2819         #wait for the activation to complete, timeout after ~1 hour
2820         i=0
2821         while i < 360:
2822             url="https://"+host+"/xyz/openbmc_project/software/"+ newversionID
2823             data = '{"data":"xyz.openbmc_project.Software.Activation.RequestedActivations.Active"}'
2824             try:
2825                 resp = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
2826             except(requests.exceptions.Timeout):
2827                 return connectionErrHandler(args.json, "Timeout", None)
2828             except(requests.exceptions.ConnectionError) as err:
2829                 return connectionErrHandler(args.json, "ConnectionError", err)
2830             fwInfo = json.loads(resp.text)['data']
2831             if 'Activating' not in fwInfo['Activation'] and 'Activating' not in fwInfo['RequestedActivation']:
2832                 print('')
2833                 break
2834             else:
2835                 sys.stdout.write('.')
2836                 sys.stdout.flush()
2837                 time.sleep(10) #check every 10 seconds
2838         return "Firmware flash and activation completed. Please reboot the bmc and then boot the host OS for the changes to take effect. "
2839     else:
2840         print("This image has been found on the bmc. Activating image: " + newversionID)
2841 
2842         d['imageID'] = newversionID
2843         return activateFWImage(host, args, session)
2844 
2845 def getFWInventoryAttributes(rawFWInvItem, ID):
2846     """
2847          gets and lists all of the firmware in the system.
2848 
2849          @return: returns a dictionary containing the image attributes
2850     """
2851     reqActivation = rawFWInvItem["RequestedActivation"].split('.')[-1]
2852     pendingActivation = ""
2853     if reqActivation == "None":
2854         pendingActivation = "No"
2855     else:
2856         pendingActivation = "Yes"
2857     firmwareAttr = {ID: {
2858         "Purpose": rawFWInvItem["Purpose"].split('.')[-1],
2859         "Version": rawFWInvItem["Version"],
2860         "RequestedActivation": pendingActivation,
2861         "ID": ID}}
2862 
2863     if "ExtendedVersion" in rawFWInvItem:
2864         firmwareAttr[ID]['ExtendedVersion'] = rawFWInvItem['ExtendedVersion'].split(',')
2865     else:
2866         firmwareAttr[ID]['ExtendedVersion'] = ""
2867     return firmwareAttr
2868 
2869 def parseFWdata(firmwareDict):
2870     """
2871          creates a dictionary with parsed firmware data
2872 
2873          @return: returns a dictionary containing the image attributes
2874     """
2875     firmwareInfoDict = {"Functional": {}, "Activated":{}, "NeedsActivated":{}}
2876     for key in firmwareDict['data']:
2877         #check for valid endpoint
2878         if "Purpose" in firmwareDict['data'][key]:
2879             id = key.split('/')[-1]
2880             if firmwareDict['data'][key]['Activation'].split('.')[-1] == "Active":
2881                 fwActivated = True
2882             else:
2883                 fwActivated = False
2884             if 'Priority' in firmwareDict['data'][key]:
2885                 if firmwareDict['data'][key]['Priority'] == 0:
2886                     firmwareInfoDict['Functional'].update(getFWInventoryAttributes(firmwareDict['data'][key], id))
2887                 elif firmwareDict['data'][key]['Priority'] >= 0 and fwActivated:
2888                     firmwareInfoDict['Activated'].update(getFWInventoryAttributes(firmwareDict['data'][key], id))
2889                 else:
2890                     firmwareInfoDict['NeedsActivated'].update(getFWInventoryAttributes(firmwareDict['data'][key], id))
2891             else:
2892                 firmwareInfoDict['NeedsActivated'].update(getFWInventoryAttributes(firmwareDict['data'][key], id))
2893     emptySections = []
2894     for key in firmwareInfoDict:
2895         if len(firmwareInfoDict[key])<=0:
2896             emptySections.append(key)
2897     for key in emptySections:
2898         del firmwareInfoDict[key]
2899     return firmwareInfoDict
2900 
2901 def displayFWInvenory(firmwareInfoDict, args):
2902     """
2903          gets and lists all of the firmware in the system.
2904 
2905          @return: returns a string containing all of the firmware information
2906     """
2907     output = ""
2908     if not args.json:
2909         for key in firmwareInfoDict:
2910             for subkey in firmwareInfoDict[key]:
2911                 firmwareInfoDict[key][subkey]['ExtendedVersion'] = str(firmwareInfoDict[key][subkey]['ExtendedVersion'])
2912         if not args.verbose:
2913             output = "---Running Images---\n"
2914             colNames = ["Purpose", "Version", "ID"]
2915             keylist = ["Purpose", "Version", "ID"]
2916             output += tableDisplay(keylist, colNames, firmwareInfoDict["Functional"])
2917             if "Activated" in firmwareInfoDict:
2918                 output += "\n---Available Images---\n"
2919                 output += tableDisplay(keylist, colNames, firmwareInfoDict["Activated"])
2920             if "NeedsActivated" in firmwareInfoDict:
2921                 output += "\n---Needs Activated Images---\n"
2922                 output += tableDisplay(keylist, colNames, firmwareInfoDict["NeedsActivated"])
2923 
2924         else:
2925             output = "---Running Images---\n"
2926             colNames = ["Purpose", "Version", "ID", "Pending Activation", "Extended Version"]
2927             keylist = ["Purpose", "Version", "ID", "RequestedActivation", "ExtendedVersion"]
2928             output += tableDisplay(keylist, colNames, firmwareInfoDict["Functional"])
2929             if "Activated" in firmwareInfoDict:
2930                 output += "\n---Available Images---\n"
2931                 output += tableDisplay(keylist, colNames, firmwareInfoDict["Activated"])
2932             if "NeedsActivated" in firmwareInfoDict:
2933                 output += "\n---Needs Activated Images---\n"
2934                 output += tableDisplay(keylist, colNames, firmwareInfoDict["NeedsActivated"])
2935         return output
2936     else:
2937         return str(json.dumps(firmwareInfoDict, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False))
2938 
2939 def firmwareList(host, args, session):
2940     """
2941          gets and lists all of the firmware in the system.
2942 
2943          @return: returns a string containing all of the firmware information
2944     """
2945     url="https://{hostname}/xyz/openbmc_project/software/enumerate".format(hostname=host)
2946     try:
2947         res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
2948     except(requests.exceptions.Timeout):
2949         return(connectionErrHandler(args.json, "Timeout", None))
2950     firmwareDict = json.loads(res.text)
2951 
2952     #sort the received information
2953     firmwareInfoDict = parseFWdata(firmwareDict)
2954 
2955     #display the information
2956     return displayFWInvenory(firmwareInfoDict, args)
2957 
2958 
2959 def deleteFWVersion(host, args, session):
2960     """
2961          deletes a firmware version on the BMC
2962 
2963          @param host: string, the hostname or IP address of the BMC
2964          @param args: contains additional arguments used by the fwflash sub command
2965          @param session: the active session to use
2966          @param fwID: the unique ID of the fw version to delete
2967     """
2968     fwID = args.versionID
2969 
2970     print("Deleting version: "+fwID)
2971     url="https://"+host+"/xyz/openbmc_project/software/"+ fwID + "/action/Delete"
2972     data = "{\"data\": [] }"
2973 
2974     try:
2975         res = session.post(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
2976     except(requests.exceptions.Timeout):
2977         return(connectionErrHandler(args.json, "Timeout", None))
2978     if res.status_code == 200:
2979         return ('The firmware version has been deleted')
2980     else:
2981         return ('Unable to delete the specified firmware version')
2982 
2983 def deleteFWAll(host, args, session):
2984     """
2985          deletes ALL contents for firmware software catalog
2986 
2987          @param host: string, the hostname or IP address of the BMC
2988          @param args: contains additional arguments used by the fwflash sub command
2989          @param session: the active session to use
2990     """
2991 
2992     print("Deleting ALL firmware versions")
2993     url="https://"+host+"/xyz/openbmc_project/software/action/DeleteAll"
2994     data = "{\"data\": [] }"
2995 
2996     try:
2997         res = session.post(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
2998     except(requests.exceptions.Timeout):
2999         return(connectionErrHandler(args.json, "Timeout", None))
3000     if res.status_code == 200:
3001         return ('All firmware versions were deleted')
3002     else:
3003         return ('Uspecified error while deleting All firmware versions')
3004 
3005 
3006 def restLogging(host, args, session):
3007     """
3008          Called by the logging function. Turns REST API logging on/off.
3009 
3010          @param host: string, the hostname or IP address of the bmc
3011          @param args: contains additional arguments used by the logging sub command
3012          @param session: the active session to use
3013          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
3014     """
3015     url="https://"+host+"/xyz/openbmc_project/logging/rest_api_logs/attr/Enabled"
3016 
3017     if(args.rest_logging == 'on'):
3018         data = '{"data": 1}'
3019     elif(args.rest_logging == 'off'):
3020         data = '{"data": 0}'
3021     else:
3022         return "Invalid logging rest_api command"
3023 
3024     try:
3025         res = session.put(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
3026     except(requests.exceptions.Timeout):
3027         return(connectionErrHandler(args.json, "Timeout", None))
3028     return res.text
3029 
3030 
3031 def remoteLogging(host, args, session):
3032     """
3033          Called by the logging function. View config information for/disable remote logging (rsyslog).
3034 
3035          @param host: string, the hostname or IP address of the bmc
3036          @param args: contains additional arguments used by the logging sub command
3037          @param session: the active session to use
3038          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
3039     """
3040 
3041     url="https://"+host+"/xyz/openbmc_project/logging/config/remote"
3042 
3043     try:
3044         if(args.remote_logging == 'view'):
3045             res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
3046         elif(args.remote_logging == 'disable'):
3047             res = session.put(url + '/attr/Port', headers=jsonHeader, json = {"data": 0}, verify=False, timeout=baseTimeout)
3048             res = session.put(url + '/attr/Address', headers=jsonHeader, json = {"data": ""}, verify=False, timeout=baseTimeout)
3049         else:
3050             return "Invalid logging remote_logging command"
3051     except(requests.exceptions.Timeout):
3052         return(connectionErrHandler(args.json, "Timeout", None))
3053     return res.text
3054 
3055 
3056 def remoteLoggingConfig(host, args, session):
3057     """
3058          Called by the logging function. Configures remote logging (rsyslog).
3059 
3060          @param host: string, the hostname or IP address of the bmc
3061          @param args: contains additional arguments used by the logging sub command
3062          @param session: the active session to use
3063          @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
3064     """
3065 
3066     url="https://"+host+"/xyz/openbmc_project/logging/config/remote"
3067 
3068     try:
3069         res = session.put(url + '/attr/Port', headers=jsonHeader, json = {"data": args.port}, verify=False, timeout=baseTimeout)
3070         res = session.put(url + '/attr/Address', headers=jsonHeader, json = {"data": args.address}, verify=False, timeout=baseTimeout)
3071     except(requests.exceptions.Timeout):
3072         return(connectionErrHandler(args.json, "Timeout", None))
3073     return res.text
3074 
3075 def redfishSupportPresent(host, session):
3076     url = "https://" + host + "/redfish/v1"
3077     try:
3078         resp = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
3079     except(requests.exceptions.Timeout):
3080         return False
3081     except(requests.exceptions.ConnectionError) as err:
3082         return False
3083     if resp.status_code != 200:
3084         return False
3085     else:
3086        return True
3087 
3088 def certificateUpdate(host, args, session):
3089     """
3090          Called by certificate management function. update server/client/authority certificates
3091          Example:
3092          certificate update server https -f cert.pem
3093          certificate update authority ldap -f Root-CA.pem
3094          certificate update client ldap -f cert.pem
3095          @param host: string, the hostname or IP address of the bmc
3096          @param args: contains additional arguments used by the certificate update sub command
3097          @param session: the active session to use
3098     """
3099     httpHeader = {'Content-Type': 'application/octet-stream'}
3100     httpHeader.update(xAuthHeader)
3101     data = open(args.fileloc, 'r').read()
3102     try:
3103         if redfishSupportPresent(host, session):
3104             if(args.type.lower() == 'server' and args.service.lower() != "https"):
3105                 return "Invalid service type"
3106             if(args.type.lower() == 'client' and args.service.lower() != "ldap"):
3107                 return "Invalid service type"
3108             if(args.type.lower() == 'authority' and args.service.lower() != "ldap"):
3109                 return "Invalid service type"
3110             url = "";
3111             if(args.type.lower() == 'server'):
3112                 url = "https://" + host + \
3113                     "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"
3114             elif(args.type.lower() == 'client'):
3115                 url = "https://" + host + \
3116                     "/redfish/v1/AccountService/LDAP/Certificates"
3117             elif(args.type.lower() == 'authority'):
3118                 url = "https://" + host + \
3119                 "/redfish/v1/Managers/bmc/Truststore/Certificates"
3120             else:
3121                 return "Unsupported certificate type"
3122             resp = session.post(url, headers=httpHeader, data=data,
3123                         verify=False)
3124         else:
3125             url = "https://" + host + "/xyz/openbmc_project/certs/" + \
3126                 args.type.lower() + "/" + args.service.lower()
3127             resp = session.put(url, headers=httpHeader, data=data, verify=False)
3128     except(requests.exceptions.Timeout):
3129         return(connectionErrHandler(args.json, "Timeout", None))
3130     except(requests.exceptions.ConnectionError) as err:
3131         return connectionErrHandler(args.json, "ConnectionError", err)
3132     if resp.status_code != 200:
3133         print(resp.text)
3134         return "Failed to update the certificate"
3135     else:
3136         print("Update complete.")
3137 
3138 def certificateDelete(host, args, session):
3139     """
3140          Called by certificate management function to delete certificate
3141          Example:
3142          certificate delete server https
3143          certificate delete authority ldap
3144          certificate delete client ldap
3145          @param host: string, the hostname or IP address of the bmc
3146          @param args: contains additional arguments used by the certificate delete sub command
3147          @param session: the active session to use
3148     """
3149     if redfishSupportPresent(host, session):
3150         return "Not supported, please use certificate replace instead";
3151     httpHeader = {'Content-Type': 'multipart/form-data'}
3152     httpHeader.update(xAuthHeader)
3153     url = "https://" + host + "/xyz/openbmc_project/certs/" + args.type.lower() + "/" + args.service.lower()
3154     print("Deleting certificate url=" + url)
3155     try:
3156         resp = session.delete(url, headers=httpHeader)
3157     except(requests.exceptions.Timeout):
3158         return(connectionErrHandler(args.json, "Timeout", None))
3159     except(requests.exceptions.ConnectionError) as err:
3160         return connectionErrHandler(args.json, "ConnectionError", err)
3161     if resp.status_code != 200:
3162         print(resp.text)
3163         return "Failed to delete the certificate"
3164     else:
3165         print("Delete complete.")
3166 
3167 def certificateReplace(host, args, session):
3168     """
3169          Called by certificate management function. replace server/client/
3170          authority certificates
3171          Example:
3172          certificate replace server https -f cert.pem
3173          certificate replace authority ldap -f Root-CA.pem
3174          certificate replace client ldap -f cert.pem
3175          @param host: string, the hostname or IP address of the bmc
3176          @param args: contains additional arguments used by the certificate
3177                       replace sub command
3178          @param session: the active session to use
3179     """
3180     cert = open(args.fileloc, 'r').read()
3181     try:
3182         if redfishSupportPresent(host, session):
3183             httpHeader = {'Content-Type': 'application/json'}
3184             httpHeader.update(xAuthHeader)
3185             url = "";
3186             if(args.type.lower() == 'server' and args.service.lower() != "https"):
3187                 return "Invalid service type"
3188             if(args.type.lower() == 'client' and args.service.lower() != "ldap"):
3189                 return "Invalid service type"
3190             if(args.type.lower() == 'authority' and args.service.lower() != "ldap"):
3191                 return "Invalid service type"
3192             if(args.type.lower() == 'server'):
3193                 url = "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/1"
3194             elif(args.type.lower() == 'client'):
3195                 url = "/redfish/v1/AccountService/LDAP/Certificates/1"
3196             elif(args.type.lower() == 'authority'):
3197                 url = "/redfish/v1/Managers/bmc/Truststore/Certificates/1"
3198             replaceUrl = "https://" + host + \
3199                 "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate"
3200             data ={"CertificateUri":{"@odata.id":url}, "CertificateType":"PEM",
3201                     "CertificateString":cert}
3202             resp = session.post(replaceUrl, headers=httpHeader, json=data, verify=False)
3203         else:
3204             httpHeader = {'Content-Type': 'application/octet-stream'}
3205             httpHeader.update(xAuthHeader)
3206             url = "https://" + host + "/xyz/openbmc_project/certs/" + \
3207                 args.type.lower() + "/" + args.service.lower()
3208             resp = session.delete(url, headers=httpHeader)
3209             resp = session.put(url, headers=httpHeader, data=cert, verify=False)
3210     except(requests.exceptions.Timeout):
3211         return(connectionErrHandler(args.json, "Timeout", None))
3212     except(requests.exceptions.ConnectionError) as err:
3213         return connectionErrHandler(args.json, "ConnectionError", err)
3214     if resp.status_code != 200:
3215         print(resp.text)
3216         return "Failed to replace the certificate"
3217     else:
3218         print("Replace complete.")
3219     return resp.text
3220 
3221 def certificateDisplay(host, args, session):
3222     """
3223          Called by certificate management function. display server/client/
3224          authority certificates
3225          Example:
3226          certificate display server
3227          certificate display authority
3228          certificate display client
3229          @param host: string, the hostname or IP address of the bmc
3230          @param args: contains additional arguments used by the certificate
3231                       display sub command
3232          @param session: the active session to use
3233     """
3234     if not redfishSupportPresent(host, session):
3235         return "Not supported";
3236 
3237     httpHeader = {'Content-Type': 'application/octet-stream'}
3238     httpHeader.update(xAuthHeader)
3239     if(args.type.lower() == 'server'):
3240         url = "https://" + host + \
3241             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/1"
3242     elif(args.type.lower() == 'client'):
3243         url = "https://" + host + \
3244             "/redfish/v1/AccountService/LDAP/Certificates/1"
3245     elif(args.type.lower() == 'authority'):
3246         url = "https://" + host + \
3247             "/redfish/v1/Managers/bmc/Truststore/Certificates/1"
3248     try:
3249         resp = session.get(url, headers=httpHeader, verify=False)
3250     except(requests.exceptions.Timeout):
3251         return(connectionErrHandler(args.json, "Timeout", None))
3252     except(requests.exceptions.ConnectionError) as err:
3253         return connectionErrHandler(args.json, "ConnectionError", err)
3254     if resp.status_code != 200:
3255         print(resp.text)
3256         return "Failed to display the certificate"
3257     else:
3258         print("Display complete.")
3259     return resp.text
3260 
3261 def certificateList(host, args, session):
3262     """
3263          Called by certificate management function.
3264          Example:
3265          certificate list
3266          @param host: string, the hostname or IP address of the bmc
3267          @param args: contains additional arguments used by the certificate
3268                       list sub command
3269          @param session: the active session to use
3270     """
3271     if not redfishSupportPresent(host, session):
3272         return "Not supported";
3273 
3274     httpHeader = {'Content-Type': 'application/octet-stream'}
3275     httpHeader.update(xAuthHeader)
3276     url = "https://" + host + \
3277         "/redfish/v1/CertificateService/CertificateLocations/"
3278     try:
3279         resp = session.get(url, headers=httpHeader, verify=False)
3280     except(requests.exceptions.Timeout):
3281         return(connectionErrHandler(args.json, "Timeout", None))
3282     except(requests.exceptions.ConnectionError) as err:
3283         return connectionErrHandler(args.json, "ConnectionError", err)
3284     if resp.status_code != 200:
3285         print(resp.text)
3286         return "Failed to list certificates"
3287     else:
3288         print("List certificates complete.")
3289     return resp.text
3290 
3291 def certificateGenerateCSR(host, args, session):
3292     """
3293         Called by certificate management function. Generate CSR for server/
3294         client certificates
3295         Example:
3296         certificate generatecsr server NJ w3.ibm.com US IBM IBM-UNIT NY EC prime256v1 cp abc.com an.com,bm.com gn sn un in
3297         certificate generatecsr client NJ w3.ibm.com US IBM IBM-UNIT NY EC prime256v1 cp abc.com an.com,bm.com gn sn un in
3298         @param host: string, the hostname or IP address of the bmc
3299         @param args: contains additional arguments used by the certificate replace sub command
3300         @param session: the active session to use
3301     """
3302     if not redfishSupportPresent(host, session):
3303         return "Not supported";
3304 
3305     httpHeader = {'Content-Type': 'application/octet-stream'}
3306     httpHeader.update(xAuthHeader)
3307     url = "";
3308     if(args.type.lower() == 'server'):
3309         url = "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/"
3310         usage_list = ["ServerAuthentication"]
3311     elif(args.type.lower() == 'client'):
3312         url = "/redfish/v1/AccountService/LDAP/Certificates/"
3313         usage_list = ["ClientAuthentication"]
3314     elif(args.type.lower() == 'authority'):
3315         url = "/redfish/v1/Managers/bmc/Truststore/Certificates/"
3316     print("Generating CSR url=" + url)
3317     generateCSRUrl = "https://" + host + \
3318         "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR"
3319     try:
3320         alt_name_list = args.alternativeNames.split(",")
3321         data ={"CertificateCollection":{"@odata.id":url},
3322             "CommonName":args.commonName, "City":args.city,
3323             "Country":args.country, "Organization":args.organization,
3324             "OrganizationalUnit":args.organizationUnit, "State":args.state,
3325             "KeyPairAlgorithm":args.keyPairAlgorithm, "KeyCurveId":args.keyCurveId,
3326             "AlternativeNames":alt_name_list, "ContactPerson":args.contactPerson,
3327             "Email":args.email, "GivenName":args.givenname, "Initials":args.initials,
3328             "KeyUsage":usage_list, "Surname":args.surname,
3329             "UnstructuredName":args.unstructuredname}
3330         resp = session.post(generateCSRUrl, headers=httpHeader,
3331             json=data, verify=False)
3332     except(requests.exceptions.Timeout):
3333         return(connectionErrHandler(args.json, "Timeout", None))
3334     except(requests.exceptions.ConnectionError) as err:
3335         return connectionErrHandler(args.json, "ConnectionError", err)
3336     if resp.status_code != 200:
3337         print(resp.text)
3338         return "Failed to generate CSR"
3339     else:
3340         print("GenerateCSR complete.")
3341     return resp.text
3342 
3343 def enableLDAPConfig(host, args, session):
3344     """
3345          Called by the ldap function. Configures LDAP.
3346 
3347          @param host: string, the hostname or IP address of the bmc
3348          @param args: contains additional arguments used by the ldap subcommand
3349          @param session: the active session to use
3350          @param args.json: boolean, if this flag is set to true, the output will
3351             be provided in json format for programmatic consumption
3352     """
3353 
3354     if(isRedfishSupport):
3355         return enableLDAP(host, args, session)
3356     else:
3357         return enableLegacyLDAP(host, args, session)
3358 
3359 def enableLegacyLDAP(host, args, session):
3360     """
3361          Called by the ldap function. Configures LDAP on Lagecy systems.
3362 
3363          @param host: string, the hostname or IP address of the bmc
3364          @param args: contains additional arguments used by the ldap subcommand
3365          @param session: the active session to use
3366          @param args.json: boolean, if this flag is set to true, the output will
3367             be provided in json format for programmatic consumption
3368     """
3369 
3370     url='https://'+host+'/xyz/openbmc_project/user/ldap/action/CreateConfig'
3371     scope = {
3372              'sub' : 'xyz.openbmc_project.User.Ldap.Create.SearchScope.sub',
3373              'one' : 'xyz.openbmc_project.User.Ldap.Create.SearchScope.one',
3374              'base': 'xyz.openbmc_project.User.Ldap.Create.SearchScope.base'
3375             }
3376 
3377     serverType = {
3378              'ActiveDirectory' : 'xyz.openbmc_project.User.Ldap.Create.Type.ActiveDirectory',
3379              'OpenLDAP' : 'xyz.openbmc_project.User.Ldap.Create.Type.OpenLdap'
3380             }
3381 
3382     data = {"data": [args.uri, args.bindDN, args.baseDN, args.bindPassword, scope[args.scope], serverType[args.serverType]]}
3383 
3384     try:
3385         res = session.post(url, headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
3386     except(requests.exceptions.Timeout):
3387         return(connectionErrHandler(args.json, "Timeout", None))
3388     except(requests.exceptions.ConnectionError) as err:
3389         return connectionErrHandler(args.json, "ConnectionError", err)
3390 
3391     return res.text
3392 
3393 def enableLDAP(host, args, session):
3394     """
3395          Called by the ldap function. Configures LDAP for systems with latest user-manager design changes
3396 
3397          @param host: string, the hostname or IP address of the bmc
3398          @param args: contains additional arguments used by the ldap subcommand
3399          @param session: the active session to use
3400          @param args.json: boolean, if this flag is set to true, the output will
3401             be provided in json format for programmatic consumption
3402     """
3403 
3404     scope = {
3405              'sub' : 'xyz.openbmc_project.User.Ldap.Config.SearchScope.sub',
3406              'one' : 'xyz.openbmc_project.User.Ldap.Config.SearchScope.one',
3407              'base': 'xyz.openbmc_project.User.Ldap.Config.SearchScope.base'
3408             }
3409 
3410     serverType = {
3411             'ActiveDirectory' : 'xyz.openbmc_project.User.Ldap.Config.Type.ActiveDirectory',
3412             'OpenLDAP' : 'xyz.openbmc_project.User.Ldap.Config.Type.OpenLdap'
3413             }
3414 
3415     url = "https://"+host+"/xyz/openbmc_project/user/ldap/"
3416 
3417     serverTypeEnabled = getLDAPTypeEnabled(host,session)
3418     serverTypeToBeEnabled = args.serverType
3419 
3420     #If the given LDAP type is already enabled, then return
3421     if (serverTypeToBeEnabled == serverTypeEnabled):
3422       return("Server type " + serverTypeToBeEnabled + " is already enabled...")
3423 
3424     try:
3425 
3426         #  Copy the role map from the currently enabled LDAP server type
3427         #  to the newly enabled server type
3428         #  Disable the currently enabled LDAP server type. Unless
3429         #  it is disabled, we cannot enable a new LDAP server type
3430         if (serverTypeEnabled is not None):
3431 
3432             if (serverTypeToBeEnabled != serverTypeEnabled):
3433                 res = syncRoleMap(host,args,session,serverTypeEnabled,serverTypeToBeEnabled)
3434 
3435             data = "{\"data\": 0 }"
3436             res = session.put(url + serverTypeMap[serverTypeEnabled] + '/attr/Enabled', headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
3437 
3438         data = {"data": args.baseDN}
3439         res = session.put(url + serverTypeMap[serverTypeToBeEnabled] + '/attr/LDAPBaseDN', headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
3440         if (res.status_code != requests.codes.ok):
3441             print("Updates to the property LDAPBaseDN failed...")
3442             return(res.text)
3443 
3444         data = {"data": args.bindDN}
3445         res = session.put(url + serverTypeMap[serverTypeToBeEnabled] + '/attr/LDAPBindDN', headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
3446         if (res.status_code != requests.codes.ok):
3447            print("Updates to the property LDAPBindDN failed...")
3448            return(res.text)
3449 
3450         data = {"data": args.bindPassword}
3451         res = session.put(url + serverTypeMap[serverTypeToBeEnabled] + '/attr/LDAPBindDNPassword', headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
3452         if (res.status_code != requests.codes.ok):
3453            print("Updates to the property LDAPBindDNPassword failed...")
3454            return(res.text)
3455 
3456         data = {"data": scope[args.scope]}
3457         res = session.put(url + serverTypeMap[serverTypeToBeEnabled] + '/attr/LDAPSearchScope', headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
3458         if (res.status_code != requests.codes.ok):
3459            print("Updates to the property LDAPSearchScope failed...")
3460            return(res.text)
3461 
3462         data = {"data": args.uri}
3463         res = session.put(url + serverTypeMap[serverTypeToBeEnabled] + '/attr/LDAPServerURI', headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
3464         if (res.status_code != requests.codes.ok):
3465            print("Updates to the property LDAPServerURI failed...")
3466            return(res.text)
3467 
3468         data = {"data": args.groupAttrName}
3469         res = session.put(url + serverTypeMap[serverTypeToBeEnabled] + '/attr/GroupNameAttribute', headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
3470         if (res.status_code != requests.codes.ok):
3471            print("Updates to the property GroupNameAttribute failed...")
3472            return(res.text)
3473 
3474         data = {"data": args.userAttrName}
3475         res = session.put(url + serverTypeMap[serverTypeToBeEnabled] + '/attr/UserNameAttribute', headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
3476         if (res.status_code != requests.codes.ok):
3477            print("Updates to the property UserNameAttribute failed...")
3478            return(res.text)
3479 
3480         #After updating the properties, enable the new server type
3481         data = "{\"data\": 1 }"
3482         res = session.put(url + serverTypeMap[serverTypeToBeEnabled] + '/attr/Enabled', headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
3483 
3484     except(requests.exceptions.Timeout):
3485         return(connectionErrHandler(args.json, "Timeout", None))
3486     except(requests.exceptions.ConnectionError) as err:
3487         return connectionErrHandler(args.json, "ConnectionError", err)
3488     return res.text
3489 
3490 def disableLDAP(host, args, session):
3491     """
3492          Called by the ldap function. Deletes the LDAP Configuration.
3493 
3494          @param host: string, the hostname or IP address of the bmc
3495          @param args: contains additional arguments used by the ldap subcommand
3496          @param session: the active session to use
3497          @param args.json: boolean, if this flag is set to true, the output
3498             will be provided in json format for programmatic consumption
3499     """
3500 
3501     try:
3502         if (isRedfishSupport) :
3503 
3504             url = "https://"+host+"/xyz/openbmc_project/user/ldap/"
3505 
3506             serverTypeEnabled = getLDAPTypeEnabled(host,session)
3507 
3508             if (serverTypeEnabled is not None):
3509                 #To keep the role map in sync,
3510                 #If the server type being disabled has role map, then
3511                 #   - copy the role map to the other server type(s)
3512                 for serverType in serverTypeMap.keys():
3513                     if (serverType != serverTypeEnabled):
3514                         res = syncRoleMap(host,args,session,serverTypeEnabled,serverType)
3515 
3516                 #Disable the currently enabled LDAP server type
3517                 data = "{\"data\": 0 }"
3518                 res = session.put(url + serverTypeMap[serverTypeEnabled] + '/attr/Enabled', headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
3519 
3520             else:
3521                 return("LDAP server has not been enabled...")
3522 
3523         else :
3524             url='https://'+host+'/xyz/openbmc_project/user/ldap/config/action/delete'
3525             data = {"data": []}
3526             res = session.post(url, headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
3527 
3528     except(requests.exceptions.Timeout):
3529         return(connectionErrHandler(args.json, "Timeout", None))
3530     except(requests.exceptions.ConnectionError) as err:
3531         return connectionErrHandler(args.json, "ConnectionError", err)
3532 
3533     return res.text
3534 
3535 def enableDHCP(host, args, session):
3536 
3537     """
3538         Called by the network function. Enables DHCP.
3539 
3540         @param host: string, the hostname or IP address of the bmc
3541         @param args: contains additional arguments used by the ldap subcommand
3542                 args.json: boolean, if this flag is set to true, the output
3543                 will be provided in json format for programmatic consumption
3544         @param session: the active session to use
3545     """
3546 
3547     url = "https://"+host+"/xyz/openbmc_project/network/"+args.Interface+\
3548     "/attr/DHCPEnabled"
3549     data = "{\"data\": 1 }"
3550     try:
3551         res = session.put(url, headers=jsonHeader, data=data, verify=False,
3552                           timeout=baseTimeout)
3553 
3554     except(requests.exceptions.Timeout):
3555         return(connectionErrHandler(args.json, "Timeout", None))
3556     except(requests.exceptions.ConnectionError) as err:
3557         return connectionErrHandler(args.json, "ConnectionError", err)
3558     if res.status_code == 403:
3559         return "The specified Interface"+"("+args.Interface+")"+\
3560         " doesn't exist"
3561 
3562     return res.text
3563 
3564 
3565 def disableDHCP(host, args, session):
3566     """
3567         Called by the network function. Disables DHCP.
3568 
3569         @param host: string, the hostname or IP address of the bmc
3570         @param args: contains additional arguments used by the ldap subcommand
3571                 args.json: boolean, if this flag is set to true, the output
3572                 will be provided in json format for programmatic consumption
3573         @param session: the active session to use
3574     """
3575 
3576     url = "https://"+host+"/xyz/openbmc_project/network/"+args.Interface+\
3577     "/attr/DHCPEnabled"
3578     data = "{\"data\": 0 }"
3579     try:
3580         res = session.put(url, headers=jsonHeader, data=data, verify=False,
3581                           timeout=baseTimeout)
3582     except(requests.exceptions.Timeout):
3583         return(connectionErrHandler(args.json, "Timeout", None))
3584     except(requests.exceptions.ConnectionError) as err:
3585         return connectionErrHandler(args.json, "ConnectionError", err)
3586     if res.status_code == 403:
3587         return "The specified Interface"+"("+args.Interface+")"+\
3588         " doesn't exist"
3589     return res.text
3590 
3591 
3592 def getHostname(host, args, session):
3593 
3594     """
3595         Called by the network function. Prints out the Hostname.
3596 
3597         @param host: string, the hostname or IP address of the bmc
3598         @param args: contains additional arguments used by the ldap subcommand
3599                 args.json: boolean, if this flag is set to true, the output
3600                 will be provided in json format for programmatic consumption
3601         @param session: the active session to use
3602     """
3603 
3604     url = "https://"+host+"/xyz/openbmc_project/network/config/attr/HostName"
3605 
3606     try:
3607         res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
3608     except(requests.exceptions.Timeout):
3609         return(connectionErrHandler(args.json, "Timeout", None))
3610     except(requests.exceptions.ConnectionError) as err:
3611         return connectionErrHandler(args.json, "ConnectionError", err)
3612 
3613     return res.text
3614 
3615 
3616 def setHostname(host, args, session):
3617     """
3618         Called by the network function. Sets the Hostname.
3619 
3620         @param host: string, the hostname or IP address of the bmc
3621         @param args: contains additional arguments used by the ldap subcommand
3622                 args.json: boolean, if this flag is set to true, the output
3623                 will be provided in json format for programmatic consumption
3624         @param session: the active session to use
3625     """
3626 
3627     url = "https://"+host+"/xyz/openbmc_project/network/config/attr/HostName"
3628 
3629     data = {"data": args.HostName}
3630 
3631     try:
3632         res = session.put(url, headers=jsonHeader, json=data, verify=False,
3633                           timeout=baseTimeout)
3634     except(requests.exceptions.Timeout):
3635         return(connectionErrHandler(args.json, "Timeout", None))
3636     except(requests.exceptions.ConnectionError) as err:
3637         return connectionErrHandler(args.json, "ConnectionError", err)
3638 
3639     return res.text
3640 
3641 
3642 def getDomainName(host, args, session):
3643 
3644     """
3645         Called by the network function. Prints out the DomainName.
3646 
3647         @param host: string, the hostname or IP address of the bmc
3648         @param args: contains additional arguments used by the ldap subcommand
3649                 args.json: boolean, if this flag is set to true, the output
3650                 will be provided in json format for programmatic consumption
3651         @param session: the active session to use
3652     """
3653 
3654     url = "https://"+host+"/xyz/openbmc_project/network/"+args.Interface+\
3655     "/attr/DomainName"
3656 
3657     try:
3658         res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
3659     except(requests.exceptions.Timeout):
3660         return(connectionErrHandler(args.json, "Timeout", None))
3661     except(requests.exceptions.ConnectionError) as err:
3662         return connectionErrHandler(args.json, "ConnectionError", err)
3663     if res.status_code == 404:
3664         return "The DomainName is not configured on Interface"+"("+args.Interface+")"
3665 
3666     return res.text
3667 
3668 
3669 def setDomainName(host, args, session):
3670     """
3671         Called by the network function. Sets the DomainName.
3672 
3673         @param host: string, the hostname or IP address of the bmc
3674         @param args: contains additional arguments used by the ldap subcommand
3675                 args.json: boolean, if this flag is set to true, the output
3676                 will be provided in json format for programmatic consumption
3677         @param session: the active session to use
3678     """
3679 
3680     url = "https://"+host+"/xyz/openbmc_project/network/"+args.Interface+\
3681     "/attr/DomainName"
3682 
3683     data = {"data": args.DomainName.split(",")}
3684 
3685     try:
3686         res = session.put(url, headers=jsonHeader, json=data, verify=False,
3687                           timeout=baseTimeout)
3688     except(requests.exceptions.Timeout):
3689         return(connectionErrHandler(args.json, "Timeout", None))
3690     except(requests.exceptions.ConnectionError) as err:
3691         return connectionErrHandler(args.json, "ConnectionError", err)
3692     if res.status_code == 403:
3693         return "Failed to set Domain Name"
3694 
3695     return res.text
3696 
3697 
3698 def getMACAddress(host, args, session):
3699 
3700     """
3701         Called by the network function. Prints out the MACAddress.
3702 
3703         @param host: string, the hostname or IP address of the bmc
3704         @param args: contains additional arguments used by the ldap subcommand
3705                 args.json: boolean, if this flag is set to true, the output
3706                 will be provided in json format for programmatic consumption
3707         @param session: the active session to use
3708     """
3709 
3710     url = "https://"+host+"/xyz/openbmc_project/network/"+args.Interface+\
3711     "/attr/MACAddress"
3712 
3713     try:
3714         res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
3715     except(requests.exceptions.Timeout):
3716         return(connectionErrHandler(args.json, "Timeout", None))
3717     except(requests.exceptions.ConnectionError) as err:
3718         return connectionErrHandler(args.json, "ConnectionError", err)
3719     if res.status_code == 404:
3720         return "Failed to get MACAddress"
3721 
3722     return res.text
3723 
3724 
3725 def setMACAddress(host, args, session):
3726     """
3727         Called by the network function. Sets the MACAddress.
3728 
3729         @param host: string, the hostname or IP address of the bmc
3730         @param args: contains additional arguments used by the ldap subcommand
3731                 args.json: boolean, if this flag is set to true, the output
3732                 will be provided in json format for programmatic consumption
3733         @param session: the active session to use
3734     """
3735 
3736     url = "https://"+host+"/xyz/openbmc_project/network/"+args.Interface+\
3737     "/attr/MACAddress"
3738 
3739     data = {"data": args.MACAddress}
3740 
3741     try:
3742         res = session.put(url, headers=jsonHeader, json=data, verify=False,
3743                           timeout=baseTimeout)
3744     except(requests.exceptions.Timeout):
3745         return(connectionErrHandler(args.json, "Timeout", None))
3746     except(requests.exceptions.ConnectionError) as err:
3747         return connectionErrHandler(args.json, "ConnectionError", err)
3748     if res.status_code == 403:
3749         return "Failed to set MACAddress"
3750 
3751     return res.text
3752 
3753 
3754 def getDefaultGateway(host, args, session):
3755 
3756     """
3757         Called by the network function. Prints out the DefaultGateway.
3758 
3759         @param host: string, the hostname or IP address of the bmc
3760         @param args: contains additional arguments used by the ldap subcommand
3761                 args.json: boolean, if this flag is set to true, the output
3762                 will be provided in json format for programmatic consumption
3763         @param session: the active session to use
3764     """
3765 
3766     url = "https://"+host+"/xyz/openbmc_project/network/config/attr/DefaultGateway"
3767 
3768     try:
3769         res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
3770     except(requests.exceptions.Timeout):
3771         return(connectionErrHandler(args.json, "Timeout", None))
3772     except(requests.exceptions.ConnectionError) as err:
3773         return connectionErrHandler(args.json, "ConnectionError", err)
3774     if res.status_code == 404:
3775         return "Failed to get Default Gateway info"
3776 
3777     return res.text
3778 
3779 
3780 def setDefaultGateway(host, args, session):
3781     """
3782         Called by the network function. Sets the DefaultGateway.
3783 
3784         @param host: string, the hostname or IP address of the bmc
3785         @param args: contains additional arguments used by the ldap subcommand
3786                 args.json: boolean, if this flag is set to true, the output
3787                 will be provided in json format for programmatic consumption
3788         @param session: the active session to use
3789     """
3790 
3791     url = "https://"+host+"/xyz/openbmc_project/network/config/attr/DefaultGateway"
3792 
3793     data = {"data": args.DefaultGW}
3794 
3795     try:
3796         res = session.put(url, headers=jsonHeader, json=data, verify=False,
3797                           timeout=baseTimeout)
3798     except(requests.exceptions.Timeout):
3799         return(connectionErrHandler(args.json, "Timeout", None))
3800     except(requests.exceptions.ConnectionError) as err:
3801         return connectionErrHandler(args.json, "ConnectionError", err)
3802     if res.status_code == 403:
3803         return "Failed to set Default Gateway"
3804 
3805     return res.text
3806 
3807 
3808 def viewNWConfig(host, args, session):
3809     """
3810          Called by the ldap function. Prints out network configured properties
3811 
3812          @param host: string, the hostname or IP address of the bmc
3813          @param args: contains additional arguments used by the ldap subcommand
3814                 args.json: boolean, if this flag is set to true, the output
3815                 will be provided in json format for programmatic consumption
3816          @param session: the active session to use
3817          @return returns LDAP's configured properties.
3818     """
3819     url = "https://"+host+"/xyz/openbmc_project/network/enumerate"
3820     try:
3821         res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
3822     except(requests.exceptions.Timeout):
3823         return(connectionErrHandler(args.json, "Timeout", None))
3824     except(requests.exceptions.ConnectionError) as err:
3825         return connectionErrHandler(args.json, "ConnectionError", err)
3826     except(requests.exceptions.RequestException) as err:
3827         return connectionErrHandler(args.json, "RequestException", err)
3828     if res.status_code == 404:
3829         return "LDAP server config has not been created"
3830     return res.text
3831 
3832 
3833 def getDNS(host, args, session):
3834 
3835     """
3836         Called by the network function. Prints out DNS servers on the interface
3837 
3838         @param host: string, the hostname or IP address of the bmc
3839         @param args: contains additional arguments used by the ldap subcommand
3840                 args.json: boolean, if this flag is set to true, the output
3841                 will be provided in json format for programmatic consumption
3842         @param session: the active session to use
3843     """
3844 
3845     url = "https://" + host + "/xyz/openbmc_project/network/" + args.Interface\
3846         + "/attr/Nameservers"
3847 
3848     try:
3849         res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
3850     except(requests.exceptions.Timeout):
3851         return(connectionErrHandler(args.json, "Timeout", None))
3852     except(requests.exceptions.ConnectionError) as err:
3853         return connectionErrHandler(args.json, "ConnectionError", err)
3854     if res.status_code == 404:
3855         return "The NameServer is not configured on Interface"+"("+args.Interface+")"
3856 
3857     return res.text
3858 
3859 
3860 def setDNS(host, args, session):
3861     """
3862         Called by the network function. Sets DNS servers on the interface.
3863 
3864         @param host: string, the hostname or IP address of the bmc
3865         @param args: contains additional arguments used by the ldap subcommand
3866                 args.json: boolean, if this flag is set to true, the output
3867                 will be provided in json format for programmatic consumption
3868         @param session: the active session to use
3869     """
3870 
3871     url = "https://" + host + "/xyz/openbmc_project/network/" + args.Interface\
3872         + "/attr/Nameservers"
3873 
3874     data = {"data": args.DNSServers.split(",")}
3875 
3876     try:
3877         res = session.put(url, headers=jsonHeader, json=data, verify=False,
3878                           timeout=baseTimeout)
3879     except(requests.exceptions.Timeout):
3880         return(connectionErrHandler(args.json, "Timeout", None))
3881     except(requests.exceptions.ConnectionError) as err:
3882         return connectionErrHandler(args.json, "ConnectionError", err)
3883     if res.status_code == 403:
3884         return "Failed to set DNS"
3885 
3886     return res.text
3887 
3888 
3889 def getNTP(host, args, session):
3890 
3891     """
3892         Called by the network function. Prints out NTP servers on the interface
3893 
3894         @param host: string, the hostname or IP address of the bmc
3895         @param args: contains additional arguments used by the ldap subcommand
3896                 args.json: boolean, if this flag is set to true, the output
3897                 will be provided in json format for programmatic consumption
3898         @param session: the active session to use
3899     """
3900 
3901     url = "https://" + host + "/xyz/openbmc_project/network/" + args.Interface\
3902         + "/attr/NTPServers"
3903     try:
3904         res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
3905     except(requests.exceptions.Timeout):
3906         return(connectionErrHandler(args.json, "Timeout", None))
3907     except(requests.exceptions.ConnectionError) as err:
3908         return connectionErrHandler(args.json, "ConnectionError", err)
3909     if res.status_code == 404:
3910         return "The NTPServer is not configured on Interface"+"("+args.Interface+")"
3911 
3912     return res.text
3913 
3914 
3915 def setNTP(host, args, session):
3916     """
3917         Called by the network function. Sets NTP servers on the interface.
3918 
3919         @param host: string, the hostname or IP address of the bmc
3920         @param args: contains additional arguments used by the ldap subcommand
3921                 args.json: boolean, if this flag is set to true, the output
3922                 will be provided in json format for programmatic consumption
3923         @param session: the active session to use
3924     """
3925 
3926     url = "https://" + host + "/xyz/openbmc_project/network/" + args.Interface\
3927         + "/attr/NTPServers"
3928 
3929     data = {"data": args.NTPServers.split(",")}
3930 
3931     try:
3932         res = session.put(url, headers=jsonHeader, json=data, verify=False,
3933                           timeout=baseTimeout)
3934     except(requests.exceptions.Timeout):
3935         return(connectionErrHandler(args.json, "Timeout", None))
3936     except(requests.exceptions.ConnectionError) as err:
3937         return connectionErrHandler(args.json, "ConnectionError", err)
3938     if res.status_code == 403:
3939         return "Failed to set NTP"
3940 
3941     return res.text
3942 
3943 
3944 def addIP(host, args, session):
3945     """
3946         Called by the network function. Configures IP address on given interface
3947 
3948         @param host: string, the hostname or IP address of the bmc
3949         @param args: contains additional arguments used by the ldap subcommand
3950                 args.json: boolean, if this flag is set to true, the output
3951                 will be provided in json format for programmatic consumption
3952         @param session: the active session to use
3953     """
3954 
3955     url = "https://" + host + "/xyz/openbmc_project/network/" + args.Interface\
3956         + "/action/IP"
3957     protocol = {
3958              'ipv4': 'xyz.openbmc_project.Network.IP.Protocol.IPv4',
3959              'ipv6': 'xyz.openbmc_project.Network.IP.Protocol.IPv6'
3960             }
3961 
3962     data = {"data": [protocol[args.type], args.address, int(args.prefixLength),
3963         args.gateway]}
3964 
3965     try:
3966         res = session.post(url, headers=jsonHeader, json=data, verify=False,
3967                            timeout=baseTimeout)
3968     except(requests.exceptions.Timeout):
3969         return(connectionErrHandler(args.json, "Timeout", None))
3970     except(requests.exceptions.ConnectionError) as err:
3971         return connectionErrHandler(args.json, "ConnectionError", err)
3972     if res.status_code == 404:
3973         return "The specified Interface" + "(" + args.Interface + ")" +\
3974             " doesn't exist"
3975 
3976     return res.text
3977 
3978 
3979 def getIP(host, args, session):
3980     """
3981         Called by the network function. Prints out IP address of given interface
3982 
3983         @param host: string, the hostname or IP address of the bmc
3984         @param args: contains additional arguments used by the ldap subcommand
3985                 args.json: boolean, if this flag is set to true, the output
3986                 will be provided in json format for programmatic consumption
3987         @param session: the active session to use
3988     """
3989 
3990     url = "https://" + host+"/xyz/openbmc_project/network/" + args.Interface +\
3991         "/enumerate"
3992     try:
3993         res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
3994     except(requests.exceptions.Timeout):
3995         return(connectionErrHandler(args.json, "Timeout", None))
3996     except(requests.exceptions.ConnectionError) as err:
3997         return connectionErrHandler(args.json, "ConnectionError", err)
3998     if res.status_code == 404:
3999         return "The specified Interface" + "(" + args.Interface + ")" +\
4000             " doesn't exist"
4001 
4002     return res.text
4003 
4004 
4005 def deleteIP(host, args, session):
4006     """
4007         Called by the network function. Deletes the IP address from given Interface
4008 
4009         @param host: string, the hostname or IP address of the bmc
4010         @param args: contains additional arguments used by the ldap subcommand
4011         @param session: the active session to use
4012         @param args.json: boolean, if this flag is set to true, the output
4013             will be provided in json format for programmatic consumption
4014     """
4015 
4016     url = "https://"+host+"/xyz/openbmc_project/network/" + args.Interface+\
4017         "/enumerate"
4018     data = {"data": []}
4019     try:
4020         res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
4021     except(requests.exceptions.Timeout):
4022         return(connectionErrHandler(args.json, "Timeout", None))
4023     except(requests.exceptions.ConnectionError) as err:
4024         return connectionErrHandler(args.json, "ConnectionError", err)
4025     if res.status_code == 404:
4026         return "The specified Interface" + "(" + args.Interface + ")" +\
4027             " doesn't exist"
4028     objDict = json.loads(res.text)
4029     if not objDict['data']:
4030         return "No object found for given address on given Interface"
4031     for obj in objDict['data']:
4032         try:
4033             if args.address in objDict['data'][obj]['Address']:
4034                 url = "https://"+host+obj+"/action/Delete"
4035                 try:
4036                     res = session.post(url, headers=jsonHeader, json=data,
4037                                        verify=False, timeout=baseTimeout)
4038                 except(requests.exceptions.Timeout):
4039                     return(connectionErrHandler(args.json, "Timeout", None))
4040                 except(requests.exceptions.ConnectionError) as err:
4041                     return connectionErrHandler(args.json, "ConnectionError", err)
4042                 return res.text
4043             else:
4044                 continue
4045         except KeyError:
4046             continue
4047     return "No object found for address " + args.address + \
4048            " on Interface(" + args.Interface + ")"
4049 
4050 
4051 def addVLAN(host, args, session):
4052     """
4053         Called by the network function. Creates VLAN on given interface.
4054 
4055         @param host: string, the hostname or IP address of the bmc
4056         @param args: contains additional arguments used by the ldap subcommand
4057                 args.json: boolean, if this flag is set to true, the output
4058                 will be provided in json format for programmatic consumption
4059         @param session: the active session to use
4060     """
4061 
4062     url = "https://" + host+"/xyz/openbmc_project/network/action/VLAN"
4063 
4064     data = {"data": [args.Interface,int(args.Identifier)]}
4065     try:
4066         res = session.post(url, headers=jsonHeader, json=data, verify=False,
4067                            timeout=baseTimeout)
4068     except(requests.exceptions.Timeout):
4069         return(connectionErrHandler(args.json, "Timeout", None))
4070     except(requests.exceptions.ConnectionError) as err:
4071         return connectionErrHandler(args.json, "ConnectionError", err)
4072     if res.status_code == 400:
4073         return "Adding VLAN to interface" + "(" + args.Interface + ")" +\
4074             " failed"
4075 
4076     return res.text
4077 
4078 
4079 def deleteVLAN(host, args, session):
4080     """
4081         Called by the network function. Creates VLAN on given interface.
4082 
4083         @param host: string, the hostname or IP address of the bmc
4084         @param args: contains additional arguments used by the ldap subcommand
4085                 args.json: boolean, if this flag is set to true, the output
4086                 will be provided in json format for programmatic consumption
4087         @param session: the active session to use
4088     """
4089 
4090     url = "https://" + host+"/xyz/openbmc_project/network/"+args.Interface+"/action/Delete"
4091     data = {"data": []}
4092 
4093     try:
4094         res = session.post(url, headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
4095     except(requests.exceptions.Timeout):
4096         return(connectionErrHandler(args.json, "Timeout", None))
4097     except(requests.exceptions.ConnectionError) as err:
4098         return connectionErrHandler(args.json, "ConnectionError", err)
4099     if res.status_code == 404:
4100         return "The specified VLAN"+"("+args.Interface+")" +" doesn't exist"
4101 
4102     return res.text
4103 
4104 
4105 def viewDHCPConfig(host, args, session):
4106     """
4107         Called by the network function. Shows DHCP configured Properties.
4108 
4109         @param host: string, the hostname or IP address of the bmc
4110         @param args: contains additional arguments used by the ldap subcommand
4111                 args.json: boolean, if this flag is set to true, the output
4112                 will be provided in json format for programmatic consumption
4113         @param session: the active session to use
4114     """
4115 
4116     url="https://"+host+"/xyz/openbmc_project/network/config/dhcp"
4117 
4118     try:
4119         res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
4120     except(requests.exceptions.Timeout):
4121         return(connectionErrHandler(args.json, "Timeout", None))
4122     except(requests.exceptions.ConnectionError) as err:
4123         return connectionErrHandler(args.json, "ConnectionError", err)
4124 
4125     return res.text
4126 
4127 
4128 def configureDHCP(host, args, session):
4129     """
4130         Called by the network function. Configures/updates DHCP Properties.
4131 
4132         @param host: string, the hostname or IP address of the bmc
4133         @param args: contains additional arguments used by the ldap subcommand
4134                 args.json: boolean, if this flag is set to true, the output
4135                 will be provided in json format for programmatic consumption
4136         @param session: the active session to use
4137     """
4138 
4139 
4140     try:
4141         url="https://"+host+"/xyz/openbmc_project/network/config/dhcp"
4142         if(args.DNSEnabled == True):
4143             data = '{"data": 1}'
4144         else:
4145             data = '{"data": 0}'
4146         res = session.put(url + '/attr/DNSEnabled', headers=jsonHeader,
4147                           data=data, verify=False, timeout=baseTimeout)
4148         if(args.HostNameEnabled == True):
4149             data = '{"data": 1}'
4150         else:
4151             data = '{"data": 0}'
4152         res = session.put(url + '/attr/HostNameEnabled', headers=jsonHeader,
4153                           data=data, verify=False, timeout=baseTimeout)
4154         if(args.NTPEnabled == True):
4155             data = '{"data": 1}'
4156         else:
4157             data = '{"data": 0}'
4158         res = session.put(url + '/attr/NTPEnabled', headers=jsonHeader,
4159                           data=data, verify=False, timeout=baseTimeout)
4160         if(args.SendHostNameEnabled == True):
4161             data = '{"data": 1}'
4162         else:
4163             data = '{"data": 0}'
4164         res = session.put(url + '/attr/SendHostNameEnabled', headers=jsonHeader,
4165                           data=data, verify=False, timeout=baseTimeout)
4166     except(requests.exceptions.Timeout):
4167         return(connectionErrHandler(args.json, "Timeout", None))
4168     except(requests.exceptions.ConnectionError) as err:
4169         return connectionErrHandler(args.json, "ConnectionError", err)
4170 
4171     return res.text
4172 
4173 
4174 def nwReset(host, args, session):
4175 
4176     """
4177         Called by the network function. Resets networks setting to factory defaults.
4178 
4179         @param host: string, the hostname or IP address of the bmc
4180         @param args: contains additional arguments used by the ldap subcommand
4181                 args.json: boolean, if this flag is set to true, the output
4182                 will be provided in json format for programmatic consumption
4183         @param session: the active session to use
4184     """
4185 
4186     url = "https://"+host+"/xyz/openbmc_project/network/action/Reset"
4187     data = '{"data":[] }'
4188     try:
4189         res = session.post(url, headers=jsonHeader, data=data, verify=False,
4190                           timeout=baseTimeout)
4191 
4192     except(requests.exceptions.Timeout):
4193         return(connectionErrHandler(args.json, "Timeout", None))
4194     except(requests.exceptions.ConnectionError) as err:
4195         return connectionErrHandler(args.json, "ConnectionError", err)
4196 
4197     return res.text
4198 
4199 def getLDAPTypeEnabled(host,session):
4200 
4201     """
4202         Called by LDAP related functions to find the LDAP server type that has been enabled.
4203         Returns None if LDAP has not been configured.
4204 
4205         @param host: string, the hostname or IP address of the bmc
4206         @param session: the active session to use
4207     """
4208 
4209     enabled = False
4210     url = 'https://'+host+'/xyz/openbmc_project/user/ldap/'
4211     for key,value in serverTypeMap.items():
4212         data = {"data": []}
4213         try:
4214             res = session.get(url + value + '/attr/Enabled', headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
4215         except(requests.exceptions.Timeout):
4216             print(connectionErrHandler(args.json, "Timeout", None))
4217             return
4218         except(requests.exceptions.ConnectionError) as err:
4219             print(connectionErrHandler(args.json, "ConnectionError", err))
4220             return
4221 
4222         enabled = res.json()['data']
4223         if (enabled):
4224             return key
4225 
4226 def syncRoleMap(host,args,session,fromServerType,toServerType):
4227 
4228     """
4229         Called by LDAP related functions to sync the role maps
4230         Returns False if LDAP has not been configured.
4231 
4232         @param host: string, the hostname or IP address of the bmc
4233         @param session: the active session to use
4234         @param fromServerType : Server type whose role map has to be copied
4235         @param toServerType : Server type to which role map has to be copied
4236     """
4237 
4238     url = "https://"+host+"/xyz/openbmc_project/user/ldap/"
4239 
4240     try:
4241         #Note: If the fromServerType has no role map, then
4242         #the toServerType will not have any role map.
4243 
4244         #delete the privilege mapping from the toServerType and
4245         #then copy the privilege mapping from fromServerType to
4246         #toServerType.
4247         args.serverType = toServerType
4248         res = deleteAllPrivilegeMapping(host, args, session)
4249 
4250         data = {"data": []}
4251         res = session.get(url + serverTypeMap[fromServerType] + '/role_map/enumerate', headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
4252         #Previously enabled server type has no role map
4253         if (res.status_code != requests.codes.ok):
4254 
4255             #fromServerType has no role map; So, no need to copy
4256             #role map to toServerType.
4257             return
4258 
4259         objDict = json.loads(res.text)
4260         dataDict = objDict['data']
4261         for  key,value in dataDict.items():
4262             data = {"data": [value["GroupName"], value["Privilege"]]}
4263             res = session.post(url + serverTypeMap[toServerType] + '/action/Create', headers=jsonHeader, json = data, verify=False, timeout=baseTimeout)
4264 
4265     except(requests.exceptions.Timeout):
4266         return(connectionErrHandler(args.json, "Timeout", None))
4267     except(requests.exceptions.ConnectionError) as err:
4268         return connectionErrHandler(args.json, "ConnectionError", err)
4269     return res.text
4270 
4271 
4272 def createPrivilegeMapping(host, args, session):
4273     """
4274          Called by the ldap function. Creates the group and the privilege mapping.
4275 
4276          @param host: string, the hostname or IP address of the bmc
4277          @param args: contains additional arguments used by the ldap subcommand
4278          @param session: the active session to use
4279          @param args.json: boolean, if this flag is set to true, the output
4280                 will be provided in json format for programmatic consumption
4281     """
4282 
4283     try:
4284         if (isRedfishSupport):
4285             url = 'https://'+host+'/xyz/openbmc_project/user/ldap/'
4286 
4287             #To maintain the interface compatibility between op930 and op940, the server type has been made
4288             #optional. If the server type is not specified, then create the role-mapper for the currently
4289             #enabled server type.
4290             serverType = args.serverType
4291             if (serverType is None):
4292                 serverType = getLDAPTypeEnabled(host,session)
4293                 if (serverType is None):
4294                      return("LDAP server has not been enabled. Please specify LDAP serverType to proceed further...")
4295 
4296             data = {"data": [args.groupName,args.privilege]}
4297             res = session.post(url + serverTypeMap[serverType] + '/action/Create', headers=jsonHeader, json = data, verify=False, timeout=baseTimeout)
4298 
4299         else:
4300             url = 'https://'+host+'/xyz/openbmc_project/user/ldap/action/Create'
4301             data = {"data": [args.groupName,args.privilege]}
4302             res = session.post(url, headers=jsonHeader, json = data, verify=False, timeout=baseTimeout)
4303 
4304     except(requests.exceptions.Timeout):
4305         return(connectionErrHandler(args.json, "Timeout", None))
4306     except(requests.exceptions.ConnectionError) as err:
4307         return connectionErrHandler(args.json, "ConnectionError", err)
4308     return res.text
4309 
4310 def listPrivilegeMapping(host, args, session):
4311     """
4312          Called by the ldap function. Lists the group and the privilege mapping.
4313 
4314          @param host: string, the hostname or IP address of the bmc
4315          @param args: contains additional arguments used by the ldap subcommand
4316          @param session: the active session to use
4317          @param args.json: boolean, if this flag is set to true, the output
4318                 will be provided in json format for programmatic consumption
4319     """
4320 
4321     if (isRedfishSupport):
4322         serverType = args.serverType
4323         if (serverType is None):
4324             serverType = getLDAPTypeEnabled(host,session)
4325             if (serverType is None):
4326                 return("LDAP has not been enabled. Please specify LDAP serverType to proceed further...")
4327 
4328         url = 'https://'+host+'/xyz/openbmc_project/user/ldap/'+serverTypeMap[serverType]+'/role_map/enumerate'
4329 
4330     else:
4331         url = 'https://'+host+'/xyz/openbmc_project/user/ldap/enumerate'
4332 
4333     data = {"data": []}
4334 
4335     try:
4336         res = session.get(url, headers=jsonHeader, json = data, verify=False, timeout=baseTimeout)
4337     except(requests.exceptions.Timeout):
4338         return(connectionErrHandler(args.json, "Timeout", None))
4339     except(requests.exceptions.ConnectionError) as err:
4340         return connectionErrHandler(args.json, "ConnectionError", err)
4341 
4342     return res.text
4343 
4344 def deletePrivilegeMapping(host, args, session):
4345     """
4346          Called by the ldap function. Deletes the mapping associated with the group.
4347 
4348          @param host: string, the hostname or IP address of the bmc
4349          @param args: contains additional arguments used by the ldap subcommand
4350          @param session: the active session to use
4351          @param args.json: boolean, if this flag is set to true, the output
4352                 will be provided in json format for programmatic consumption
4353     """
4354 
4355     ldapNameSpaceObjects = listPrivilegeMapping(host, args, session)
4356     ldapNameSpaceObjects = json.loads(ldapNameSpaceObjects)["data"]
4357     path = ''
4358     data = {"data": []}
4359 
4360     if (isRedfishSupport):
4361         if (args.serverType is None):
4362             serverType = getLDAPTypeEnabled(host,session)
4363             if (serverType is None):
4364                 return("LDAP has not been enabled. Please specify LDAP serverType to proceed further...")
4365         # search for the object having the mapping for the given group
4366         for key,value in ldapNameSpaceObjects.items():
4367             if value['GroupName'] == args.groupName:
4368                 path = key
4369                 break
4370 
4371         if path == '':
4372             return "No privilege mapping found for this group."
4373 
4374         # delete the object
4375         url = 'https://'+host+path+'/action/Delete'
4376 
4377     else:
4378         # not interested in the config objet
4379         ldapNameSpaceObjects.pop('/xyz/openbmc_project/user/ldap/config', None)
4380 
4381         # search for the object having the mapping for the given group
4382         for key,value in ldapNameSpaceObjects.items():
4383             if value['GroupName'] == args.groupName:
4384                 path = key
4385                 break
4386 
4387         if path == '':
4388             return "No privilege mapping found for this group."
4389 
4390         # delete the object
4391         url = 'https://'+host+path+'/action/delete'
4392 
4393     try:
4394         res = session.post(url, headers=jsonHeader, json = data, verify=False, timeout=baseTimeout)
4395     except(requests.exceptions.Timeout):
4396         return(connectionErrHandler(args.json, "Timeout", None))
4397     except(requests.exceptions.ConnectionError) as err:
4398         return connectionErrHandler(args.json, "ConnectionError", err)
4399     return res.text
4400 
4401 def deleteAllPrivilegeMapping(host, args, session):
4402     """
4403          Called by the ldap function. Deletes all the privilege mapping and group defined.
4404          @param host: string, the hostname or IP address of the bmc
4405          @param args: contains additional arguments used by the ldap subcommand
4406          @param session: the active session to use
4407          @param args.json: boolean, if this flag is set to true, the output
4408                 will be provided in json format for programmatic consumption
4409     """
4410 
4411     ldapNameSpaceObjects = listPrivilegeMapping(host, args, session)
4412     ldapNameSpaceObjects = json.loads(ldapNameSpaceObjects)["data"]
4413     path = ''
4414     data = {"data": []}
4415 
4416     if (isRedfishSupport):
4417         if (args.serverType is None):
4418             serverType = getLDAPTypeEnabled(host,session)
4419             if (serverType is None):
4420                 return("LDAP has not been enabled. Please specify LDAP serverType to proceed further...")
4421 
4422     else:
4423         # Remove the config object.
4424         ldapNameSpaceObjects.pop('/xyz/openbmc_project/user/ldap/config', None)
4425 
4426     try:
4427         # search for GroupName property and delete if it is available.
4428         for path in ldapNameSpaceObjects.keys():
4429             # delete the object
4430             url = 'https://'+host+path+'/action/Delete'
4431             res = session.post(url, headers=jsonHeader, json = data, verify=False, timeout=baseTimeout)
4432 
4433     except(requests.exceptions.Timeout):
4434         return(connectionErrHandler(args.json, "Timeout", None))
4435     except(requests.exceptions.ConnectionError) as err:
4436         return connectionErrHandler(args.json, "ConnectionError", err)
4437     return res.text
4438 
4439 def viewLDAPConfig(host, args, session):
4440     """
4441          Called by the ldap function. Prints out active LDAP configuration properties
4442 
4443          @param host: string, the hostname or IP address of the bmc
4444          @param args: contains additional arguments used by the ldap subcommand
4445                 args.json: boolean, if this flag is set to true, the output
4446                 will be provided in json format for programmatic consumption
4447          @param session: the active session to use
4448          @return returns LDAP's configured properties.
4449     """
4450 
4451     try:
4452         if (isRedfishSupport):
4453 
4454             url = "https://"+host+"/xyz/openbmc_project/user/ldap/"
4455 
4456             serverTypeEnabled = getLDAPTypeEnabled(host,session)
4457 
4458             if (serverTypeEnabled is not None):
4459                 data = {"data": []}
4460                 res = session.get(url + serverTypeMap[serverTypeEnabled], headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
4461             else:
4462                 return("LDAP server has not been enabled...")
4463 
4464         else :
4465             url = "https://"+host+"/xyz/openbmc_project/user/ldap/config"
4466             res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
4467 
4468     except(requests.exceptions.Timeout):
4469         return(connectionErrHandler(args.json, "Timeout", None))
4470     except(requests.exceptions.ConnectionError) as err:
4471         return connectionErrHandler(args.json, "ConnectionError", err)
4472     if res.status_code == 404:
4473         return "LDAP server config has not been created"
4474     return res.text
4475 
4476 def str2bool(v):
4477     if v.lower() in ('yes', 'true', 't', 'y', '1'):
4478         return True
4479     elif v.lower() in ('no', 'false', 'f', 'n', '0'):
4480         return False
4481     else:
4482         raise argparse.ArgumentTypeError('Boolean value expected.')
4483 
4484 def localUsers(host, args, session):
4485     """
4486         Enables and disables local BMC users.
4487 
4488         @param host: string, the hostname or IP address of the bmc
4489         @param args: contains additional arguments used by the logging sub command
4490         @param session: the active session to use
4491     """
4492 
4493     url="https://{hostname}/xyz/openbmc_project/user/enumerate".format(hostname=host)
4494     try:
4495         res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
4496     except(requests.exceptions.Timeout):
4497         return(connectionErrHandler(args.json, "Timeout", None))
4498     usersDict = json.loads(res.text)
4499 
4500     if not usersDict['data']:
4501         return "No users found"
4502 
4503     output = ""
4504     for user in usersDict['data']:
4505 
4506         # Skip LDAP and another non-local users
4507         if 'UserEnabled' not in usersDict['data'][user]:
4508             continue
4509 
4510         name = user.split('/')[-1]
4511         url = "https://{hostname}{user}/attr/UserEnabled".format(hostname=host, user=user)
4512 
4513         if args.local_users == "queryenabled":
4514             try:
4515                 res = session.get(url, headers=jsonHeader,verify=False, timeout=baseTimeout)
4516             except(requests.exceptions.Timeout):
4517                 return(connectionErrHandler(args.json, "Timeout", None))
4518 
4519             result = json.loads(res.text)
4520             output += ("User: {name}  Enabled: {result}\n").format(name=name, result=result['data'])
4521 
4522         elif args.local_users in ["enableall", "disableall"]:
4523             action = ""
4524             if args.local_users == "enableall":
4525                 data = '{"data": true}'
4526                 action = "Enabling"
4527             else:
4528                 data = '{"data": false}'
4529                 action = "Disabling"
4530 
4531             output += "{action} {name}\n".format(action=action, name=name)
4532 
4533             try:
4534                 resp = session.put(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
4535             except(requests.exceptions.Timeout):
4536                 return connectionErrHandler(args.json, "Timeout", None)
4537             except(requests.exceptions.ConnectionError) as err:
4538                 return connectionErrHandler(args.json, "ConnectionError", err)
4539         else:
4540             return "Invalid local users argument"
4541 
4542     return output
4543 
4544 def setPassword(host, args, session):
4545     """
4546          Set local user password
4547          @param host: string, the hostname or IP address of the bmc
4548          @param args: contains additional arguments used by the logging sub
4549                 command
4550          @param session: the active session to use
4551          @param args.json: boolean, if this flag is set to true, the output
4552                 will be provided in json format for programmatic consumption
4553          @return: Session object
4554     """
4555     try:
4556         if(isRedfishSupport):
4557             url = "https://" + host + "/redfish/v1/AccountService/Accounts/"+ \
4558                   args.user
4559             data = {"Password":args.password}
4560             res = session.patch(url, headers=jsonHeader, json=data,
4561                                 verify=False, timeout=baseTimeout)
4562         else:
4563             url = "https://" + host + "/xyz/openbmc_project/user/" + args.user + \
4564                 "/action/SetPassword"
4565             res = session.post(url, headers=jsonHeader,
4566                            json={"data": [args.password]}, verify=False,
4567                            timeout=baseTimeout)
4568     except(requests.exceptions.Timeout):
4569         return(connectionErrHandler(args.json, "Timeout", None))
4570     except(requests.exceptions.ConnectionError) as err:
4571         return connectionErrHandler(args.json, "ConnectionError", err)
4572     except(requests.exceptions.RequestException) as err:
4573         return connectionErrHandler(args.json, "RequestException", err)
4574     return res.status_code
4575 
4576 def getThermalZones(host, args, session):
4577     """
4578         Get the available thermal control zones
4579         @param host: string, the hostname or IP address of the bmc
4580         @param args: contains additional arguments used to get the thermal
4581                control zones
4582         @param session: the active session to use
4583         @return: Session object
4584     """
4585     url = "https://" + host + "/xyz/openbmc_project/control/thermal/enumerate"
4586 
4587     try:
4588         res = session.get(url, headers=jsonHeader, verify=False, timeout=30)
4589     except(requests.exceptions.Timeout):
4590         return(connectionErrHandler(args.json, "Timeout", None))
4591     except(requests.exceptions.ConnectionError) as err:
4592         return connectionErrHandler(args.json, "ConnectionError", err)
4593     except(requests.exceptions.RequestException) as err:
4594         return connectionErrHandler(args.json, "RequestException", err)
4595 
4596     if (res.status_code == 404):
4597         return "No thermal control zones found"
4598 
4599     zonesDict = json.loads(res.text)
4600     if not zonesDict['data']:
4601         return "No thermal control zones found"
4602     for zone in zonesDict['data']:
4603         z = ",".join(str(zone.split('/')[-1]) for zone in zonesDict['data'])
4604 
4605     return "Zones: [ " + z + " ]"
4606 
4607 
4608 def getThermalMode(host, args, session):
4609     """
4610         Get thermal control mode
4611         @param host: string, the hostname or IP address of the bmc
4612         @param args: contains additional arguments used to get the thermal
4613                control mode
4614         @param session: the active session to use
4615         @param args.zone: the zone to get the mode on
4616         @return: Session object
4617     """
4618     url = "https://" + host + "/xyz/openbmc_project/control/thermal/" + \
4619         args.zone
4620 
4621     try:
4622         res = session.get(url, headers=jsonHeader, verify=False, timeout=30)
4623     except(requests.exceptions.Timeout):
4624         return(connectionErrHandler(args.json, "Timeout", None))
4625     except(requests.exceptions.ConnectionError) as err:
4626         return connectionErrHandler(args.json, "ConnectionError", err)
4627     except(requests.exceptions.RequestException) as err:
4628         return connectionErrHandler(args.json, "RequestException", err)
4629 
4630     if (res.status_code == 404):
4631         return "Thermal control zone(" + args.zone + ") not found"
4632 
4633     propsDict = json.loads(res.text)
4634     if not propsDict['data']:
4635         return "No thermal control properties found on zone(" + args.zone + ")"
4636     curMode = "Current"
4637     supModes = "Supported"
4638     result = "\n"
4639     for prop in propsDict['data']:
4640         if (prop.casefold() == curMode.casefold()):
4641             result += curMode + " Mode: " + propsDict['data'][curMode] + "\n"
4642         if (prop.casefold() == supModes.casefold()):
4643             s = ", ".join(str(sup) for sup in propsDict['data'][supModes])
4644             result += supModes + " Modes: [ " + s + " ]\n"
4645 
4646     return result
4647 
4648 def setThermalMode(host, args, session):
4649     """
4650         Set thermal control mode
4651         @param host: string, the hostname or IP address of the bmc
4652         @param args: contains additional arguments used for setting the thermal
4653                control mode
4654         @param session: the active session to use
4655         @param args.zone: the zone to set the mode on
4656         @param args.mode: the mode to enable
4657         @return: Session object
4658     """
4659     url = "https://" + host + "/xyz/openbmc_project/control/thermal/" + \
4660         args.zone + "/attr/Current"
4661 
4662     # Check args.mode against supported modes using `getThermalMode` output
4663     modes = getThermalMode(host, args, session)
4664     modes = os.linesep.join([m for m in modes.splitlines() if m])
4665     modes = modes.replace("\n", ";").strip()
4666     modesDict = dict(m.split(': ') for m in modes.split(';'))
4667     sModes = ''.join(s for s in modesDict['Supported Modes'] if s not in '[ ]')
4668     if args.mode.casefold() not in \
4669             (m.casefold() for m in sModes.split(',')) or not args.mode:
4670         result = ("Unsupported mode('" + args.mode + "') given, " +
4671                   "select a supported mode: \n" +
4672                   getThermalMode(host, args, session))
4673         return result
4674 
4675     data = '{"data":"' + args.mode + '"}'
4676     try:
4677         res = session.get(url, headers=jsonHeader, verify=False, timeout=30)
4678     except(requests.exceptions.Timeout):
4679         return(connectionErrHandler(args.json, "Timeout", None))
4680     except(requests.exceptions.ConnectionError) as err:
4681         return connectionErrHandler(args.json, "ConnectionError", err)
4682     except(requests.exceptions.RequestException) as err:
4683         return connectionErrHandler(args.json, "RequestException", err)
4684 
4685     if (data and res.status_code != 404):
4686         try:
4687             res = session.put(url, headers=jsonHeader,
4688                               data=data, verify=False,
4689                               timeout=30)
4690         except(requests.exceptions.Timeout):
4691             return(connectionErrHandler(args.json, "Timeout", None))
4692         except(requests.exceptions.ConnectionError) as err:
4693             return connectionErrHandler(args.json, "ConnectionError", err)
4694         except(requests.exceptions.RequestException) as err:
4695             return connectionErrHandler(args.json, "RequestException", err)
4696 
4697         if res.status_code == 403:
4698             return "The specified thermal control zone(" + args.zone + ")" + \
4699                 " does not exist"
4700 
4701         return res.text
4702     else:
4703         return "Setting thermal control mode(" + args.mode + ")" + \
4704             " not supported or operation not available"
4705 
4706 
4707 def createCommandParser():
4708     """
4709          creates the parser for the command line along with help for each command and subcommand
4710 
4711          @return: returns the parser for the command line
4712     """
4713     parser = argparse.ArgumentParser(description='Process arguments')
4714     parser.add_argument("-H", "--host", help='A hostname or IP for the BMC')
4715     parser.add_argument("-U", "--user", help='The username to login with')
4716     group = parser.add_mutually_exclusive_group()
4717     group.add_argument("-A", "--askpw", action='store_true', help='prompt for password')
4718     group.add_argument("-P", "--PW", help='Provide the password in-line')
4719     group.add_argument("-E", "--PWenvvar", action='store_true', help='Get password from envvar OPENBMCTOOL_PASSWORD')
4720     parser.add_argument('-j', '--json', action='store_true', help='output json data only')
4721     parser.add_argument('-t', '--policyTableLoc', help='The location of the policy table to parse alerts')
4722     parser.add_argument('-c', '--CerFormat', action='store_true', help=argparse.SUPPRESS)
4723     parser.add_argument('-T', '--procTime', action='store_true', help= argparse.SUPPRESS)
4724     parser.add_argument('-V', '--version', action='store_true', help='Display the version number of the openbmctool')
4725     subparsers = parser.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
4726 
4727     #fru command
4728     parser_inv = subparsers.add_parser("fru", help='Work with platform inventory')
4729     inv_subparser = parser_inv.add_subparsers(title='subcommands', description='valid inventory actions', help="valid inventory actions", dest='command')
4730     inv_subparser.required = True
4731     #fru print
4732     inv_print = inv_subparser.add_parser("print", help="prints out a list of all FRUs")
4733     inv_print.set_defaults(func=fruPrint)
4734     #fru list [0....n]
4735     inv_list = inv_subparser.add_parser("list", help="print out details on selected FRUs. Specifying no items will list the entire inventory")
4736     inv_list.add_argument('items', nargs='?', help="print out details on selected FRUs. Specifying no items will list the entire inventory")
4737     inv_list.set_defaults(func=fruList)
4738     #fru status
4739     inv_status = inv_subparser.add_parser("status", help="prints out the status of all FRUs")
4740     inv_status.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
4741     inv_status.set_defaults(func=fruStatus)
4742 
4743     #sensors command
4744     parser_sens = subparsers.add_parser("sensors", help="Work with platform sensors")
4745     sens_subparser=parser_sens.add_subparsers(title='subcommands', description='valid sensor actions', help='valid sensor actions', dest='command')
4746     sens_subparser.required = True
4747     #sensor print
4748     sens_print= sens_subparser.add_parser('print', help="prints out a list of all Sensors.")
4749     sens_print.set_defaults(func=sensor)
4750     #sensor list[0...n]
4751     sens_list=sens_subparser.add_parser("list", help="Lists all Sensors in the platform. Specify a sensor for full details. ")
4752     sens_list.add_argument("sensNum", nargs='?', help="The Sensor number to get full details on" )
4753     sens_list.set_defaults(func=sensor)
4754 
4755     #thermal control commands
4756     parser_therm = subparsers.add_parser("thermal", help="Work with thermal control parameters")
4757     therm_subparser=parser_therm.add_subparsers(title='subcommands', description='Thermal control actions to work with', help='Valid thermal control actions to work with', dest='command')
4758     #thermal control zones
4759     parser_thermZones = therm_subparser.add_parser("zones", help="Get a list of available thermal control zones")
4760     parser_thermZones.set_defaults(func=getThermalZones)
4761     #thermal control modes
4762     parser_thermMode = therm_subparser.add_parser("modes", help="Work with thermal control modes")
4763     thermMode_sub = parser_thermMode.add_subparsers(title='subactions', description='Work with thermal control modes', help="Work with thermal control modes")
4764     #get thermal control mode
4765     parser_getThermMode = thermMode_sub.add_parser("get", help="Get current and supported thermal control modes")
4766     parser_getThermMode.add_argument('-z', '--zone', required=True, help='Thermal zone to work with')
4767     parser_getThermMode.set_defaults(func=getThermalMode)
4768     #set thermal control mode
4769     parser_setThermMode = thermMode_sub.add_parser("set", help="Set the thermal control mode")
4770     parser_setThermMode.add_argument('-z', '--zone', required=True, help='Thermal zone to work with')
4771     parser_setThermMode.add_argument('-m', '--mode', required=True, help='The supported thermal control mode')
4772     parser_setThermMode.set_defaults(func=setThermalMode)
4773 
4774     #sel command
4775     parser_sel = subparsers.add_parser("sel", help="Work with platform alerts")
4776     sel_subparser = parser_sel.add_subparsers(title='subcommands', description='valid SEL actions', help = 'valid SEL actions', dest='command')
4777     sel_subparser.required = True
4778     #sel print
4779     sel_print = sel_subparser.add_parser("print", help="prints out a list of all sels in a condensed list")
4780     sel_print.add_argument('-d', '--devdebug', action='store_true', help=argparse.SUPPRESS)
4781     sel_print.add_argument('-v', '--verbose', action='store_true', help="Changes the output to being very verbose")
4782     sel_print.add_argument('-f', '--fileloc', help='Parse a file instead of the BMC output')
4783     sel_print.set_defaults(func=selPrint)
4784 
4785     #sel list
4786     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")
4787     sel_list.add_argument("selNum", nargs='?', type=int, help="The SEL entry to get details on")
4788     sel_list.set_defaults(func=selList)
4789 
4790     sel_get = sel_subparser.add_parser("get", help="Gets the verbose details of a specified SEL entry")
4791     sel_get.add_argument('selNum', type=int, help="the number of the SEL entry to get")
4792     sel_get.set_defaults(func=selList)
4793 
4794     sel_clear = sel_subparser.add_parser("clear", help="Clears all entries from the SEL")
4795     sel_clear.set_defaults(func=selClear)
4796 
4797     sel_setResolved = sel_subparser.add_parser("resolve", help="Sets the sel entry to resolved")
4798     sel_setResolved.add_argument('-n', '--selNum', type=int, help="the number of the SEL entry to resolve")
4799     sel_ResolveAll_sub = sel_setResolved.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
4800     sel_ResolveAll = sel_ResolveAll_sub.add_parser('all', help='Resolve all SEL entries')
4801     sel_ResolveAll.set_defaults(func=selResolveAll)
4802     sel_setResolved.set_defaults(func=selSetResolved)
4803 
4804     parser_chassis = subparsers.add_parser("chassis", help="Work with chassis power and status")
4805     chas_sub = parser_chassis.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
4806 
4807     parser_chassis.add_argument('status', action='store_true', help='Returns the current status of the platform')
4808     parser_chassis.set_defaults(func=chassis)
4809 
4810     parser_chasPower = chas_sub.add_parser("power", help="Turn the chassis on or off, check the power state")
4811     parser_chasPower.add_argument('powcmd',  choices=['on','softoff', 'hardoff', 'status'], help='The value for the power command. on, off, or status')
4812     parser_chasPower.set_defaults(func=chassisPower)
4813 
4814     #control the chassis identify led
4815     parser_chasIdent = chas_sub.add_parser("identify", help="Control the chassis identify led")
4816     parser_chasIdent.add_argument('identcmd', choices=['on', 'off', 'status'], help='The control option for the led: on, off, blink, status')
4817     parser_chasIdent.set_defaults(func=chassisIdent)
4818 
4819     #collect service data
4820     parser_servData = subparsers.add_parser("collect_service_data", help="Collect all bmc data needed for service")
4821     parser_servData.add_argument('-d', '--devdebug', action='store_true', help=argparse.SUPPRESS)
4822     parser_servData.set_defaults(func=collectServiceData)
4823 
4824     #system quick health check
4825     parser_healthChk = subparsers.add_parser("health_check", help="Work with platform sensors")
4826     parser_healthChk.set_defaults(func=healthCheck)
4827 
4828     #tasks
4829     parser_tasks = subparsers.add_parser("task", help="Work with tasks")
4830     tasks_sub = parser_tasks.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
4831     tasks_sub.required = True
4832     get_Task = tasks_sub.add_parser('get', help="Get on Task Monitor URL")
4833     get_Task.add_argument("-u", "--taskURI", help="Task Monitor URI")
4834     get_Task.set_defaults(func=getTask)
4835 
4836     #work with dumps
4837     parser_bmcdump = subparsers.add_parser("dump", help="Work with dumps")
4838     parser_bmcdump.add_argument("-t", "--dumpType", default='bmc', choices=['bmc','SystemDump'],help="Type of dump")
4839     bmcDump_sub = parser_bmcdump.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
4840     bmcDump_sub.required = True
4841     dump_Create = bmcDump_sub.add_parser('create', help="Create a dump of given type")
4842     dump_Create.set_defaults(func=dumpCreate)
4843 
4844     dump_list = bmcDump_sub.add_parser('list', help="list all dumps")
4845     dump_list.set_defaults(func=dumpList)
4846 
4847     parserdumpdelete = bmcDump_sub.add_parser('delete', help="Delete dump")
4848     parserdumpdelete.add_argument("-n", "--dumpNum", nargs='*', type=int, help="The Dump entry to delete")
4849     parserdumpdelete.set_defaults(func=dumpDelete)
4850 
4851     bmcDumpDelsub = parserdumpdelete.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
4852     deleteAllDumps = bmcDumpDelsub.add_parser('all', help='Delete all dumps')
4853     deleteAllDumps.set_defaults(func=dumpDeleteAll)
4854 
4855     parser_dumpretrieve = bmcDump_sub.add_parser('retrieve', help='Retrieve a dump file')
4856     parser_dumpretrieve.add_argument("-n,", "--dumpNum", help="The Dump entry to retrieve")
4857     parser_dumpretrieve.add_argument("-s", "--dumpSaveLoc", help="The location to save the bmc dump file or file path for system dump")
4858     parser_dumpretrieve.set_defaults(func=dumpRetrieve)
4859 
4860     #bmc command for reseting the bmc
4861     parser_bmc = subparsers.add_parser('bmc', help="Work with the bmc")
4862     bmc_sub = parser_bmc.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
4863     parser_BMCReset = bmc_sub.add_parser('reset', help='Reset the bmc' )
4864     parser_BMCReset.add_argument('type', choices=['warm','cold'], help="Warm: Reboot the BMC, Cold: CLEAR config and reboot bmc")
4865     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.")
4866     parser_bmc.set_defaults(func=bmc)
4867 
4868     #add alias to the bmc command
4869     parser_mc = subparsers.add_parser('mc', help="Work with the management controller")
4870     mc_sub = parser_mc.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
4871     parser_MCReset = mc_sub.add_parser('reset', help='Reset the bmc' )
4872     parser_MCReset.add_argument('type', choices=['warm','cold'], help="Reboot the BMC")
4873     #parser_MCReset.add_argument('cold', action='store_true', help="Reboot the BMC and CLEAR the configuration")
4874     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.")
4875     parser_MCReset.set_defaults(func=bmcReset)
4876     parser_mc.set_defaults(func=bmc)
4877 
4878     #gard clear
4879     parser_gc = subparsers.add_parser("gardclear", help="Used to clear gard records")
4880     parser_gc.set_defaults(func=gardClear)
4881 
4882     #firmware_flash
4883     parser_fw = subparsers.add_parser("firmware", help="Work with the system firmware")
4884     fwflash_subproc = parser_fw.add_subparsers(title='subcommands', description='valid firmware commands', help='sub-command help', dest='command')
4885     fwflash_subproc.required = True
4886 
4887     fwflash = fwflash_subproc.add_parser('flash', help="Flash the system firmware")
4888     fwflash.add_argument('type', choices=['bmc', 'pnor'], help="image type to flash")
4889     fwflash.add_argument('-f', '--fileloc', required=True, help="The absolute path to the firmware image")
4890     fwflash.set_defaults(func=fwFlash)
4891 
4892     fwActivate = fwflash_subproc.add_parser('activate', help="Activate existing image on the bmc")
4893     fwActivate.add_argument('imageID', help="The image ID to activate from the firmware list. Ex: 63c95399")
4894     fwActivate.set_defaults(func=activateFWImage)
4895 
4896     fwActivateStatus = fwflash_subproc.add_parser('activation_status', help="Check Status of activations")
4897     fwActivateStatus.set_defaults(func=activateStatus)
4898 
4899     fwList = fwflash_subproc.add_parser('list', help="List all of the installed firmware")
4900     fwList.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
4901     fwList.set_defaults(func=firmwareList)
4902 
4903     fwprint = fwflash_subproc.add_parser('print', help="List all of the installed firmware")
4904     fwprint.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
4905     fwprint.set_defaults(func=firmwareList)
4906 
4907     fwDelete = fwflash_subproc.add_parser('delete', help="Delete an existing firmware version")
4908     fwDelete.add_argument('versionID', help="The version ID to delete from the firmware list. Ex: 63c95399")
4909     fwDelete.set_defaults(func=deleteFWVersion)
4910 
4911     fwDeleteAll = fwflash_subproc.add_parser('deleteAll', help="Delete ALL firmware versions")
4912     fwDeleteAll.set_defaults(func=deleteFWAll)
4913 
4914     #logging
4915     parser_logging = subparsers.add_parser("logging", help="logging controls")
4916     logging_sub = parser_logging.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
4917 
4918     #turn rest api logging on/off
4919     parser_rest_logging = logging_sub.add_parser("rest_api", help="turn rest api logging on/off")
4920     parser_rest_logging.add_argument('rest_logging', choices=['on', 'off'], help='The control option for rest logging: on, off')
4921     parser_rest_logging.set_defaults(func=restLogging)
4922 
4923     #remote logging
4924     parser_remote_logging = logging_sub.add_parser("remote_logging", help="Remote logging (rsyslog) commands")
4925     parser_remote_logging.add_argument('remote_logging', choices=['view', 'disable'], help='Remote logging (rsyslog) commands')
4926     parser_remote_logging.set_defaults(func=remoteLogging)
4927 
4928     #configure remote logging
4929     parser_remote_logging_config = logging_sub.add_parser("remote_logging_config", help="Configure remote logging (rsyslog)")
4930     parser_remote_logging_config.add_argument("-a", "--address", required=True, help="Set IP address of rsyslog server")
4931     parser_remote_logging_config.add_argument("-p", "--port", required=True, type=int, help="Set Port of rsyslog server")
4932     parser_remote_logging_config.set_defaults(func=remoteLoggingConfig)
4933 
4934     #certificate management
4935     parser_cert = subparsers.add_parser("certificate", help="Certificate management")
4936     certMgmt_subproc = parser_cert.add_subparsers(title='subcommands', description='valid certificate commands', help='sub-command help', dest='command')
4937 
4938     certUpdate = certMgmt_subproc.add_parser('update', help="Update the certificate")
4939     certUpdate.add_argument('type', choices=['server', 'client', 'authority'], help="certificate type to update")
4940     certUpdate.add_argument('service', choices=['https', 'ldap'], help="Service to update")
4941     certUpdate.add_argument('-f', '--fileloc', required=True, help="The absolute path to the certificate file")
4942     certUpdate.set_defaults(func=certificateUpdate)
4943 
4944     certDelete = certMgmt_subproc.add_parser('delete', help="Delete the certificate")
4945     certDelete.add_argument('type', choices=['server', 'client', 'authority'], help="certificate type to delete")
4946     certDelete.add_argument('service', choices=['https', 'ldap'], help="Service to delete the certificate")
4947     certDelete.set_defaults(func=certificateDelete)
4948 
4949     certReplace = certMgmt_subproc.add_parser('replace',
4950         help="Replace the certificate")
4951     certReplace.add_argument('type', choices=['server', 'client', 'authority'],
4952         help="certificate type to replace")
4953     certReplace.add_argument('service', choices=['https', 'ldap'],
4954         help="Service to replace the certificate")
4955     certReplace.add_argument('-f', '--fileloc', required=True,
4956         help="The absolute path to the certificate file")
4957     certReplace.set_defaults(func=certificateReplace)
4958 
4959     certDisplay = certMgmt_subproc.add_parser('display',
4960         help="Print the certificate")
4961     certDisplay.add_argument('type', choices=['server', 'client', 'authority'],
4962         help="certificate type to display")
4963     certDisplay.set_defaults(func=certificateDisplay)
4964 
4965     certList = certMgmt_subproc.add_parser('list',
4966         help="Certificate list")
4967     certList.set_defaults(func=certificateList)
4968 
4969     certGenerateCSR = certMgmt_subproc.add_parser('generatecsr', help="Generate CSR")
4970     certGenerateCSR.add_argument('type', choices=['server', 'client', 'authority'],
4971         help="Generate CSR")
4972     certGenerateCSR.add_argument('city',
4973         help="The city or locality of the organization making the request")
4974     certGenerateCSR.add_argument('commonName',
4975         help="The fully qualified domain name of the component that is being secured.")
4976     certGenerateCSR.add_argument('country',
4977         help="The country of the organization making the request")
4978     certGenerateCSR.add_argument('organization',
4979         help="The name of the organization making the request.")
4980     certGenerateCSR.add_argument('organizationUnit',
4981         help="The name of the unit or division of the organization making the request.")
4982     certGenerateCSR.add_argument('state',
4983         help="The state, province, or region of the organization making the request.")
4984     certGenerateCSR.add_argument('keyPairAlgorithm',  choices=['RSA', 'EC'],
4985         help="The type of key pair for use with signing algorithms.")
4986     certGenerateCSR.add_argument('keyCurveId',
4987         help="The curve ID to be used with the key, if needed based on the value of the 'KeyPairAlgorithm' parameter.")
4988     certGenerateCSR.add_argument('contactPerson',
4989         help="The name of the user making the request")
4990     certGenerateCSR.add_argument('email',
4991         help="The email address of the contact within the organization")
4992     certGenerateCSR.add_argument('alternativeNames',
4993         help="Additional hostnames of the component that is being secured")
4994     certGenerateCSR.add_argument('givenname',
4995         help="The given name of the user making the request")
4996     certGenerateCSR.add_argument('surname',
4997         help="The surname of the user making the request")
4998     certGenerateCSR.add_argument('unstructuredname',
4999         help="he unstructured name of the subject")
5000     certGenerateCSR.add_argument('initials',
5001         help="The initials of the user making the request")
5002     certGenerateCSR.set_defaults(func=certificateGenerateCSR)
5003 
5004     # local users
5005     parser_users = subparsers.add_parser("local_users", help="Work with local users")
5006     parser_users.add_argument('local_users', choices=['disableall','enableall', 'queryenabled'], help="Disable, enable or query local user accounts")
5007     parser_users.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
5008     parser_users.set_defaults(func=localUsers)
5009 
5010     #LDAP
5011     parser_ldap = subparsers.add_parser("ldap", help="LDAP controls")
5012     ldap_sub = parser_ldap.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
5013 
5014     #configure and enable LDAP
5015     parser_ldap_config = ldap_sub.add_parser("enable", help="Configure and enables the LDAP")
5016     parser_ldap_config.add_argument("-a", "--uri", required=True, help="Set LDAP server URI")
5017     parser_ldap_config.add_argument("-B", "--bindDN", required=True, help="Set the bind DN of the LDAP server")
5018     parser_ldap_config.add_argument("-b", "--baseDN", required=True, help="Set the base DN of the LDAP server")
5019     parser_ldap_config.add_argument("-p", "--bindPassword", required=True, help="Set the bind password of the LDAP server")
5020     parser_ldap_config.add_argument("-S", "--scope", choices=['sub','one', 'base'],
5021             help='Specifies the search scope:subtree, one level or base object.')
5022     parser_ldap_config.add_argument("-t", "--serverType", required=True, choices=['ActiveDirectory','OpenLDAP'],
5023             help='Specifies the configured server is ActiveDirectory(AD) or OpenLdap')
5024     parser_ldap_config.add_argument("-g","--groupAttrName", required=False, default='', help="Group Attribute Name")
5025     parser_ldap_config.add_argument("-u","--userAttrName", required=False, default='', help="User Attribute Name")
5026     parser_ldap_config.set_defaults(func=enableLDAPConfig)
5027 
5028     # disable LDAP
5029     parser_disable_ldap = ldap_sub.add_parser("disable", help="disables the LDAP")
5030     parser_disable_ldap.set_defaults(func=disableLDAP)
5031     # view-config
5032     parser_ldap_config = \
5033     ldap_sub.add_parser("view-config", help="prints out a list of all \
5034                         LDAPS's configured properties")
5035     parser_ldap_config.set_defaults(func=viewLDAPConfig)
5036 
5037     #create group privilege mapping
5038     parser_ldap_mapper = ldap_sub.add_parser("privilege-mapper", help="LDAP group privilege controls")
5039     parser_ldap_mapper_sub = parser_ldap_mapper.add_subparsers(title='subcommands', description='valid subcommands',
5040             help="sub-command help", dest='command')
5041 
5042     parser_ldap_mapper_create = parser_ldap_mapper_sub.add_parser("create", help="Create mapping of ldap group and privilege")
5043     parser_ldap_mapper_create.add_argument("-t", "--serverType", choices=['ActiveDirectory','OpenLDAP'],
5044             help='Specifies the configured server is ActiveDirectory(AD) or OpenLdap')
5045     parser_ldap_mapper_create.add_argument("-g","--groupName",required=True,help="Group Name")
5046     parser_ldap_mapper_create.add_argument("-p","--privilege",choices=['priv-admin','priv-operator','priv-user','priv-callback'],required=True,help="Privilege")
5047     parser_ldap_mapper_create.set_defaults(func=createPrivilegeMapping)
5048 
5049     #list group privilege mapping
5050     parser_ldap_mapper_list = parser_ldap_mapper_sub.add_parser("list",help="List privilege mapping")
5051     parser_ldap_mapper_list.add_argument("-t", "--serverType", choices=['ActiveDirectory','OpenLDAP'],
5052             help='Specifies the configured server is ActiveDirectory(AD) or OpenLdap')
5053     parser_ldap_mapper_list.set_defaults(func=listPrivilegeMapping)
5054 
5055     #delete group privilege mapping
5056     parser_ldap_mapper_delete = parser_ldap_mapper_sub.add_parser("delete",help="Delete privilege mapping")
5057     parser_ldap_mapper_delete.add_argument("-t", "--serverType", choices=['ActiveDirectory','OpenLDAP'],
5058             help='Specifies the configured server is ActiveDirectory(AD) or OpenLdap')
5059     parser_ldap_mapper_delete.add_argument("-g","--groupName",required=True,help="Group Name")
5060     parser_ldap_mapper_delete.set_defaults(func=deletePrivilegeMapping)
5061 
5062     #deleteAll group privilege mapping
5063     parser_ldap_mapper_delete = parser_ldap_mapper_sub.add_parser("purge",help="Delete All privilege mapping")
5064     parser_ldap_mapper_delete.add_argument("-t", "--serverType", choices=['ActiveDirectory','OpenLDAP'],
5065             help='Specifies the configured server is ActiveDirectory(AD) or OpenLdap')
5066     parser_ldap_mapper_delete.set_defaults(func=deleteAllPrivilegeMapping)
5067 
5068     # set local user password
5069     parser_set_password = subparsers.add_parser("set_password",
5070         help="Set password of local user")
5071     parser_set_password.add_argument( "-p", "--password", required=True,
5072         help="Password of local user")
5073     parser_set_password.set_defaults(func=setPassword)
5074 
5075     # network
5076     parser_nw = subparsers.add_parser("network", help="network controls")
5077     nw_sub = parser_nw.add_subparsers(title='subcommands',
5078                                       description='valid subcommands',
5079                                       help="sub-command help",
5080                                       dest='command')
5081 
5082     # enable DHCP
5083     parser_enable_dhcp = nw_sub.add_parser("enableDHCP",
5084                                            help="enables the DHCP on given "
5085                                            "Interface")
5086     parser_enable_dhcp.add_argument("-I", "--Interface", required=True,
5087                                     help="Name of the ethernet interface(it can"
5088                                     "be obtained by the "
5089                                     "command:network view-config)"
5090                                     "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5091     parser_enable_dhcp.set_defaults(func=enableDHCP)
5092 
5093     # disable DHCP
5094     parser_disable_dhcp = nw_sub.add_parser("disableDHCP",
5095                                             help="disables the DHCP on given "
5096                                             "Interface")
5097     parser_disable_dhcp.add_argument("-I", "--Interface", required=True,
5098                                      help="Name of the ethernet interface(it can"
5099                                      "be obtained by the "
5100                                      "command:network view-config)"
5101                                      "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5102     parser_disable_dhcp.set_defaults(func=disableDHCP)
5103 
5104     # get HostName
5105     parser_gethostname = nw_sub.add_parser("getHostName",
5106                                            help="prints out HostName")
5107     parser_gethostname.set_defaults(func=getHostname)
5108 
5109     # set HostName
5110     parser_sethostname = nw_sub.add_parser("setHostName", help="sets HostName")
5111     parser_sethostname.add_argument("-H", "--HostName", required=True,
5112                                     help="A HostName for the BMC")
5113     parser_sethostname.set_defaults(func=setHostname)
5114 
5115     # get domainname
5116     parser_getdomainname = nw_sub.add_parser("getDomainName",
5117                                              help="prints out DomainName of "
5118                                              "given Interface")
5119     parser_getdomainname.add_argument("-I", "--Interface", required=True,
5120                                       help="Name of the ethernet interface(it "
5121                                       "can be obtained by the "
5122                                       "command:network view-config)"
5123                                       "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5124     parser_getdomainname.set_defaults(func=getDomainName)
5125 
5126     # set domainname
5127     parser_setdomainname = nw_sub.add_parser("setDomainName",
5128                                              help="sets DomainName of given "
5129                                              "Interface")
5130     parser_setdomainname.add_argument("-D", "--DomainName", required=True,
5131                                       help="Ex: DomainName=Domain1,Domain2,...")
5132     parser_setdomainname.add_argument("-I", "--Interface", required=True,
5133                                       help="Name of the ethernet interface(it "
5134                                       "can be obtained by the "
5135                                       "command:network view-config)"
5136                                       "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5137     parser_setdomainname.set_defaults(func=setDomainName)
5138 
5139     # get MACAddress
5140     parser_getmacaddress = nw_sub.add_parser("getMACAddress",
5141                                              help="prints out MACAddress the "
5142                                              "given Interface")
5143     parser_getmacaddress.add_argument("-I", "--Interface", required=True,
5144                                       help="Name of the ethernet interface(it "
5145                                       "can be obtained by the "
5146                                       "command:network view-config)"
5147                                       "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5148     parser_getmacaddress.set_defaults(func=getMACAddress)
5149 
5150     # set MACAddress
5151     parser_setmacaddress = nw_sub.add_parser("setMACAddress",
5152                                              help="sets MACAddress")
5153     parser_setmacaddress.add_argument("-MA", "--MACAddress", required=True,
5154                                       help="A MACAddress for the given "
5155                                       "Interface")
5156     parser_setmacaddress.add_argument("-I", "--Interface", required=True,
5157                                     help="Name of the ethernet interface(it can"
5158                                     "be obtained by the "
5159                                     "command:network view-config)"
5160                                     "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5161     parser_setmacaddress.set_defaults(func=setMACAddress)
5162 
5163     # get DefaultGW
5164     parser_getdefaultgw = nw_sub.add_parser("getDefaultGW",
5165                                             help="prints out DefaultGateway "
5166                                             "the BMC")
5167     parser_getdefaultgw.set_defaults(func=getDefaultGateway)
5168 
5169     # set DefaultGW
5170     parser_setdefaultgw = nw_sub.add_parser("setDefaultGW",
5171                                              help="sets DefaultGW")
5172     parser_setdefaultgw.add_argument("-GW", "--DefaultGW", required=True,
5173                                       help="A DefaultGateway for the BMC")
5174     parser_setdefaultgw.set_defaults(func=setDefaultGateway)
5175 
5176     # view network Config
5177     parser_ldap_config = nw_sub.add_parser("view-config", help="prints out a "
5178                                            "list of all network's configured "
5179                                            "properties")
5180     parser_ldap_config.set_defaults(func=viewNWConfig)
5181 
5182     # get DNS
5183     parser_getDNS = nw_sub.add_parser("getDNS",
5184                                       help="prints out DNS servers on the "
5185                                       "given interface")
5186     parser_getDNS.add_argument("-I", "--Interface", required=True,
5187                                help="Name of the ethernet interface(it can"
5188                                "be obtained by the "
5189                                "command:network view-config)"
5190                                "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5191     parser_getDNS.set_defaults(func=getDNS)
5192 
5193     # set DNS
5194     parser_setDNS = nw_sub.add_parser("setDNS",
5195                                       help="sets DNS servers on the given "
5196                                       "interface")
5197     parser_setDNS.add_argument("-d", "--DNSServers", required=True,
5198                                help="Ex: DNSSERVERS=DNS1,DNS2,...")
5199     parser_setDNS.add_argument("-I", "--Interface", required=True,
5200                                help="Name of the ethernet interface(it can"
5201                                "be obtained by the "
5202                                "command:network view-config)"
5203                                "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5204     parser_setDNS.set_defaults(func=setDNS)
5205 
5206     # get NTP
5207     parser_getNTP = nw_sub.add_parser("getNTP",
5208                                       help="prints out NTP servers on the "
5209                                       "given interface")
5210     parser_getNTP.add_argument("-I", "--Interface", required=True,
5211                                help="Name of the ethernet interface(it can"
5212                                "be obtained by the "
5213                                "command:network view-config)"
5214                                "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5215     parser_getNTP.set_defaults(func=getNTP)
5216 
5217     # set NTP
5218     parser_setNTP = nw_sub.add_parser("setNTP",
5219                                       help="sets NTP servers on the given "
5220                                       "interface")
5221     parser_setNTP.add_argument("-N", "--NTPServers", required=True,
5222                                help="Ex: NTPSERVERS=NTP1,NTP2,...")
5223     parser_setNTP.add_argument("-I", "--Interface", required=True,
5224                                help="Name of the ethernet interface(it can"
5225                                "be obtained by the "
5226                                "command:network view-config)"
5227                                "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5228     parser_setNTP.set_defaults(func=setNTP)
5229 
5230     # configure IP
5231     parser_ip_config = nw_sub.add_parser("addIP", help="Sets IP address to"
5232                                          "given interface")
5233     parser_ip_config.add_argument("-a", "--address", required=True,
5234                                   help="IP address of given interface")
5235     parser_ip_config.add_argument("-gw", "--gateway", required=False, default='',
5236                                   help="The gateway for given interface")
5237     parser_ip_config.add_argument("-l", "--prefixLength", required=True,
5238                                   help="The prefixLength of IP address")
5239     parser_ip_config.add_argument("-p", "--type", required=True,
5240                                   choices=['ipv4', 'ipv6'],
5241                                   help="The protocol type of the given"
5242                                   "IP address")
5243     parser_ip_config.add_argument("-I", "--Interface", required=True,
5244                                   help="Name of the ethernet interface(it can"
5245                                   "be obtained by the "
5246                                   "command:network view-config)"
5247                                   "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5248     parser_ip_config.set_defaults(func=addIP)
5249 
5250     # getIP
5251     parser_getIP = nw_sub.add_parser("getIP", help="prints out IP address"
5252                                      "of given interface")
5253     parser_getIP.add_argument("-I", "--Interface", required=True,
5254                               help="Name of the ethernet interface(it can"
5255                               "be obtained by the command:network view-config)"
5256                               "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5257     parser_getIP.set_defaults(func=getIP)
5258 
5259     # rmIP
5260     parser_rmIP = nw_sub.add_parser("rmIP", help="deletes IP address"
5261                                      "of given interface")
5262     parser_rmIP.add_argument("-a", "--address", required=True,
5263                                   help="IP address to remove form given Interface")
5264     parser_rmIP.add_argument("-I", "--Interface", required=True,
5265                              help="Name of the ethernet interface(it can"
5266                              "be obtained by the command:network view-config)"
5267                              "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5268     parser_rmIP.set_defaults(func=deleteIP)
5269 
5270     # add VLAN
5271     parser_create_vlan = nw_sub.add_parser("addVLAN", help="enables VLAN "
5272                                            "on given interface with given "
5273                                            "VLAN Identifier")
5274     parser_create_vlan.add_argument("-I", "--Interface", required=True,
5275                                     choices=['eth0', 'eth1'],
5276                                     help="Name of the ethernet interface")
5277     parser_create_vlan.add_argument("-n", "--Identifier", required=True,
5278                                   help="VLAN Identifier")
5279     parser_create_vlan.set_defaults(func=addVLAN)
5280 
5281     # delete VLAN
5282     parser_delete_vlan = nw_sub.add_parser("deleteVLAN", help="disables VLAN "
5283                                            "on given interface with given "
5284                                            "VLAN Identifier")
5285     parser_delete_vlan.add_argument("-I", "--Interface", required=True,
5286                                     help="Name of the ethernet interface(it can"
5287                                     "be obtained by the "
5288                                     "command:network view-config)"
5289                                     "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5290     parser_delete_vlan.set_defaults(func=deleteVLAN)
5291 
5292     # viewDHCPConfig
5293     parser_viewDHCPConfig = nw_sub.add_parser("viewDHCPConfig",
5294                                               help="Shows DHCP configured "
5295                                               "Properties")
5296     parser_viewDHCPConfig.set_defaults(func=viewDHCPConfig)
5297 
5298     # configureDHCP
5299     parser_configDHCP = nw_sub.add_parser("configureDHCP",
5300                                           help="Configures/updates DHCP "
5301                                           "Properties")
5302     parser_configDHCP.add_argument("-d", "--DNSEnabled", type=str2bool,
5303                                    required=True, help="Sets DNSEnabled property")
5304     parser_configDHCP.add_argument("-n", "--HostNameEnabled", type=str2bool,
5305                                    required=True,
5306                                    help="Sets HostNameEnabled property")
5307     parser_configDHCP.add_argument("-t", "--NTPEnabled", type=str2bool,
5308                                    required=True,
5309                                    help="Sets NTPEnabled property")
5310     parser_configDHCP.add_argument("-s", "--SendHostNameEnabled", type=str2bool,
5311                                    required=True,
5312                                    help="Sets SendHostNameEnabled property")
5313     parser_configDHCP.set_defaults(func=configureDHCP)
5314 
5315     # network factory reset
5316     parser_nw_reset = nw_sub.add_parser("nwReset",
5317                                         help="Resets networks setting to "
5318                                         "factory defaults. "
5319                                         "note:Reset settings will be applied "
5320                                         "after BMC reboot")
5321     parser_nw_reset.set_defaults(func=nwReset)
5322 
5323     return parser
5324 
5325 def main(argv=None):
5326     """
5327          main function for running the command line utility as a sub application
5328     """
5329     global toolVersion
5330     toolVersion = "1.19"
5331     global isRedfishSupport
5332 
5333     parser = createCommandParser()
5334     args = parser.parse_args(argv)
5335 
5336     totTimeStart = int(round(time.time()*1000))
5337 
5338     if(sys.version_info < (3,0)):
5339         urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
5340     if sys.version_info >= (3,0):
5341         requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
5342     if (args.version):
5343         print("Version: "+ toolVersion)
5344         sys.exit(0)
5345     if (hasattr(args, 'fileloc') and args.fileloc is not None and 'print' in args.command):
5346         mysess = None
5347         print(selPrint('N/A', args, mysess))
5348     else:
5349         if(hasattr(args, 'host') and hasattr(args,'user')):
5350             if (args.askpw):
5351                 pw = getpass.getpass()
5352             elif(args.PW is not None):
5353                 pw = args.PW
5354             elif(args.PWenvvar):
5355                 pw = os.environ['OPENBMCTOOL_PASSWORD']
5356             else:
5357                 print("You must specify a password")
5358                 sys.exit()
5359             logintimeStart = int(round(time.time()*1000))
5360             mysess = login(args.host, args.user, pw, args.json,
5361                            args.command == 'set_password')
5362             if(mysess == None):
5363                 print("Login Failed!")
5364                 sys.exit()
5365             if(sys.version_info < (3,0)):
5366                 if isinstance(mysess, basestring):
5367                     print(mysess)
5368                     sys.exit(1)
5369             elif sys.version_info >= (3,0):
5370                 if isinstance(mysess, str):
5371                     print(mysess)
5372                     sys.exit(1)
5373             logintimeStop = int(round(time.time()*1000))
5374             isRedfishSupport = redfishSupportPresent(args.host,mysess)
5375             commandTimeStart = int(round(time.time()*1000))
5376             output = args.func(args.host, args, mysess)
5377             commandTimeStop = int(round(time.time()*1000))
5378             if isinstance(output, dict):
5379                 print(json.dumps(output, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False))
5380             else:
5381                 print(output)
5382             if (mysess is not None):
5383                 logout(args.host, args.user, pw, mysess, args.json)
5384             if(args.procTime):
5385                 print("Total time: " + str(int(round(time.time()*1000))- totTimeStart))
5386                 print("loginTime: " + str(logintimeStop - logintimeStart))
5387                 print("command Time: " + str(commandTimeStop - commandTimeStart))
5388         else:
5389             print("usage:\n"
5390                   "  OPENBMCTOOL_PASSWORD=secret  # if using -E\n"
5391                   "  openbmctool.py [-h] -H HOST -U USER {-A | -P PW | -E} [-j]\n" +
5392                       "\t[-t POLICYTABLELOC] [-V]\n" +
5393                       "\t{fru,sensors,sel,chassis,collect_service_data, \
5394                           health_check,dump,bmc,mc,gardclear,firmware,logging}\n" +
5395                       "\t...\n" +
5396                       "openbmctool.py: error: the following arguments are required: -H/--host, -U/--user")
5397             sys.exit()
5398 
5399 if __name__ == '__main__':
5400     """
5401          main function when called from the command line
5402 
5403     """
5404     import sys
5405 
5406     isTTY = sys.stdout.isatty()
5407     assert sys.version_info >= (2,7)
5408     main()
5409