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"""
17import argparse
18import requests
19import getpass
20import json
21import os
22import urllib3
23import time, datetime
24import binascii
25import subprocess
26import platform
27import zipfile
28import tarfile
29import tempfile
30import hashlib
31import re
32import uuid
33import ssl
34import socket
35import select
36import http.client
37from subprocess import check_output
38import traceback
39
40
41MAX_NBD_PACKET_SIZE = 131088
42jsonHeader = {'Content-Type' : 'application/json'}
43xAuthHeader = {}
44baseTimeout = 60
45serverTypeMap = {
46        'ActiveDirectory' : 'active_directory',
47        'OpenLDAP' : 'openldap'
48        }
49
50class 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
113def 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()["Oem"]["OpenBmc"]['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
128def 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('SESSION=(\w+);', cookie)
134       return match.group(1)
135
136
137
138def 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
146def 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
154def isThisProcessRunning( process_name ):
155  pid = get_pid(process_name)
156  if (pid == 0 ):
157    return False
158  else:
159    return True
160
161def 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
202def 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
233def 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
294def 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
316def 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
334def 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
346def 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
357def 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
369def 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
397def 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
419def 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('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
466def 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
486def 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
538def 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
579def 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
595def 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
674def 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
756def 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
774def 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
848def 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
879def 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
915def 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
1063def 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
1172def 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
1238def 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
1250def 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
1285def 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
1305def 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
1352def 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
1422def 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
1467def 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
1484def dumpRetrieve(host, args, session):
1485    """
1486         Downloads dump of given dump type
1487
1488         @param host: string, the hostname or IP address of the bmc
1489         @param args: contains additional arguments used by the collectServiceData 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    dumpType = args.dumpType
1494    if (args.dumpType=="SystemDump"):
1495        dumpResp=systemDumpRetrieve(host,args,session)
1496    elif(args.dumpType=="bmc"):
1497        dumpResp=bmcDumpRetrieve(host,args,session)
1498    return dumpResp
1499
1500def dumpList(host, args, session):
1501    """
1502         Lists dump of the given dump type
1503
1504         @param host: string, the hostname or IP address of the bmc
1505         @param args: contains additional arguments used by the collectServiceData sub command
1506         @param session: the active session to use
1507         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1508    """
1509    if (args.dumpType=="SystemDump"):
1510        dumpResp=systemDumpList(host,args,session)
1511    elif(args.dumpType=="bmc"):
1512        dumpResp=bmcDumpList(host,args,session)
1513    return dumpResp
1514
1515def dumpDelete(host, args, session):
1516    """
1517         Deletes dump of the given dump type
1518
1519         @param host: string, the hostname or IP address of the bmc
1520         @param args: contains additional arguments used by the collectServiceData sub command
1521         @param session: the active session to use
1522         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1523    """
1524    if (args.dumpType=="SystemDump"):
1525        dumpResp=systemDumpDelete(host,args,session)
1526    elif(args.dumpType=="bmc"):
1527        dumpResp=bmcDumpDelete(host,args,session)
1528    return dumpResp
1529
1530def dumpDeleteAll(host, args, session):
1531    """
1532         Deletes all dumps of the given dump type
1533
1534         @param host: string, the hostname or IP address of the bmc
1535         @param args: contains additional arguments used by the collectServiceData sub command
1536         @param session: the active session to use
1537         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1538    """
1539    if (args.dumpType=="SystemDump"):
1540        dumpResp=systemDumpDeleteAll(host,args,session)
1541    elif(args.dumpType=="bmc"):
1542        dumpResp=bmcDumpDeleteAll(host,args,session)
1543    return dumpResp
1544
1545def dumpCreate(host, args, session):
1546    """
1547         Creates dump for the given dump type
1548
1549         @param host: string, the hostname or IP address of the bmc
1550         @param args: contains additional arguments used by the collectServiceData sub command
1551         @param session: the active session to use
1552         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1553    """
1554    if (args.dumpType=="SystemDump"):
1555        dumpResp=systemDumpCreate(host,args,session)
1556    elif(args.dumpType=="bmc"):
1557        dumpResp=bmcDumpCreate(host,args,session)
1558    return dumpResp
1559
1560
1561def bmcDumpRetrieve(host, args, session):
1562    """
1563         Downloads a dump file from the bmc
1564
1565         @param host: string, the hostname or IP address of the bmc
1566         @param args: contains additional arguments used by the collectServiceData sub command
1567         @param session: the active session to use
1568         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1569    """
1570    dumpNum = args.dumpNum
1571    if (args.dumpSaveLoc is not None):
1572        saveLoc = args.dumpSaveLoc
1573    else:
1574        saveLoc = tempfile.gettempdir()
1575    url ='https://'+host+'/download/dump/' + str(dumpNum)
1576    try:
1577        r = session.get(url, headers=jsonHeader, stream=True, verify=False, timeout=baseTimeout)
1578        if (args.dumpSaveLoc is not None):
1579            if os.path.exists(saveLoc):
1580                if saveLoc[-1] != os.path.sep:
1581                    saveLoc = saveLoc + os.path.sep
1582                filename = saveLoc + host+'-dump' + str(dumpNum) + '.tar.xz'
1583
1584            else:
1585                return 'Invalid save location specified'
1586        else:
1587            filename = tempfile.gettempdir()+os.sep + host+'-dump' + str(dumpNum) + '.tar.xz'
1588
1589        with open(filename, 'wb') as f:
1590                    for chunk in r.iter_content(chunk_size =1024):
1591                        if chunk:
1592                            f.write(chunk)
1593        return 'Saved as ' + filename
1594
1595    except(requests.exceptions.Timeout):
1596        return connectionErrHandler(args.json, "Timeout", None)
1597
1598    except(requests.exceptions.ConnectionError) as err:
1599        return connectionErrHandler(args.json, "ConnectionError", err)
1600
1601def bmcDumpList(host, args, session):
1602    """
1603         Lists the number of dump files on the bmc
1604
1605         @param host: string, the hostname or IP address of the bmc
1606         @param args: contains additional arguments used by the collectServiceData sub command
1607         @param session: the active session to use
1608         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1609    """
1610    url ='https://'+host+'/xyz/openbmc_project/dump/list'
1611    try:
1612        r = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
1613        dumpList = r.json()
1614        formattedList = []
1615        #remove items that aren't dump entries 'entry, internal, manager endpoints'
1616        if 'data' in dumpList:
1617            for entry in dumpList['data']:
1618                if 'entry' in entry:
1619                    if entry.split('/')[-1].isnumeric():
1620                        formattedList.append(entry)
1621            dumpList['data']= formattedList
1622        return dumpList
1623    except(requests.exceptions.Timeout):
1624        return connectionErrHandler(args.json, "Timeout", None)
1625
1626    except(requests.exceptions.ConnectionError) as err:
1627        return connectionErrHandler(args.json, "ConnectionError", err)
1628
1629def bmcDumpDelete(host, args, session):
1630    """
1631         Deletes BMC dump files from the bmc
1632
1633         @param host: string, the hostname or IP address of the bmc
1634         @param args: contains additional arguments used by the collectServiceData sub command
1635         @param session: the active session to use
1636         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1637    """
1638    dumpList = []
1639    successList = []
1640    failedList = []
1641    if args.dumpNum is not None:
1642        if isinstance(args.dumpNum, list):
1643            dumpList = args.dumpNum
1644        else:
1645            dumpList.append(args.dumpNum)
1646        for dumpNum in dumpList:
1647            url ='https://'+host+'/xyz/openbmc_project/dump/entry/'+str(dumpNum)+'/action/Delete'
1648            try:
1649                r = session.post(url, headers=jsonHeader, json = {"data": []}, verify=False, timeout=baseTimeout)
1650                if r.status_code == 200:
1651                    successList.append(str(dumpNum))
1652                else:
1653                    failedList.append(str(dumpNum))
1654            except(requests.exceptions.Timeout):
1655                return connectionErrHandler(args.json, "Timeout", None)
1656            except(requests.exceptions.ConnectionError) as err:
1657                return connectionErrHandler(args.json, "ConnectionError", err)
1658        output = "Successfully deleted dumps: " + ', '.join(successList)
1659        if(len(failedList)>0):
1660            output+= '\nFailed to delete dumps: ' + ', '.join(failedList)
1661        return output
1662    else:
1663        return 'You must specify an entry number to delete'
1664
1665def bmcDumpDeleteAll(host, args, session):
1666    """
1667         Deletes All BMC dump files from the bmc
1668
1669         @param host: string, the hostname or IP address of the bmc
1670         @param args: contains additional arguments used by the collectServiceData sub command
1671         @param session: the active session to use
1672         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1673    """
1674    dumpResp = bmcDumpList(host, args, session)
1675    if 'FQPSPIN0000M' in dumpResp or 'FQPSPIN0001M'in dumpResp:
1676        return dumpResp
1677    dumpList = dumpResp['data']
1678    d = vars(args)
1679    dumpNums = []
1680    for dump in dumpList:
1681        dumpNum = dump.strip().split('/')[-1]
1682        if dumpNum.isdigit():
1683            dumpNums.append(int(dumpNum))
1684    d['dumpNum'] = dumpNums
1685
1686    return bmcDumpDelete(host, args, session)
1687
1688
1689def bmcDumpCreate(host, args, session):
1690    """
1691         Creates a bmc dump file
1692
1693         @param host: string, the hostname or IP address of the bmc
1694         @param args: contains additional arguments used by the collectServiceData sub command
1695         @param session: the active session to use
1696         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1697    """
1698    url = 'https://'+host+'/xyz/openbmc_project/dump/action/CreateDump'
1699    try:
1700        r = session.post(url, headers=jsonHeader, json = {"data": []}, verify=False, timeout=baseTimeout)
1701        info = r.json()
1702        if(r.status_code == 200 and not args.json):
1703            return ('Dump successfully created')
1704        elif(args.json):
1705            return info
1706        elif 'data' in info:
1707            if 'QuotaExceeded' in info['data']['description']:
1708                return 'BMC dump space is full. Please delete at least one existing dump entry and try again.'
1709            else:
1710                return "Failed to create a BMC dump. BMC Response:\n {resp}".format(resp=info)
1711        else:
1712            return "Failed to create a BMC dump. BMC Response:\n {resp}".format(resp=info)
1713    except(requests.exceptions.Timeout):
1714        return connectionErrHandler(args.json, "Timeout", None)
1715    except(requests.exceptions.ConnectionError) as err:
1716        return connectionErrHandler(args.json, "ConnectionError", err)
1717
1718
1719def systemDumpRetrieve(host, args, session):
1720    """
1721         Downloads system dump
1722
1723         @param host: string, the hostname or IP address of the bmc
1724         @param args: contains additional arguments used by the collectServiceData sub command
1725         @param session: the active session to use
1726         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1727    """
1728    NBDSetup(host,args,session)
1729    pipe = NBDPipe()
1730    pipe.openHTTPSocket(args)
1731    pipe.openTCPSocket()
1732    pipe.waitformessage()
1733
1734def systemDumpList(host, args, session):
1735    """
1736         Lists system dumps
1737
1738         @param host: string, the hostname or IP address of the bmc
1739         @param args: contains additional arguments used by the collectServiceData sub command
1740         @param session: the active session to use
1741         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1742    """
1743    url = "https://"+host+"/redfish/v1/Systems/system/LogServices/Dump/Entries"
1744    try:
1745        r = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
1746        dumpList = r.json()
1747        return dumpList
1748    except(requests.exceptions.Timeout):
1749        return connectionErrHandler(args.json, "Timeout", None)
1750
1751    except(requests.exceptions.ConnectionError) as err:
1752        return connectionErrHandler(args.json, "ConnectionError", err)
1753
1754
1755def systemDumpDelete(host, args, session):
1756    """
1757         Deletes system dump
1758
1759         @param host: string, the hostname or IP address of the bmc
1760         @param args: contains additional arguments used by the collectServiceData sub command
1761         @param session: the active session to use
1762         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1763    """
1764    dumpList = []
1765    successList = []
1766    failedList = []
1767    if args.dumpNum is not None:
1768        if isinstance(args.dumpNum, list):
1769            dumpList = args.dumpNum
1770        else:
1771            dumpList.append(args.dumpNum)
1772        for dumpNum in dumpList:
1773            url = 'https://'+host+'/redfish/v1/Systems/system/LogServices/Dump/Entries/'+ str(dumpNum)
1774            try:
1775                r = session.delete(url, headers=jsonHeader, json = {"data": []}, verify=False, timeout=baseTimeout)
1776                if r.status_code == 200:
1777                    successList.append(str(dumpNum))
1778                else:
1779                    failedList.append(str(dumpNum))
1780            except(requests.exceptions.Timeout):
1781                return connectionErrHandler(args.json, "Timeout", None)
1782            except(requests.exceptions.ConnectionError) as err:
1783                return connectionErrHandler(args.json, "ConnectionError", err)
1784        output = "Successfully deleted dumps: " + ', '.join(successList)
1785        if(len(failedList)>0):
1786            output+= '\nFailed to delete dumps: ' + ', '.join(failedList)
1787        return output
1788    else:
1789        return 'You must specify an entry number to delete'
1790
1791def systemDumpDeleteAll(host, args, session):
1792    """
1793         Deletes All system dumps
1794
1795         @param host: string, the hostname or IP address of the bmc
1796         @param args: contains additional arguments used by the collectServiceData sub command
1797         @param session: the active session to use
1798         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1799    """
1800    url = 'https://'+host+'/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.ClearLog'
1801    try:
1802        r = session.post(url, headers=jsonHeader, json = {"data": []}, verify=False, timeout=baseTimeout)
1803        if(r.status_code == 200 and not args.json):
1804            return ('Dumps successfully cleared')
1805        elif(args.json):
1806            return r.json()
1807        else:
1808            return ('Failed to clear dumps')
1809    except(requests.exceptions.Timeout):
1810        return connectionErrHandler(args.json, "Timeout", None)
1811    except(requests.exceptions.ConnectionError) as err:
1812        return connectionErrHandler(args.json, "ConnectionError", err)
1813
1814def systemDumpCreate(host, args, session):
1815    """
1816         Creates a system dump
1817
1818         @param host: string, the hostname or IP address of the bmc
1819         @param args: contains additional arguments used by the collectServiceData sub command
1820         @param session: the active session to use
1821         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1822    """
1823    url =  'https://'+host+'/redfish/v1/Systems/system/LogServices/Dump/Actions/Oem/OemLogService.CollectDiagnosticData'
1824    try:
1825        r = session.post(url, headers=jsonHeader, json = {"data": []}, verify=False, timeout=baseTimeout)
1826        if(r.status_code == 200):
1827            return r.json()
1828        else:
1829            return ('Failed to create dump')
1830    except(requests.exceptions.Timeout):
1831        return connectionErrHandler(args.json, "Timeout", None)
1832    except(requests.exceptions.ConnectionError) as err:
1833        return connectionErrHandler(args.json, "ConnectionError", err)
1834
1835def csdDumpInitiate(host, args, session):
1836    """
1837        Starts the process of getting the current list of dumps then initiates the creation of one.
1838
1839         @param host: string, the hostname or IP address of the bmc
1840         @param args: contains additional arguments used by the collectServiceData sub command
1841         @param session: the active session to use
1842         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1843    """
1844    errorInfo = ""
1845    dumpcount = 0
1846    try:
1847        d = vars(args)
1848        d['json'] = True
1849    except Exception as e:
1850        errorInfo += "Failed to set the json flag to True \n Exception: {eInfo}\n".format(eInfo=e)
1851        exc_type, exc_obj, exc_tb = sys.exc_info()
1852        fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
1853        errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
1854        errorInfo += traceback.format_exc()
1855
1856    try:
1857        for i in range(3):
1858            dumpInfo = bmcDumpList(host, args, session)
1859            if 'data' in dumpInfo:
1860                dumpcount = len(dumpInfo['data'])
1861                break
1862            else:
1863                errorInfo+= "Dump List Message returned: " + json.dumps(dumpInfo,indent=0, separators=(',', ':')).replace('\n','') +"\n"
1864    except Exception as e:
1865        errorInfo+= "Failed to collect the list of dumps.\nException: {eInfo}\n".format(eInfo=e)
1866        exc_type, exc_obj, exc_tb = sys.exc_info()
1867        fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
1868        errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
1869        errorInfo += traceback.format_exc()
1870
1871    #Create a user initiated dump
1872    dumpFailure = True
1873    try:
1874        for i in range(3):
1875            dumpcreated = bmcDumpCreate(host, args, session)
1876            if 'message' in dumpcreated:
1877                if 'ok' in dumpcreated['message'].lower():
1878                    dumpFailure = False
1879                    break
1880                elif 'data' in dumpcreated:
1881                    if 'QuotaExceeded' in dumpcreated['data']['description']:
1882                        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.')
1883                        errorInfo+='Dump Space is full. No new dump was created with this collection'
1884                        break
1885                    else:
1886                        errorInfo+= "Dump create message returned: " + json.dumps(dumpcreated,indent=0, separators=(',', ':')).replace('\n','') +"\n"
1887                else:
1888                    errorInfo+= "Dump create message returned: " + json.dumps(dumpcreated,indent=0, separators=(',', ':')).replace('\n','') +"\n"
1889            else:
1890                errorInfo+= "Dump create message returned: " + json.dumps(dumpcreated,indent=0, separators=(',', ':')).replace('\n','') +"\n"
1891    except Exception as e:
1892        errorInfo+= "Dump create exception encountered: {eInfo}\n".format(eInfo=e)
1893        exc_type, exc_obj, exc_tb = sys.exc_info()
1894        fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
1895        errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
1896        errorInfo += traceback.format_exc()
1897
1898    output = {}
1899    output['errors'] = errorInfo
1900    output['dumpcount'] = dumpcount
1901    if dumpFailure: output['dumpFailure'] = True
1902    return output
1903
1904def csdInventory(host, args,session, fileDir):
1905    """
1906        Collects the BMC inventory, retrying if necessary
1907
1908         @param host: string, the hostname or IP address of the bmc
1909         @param args: contains additional arguments used by the collectServiceData sub command
1910         @param session: the active session to use
1911         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1912         @param fileDir: string representation of the path to use for putting files created
1913    """
1914    errorInfo = "===========Inventory =============\n"
1915    output={}
1916    inventoryCollected = False
1917    try:
1918        for i in range(3):
1919            frulist = fruPrint(host, args, session)
1920            if 'Hardware' in frulist:
1921                inventoryCollected = True
1922                break
1923            else:
1924                errorInfo += json.dumps(frulist, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False) + '\n'
1925    except Exception as e:
1926        errorInfo += "Inventory collection exception: {eInfo}\n".format(eInfo=e)
1927        exc_type, exc_obj, exc_tb = sys.exc_info()
1928        fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
1929        errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
1930        errorInfo += traceback.format_exc()
1931    if inventoryCollected:
1932        try:
1933            with open(fileDir +os.sep+'inventory.txt', 'w') as f:
1934                f.write(json.dumps(frulist, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False) + '\n')
1935            print("Inventory collected and stored in " + fileDir + os.sep + "inventory.txt")
1936            output['fileLoc'] = fileDir+os.sep+'inventory.txt'
1937        except Exception as e:
1938            print("Failed to write inventory to file.")
1939            errorInfo += "Error writing inventory to the file. Exception: {eInfo}\n".format(eInfo=e)
1940            exc_type, exc_obj, exc_tb = sys.exc_info()
1941            fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
1942            errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
1943            errorInfo += traceback.format_exc()
1944
1945    output['errors'] = errorInfo
1946
1947    return output
1948
1949def csdSensors(host, args,session, fileDir):
1950    """
1951        Collects the BMC sensor readings, retrying if necessary
1952
1953         @param host: string, the hostname or IP address of the bmc
1954         @param args: contains additional arguments used by the collectServiceData sub command
1955         @param session: the active session to use
1956         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
1957         @param fileDir: string representation of the path to use for putting files created
1958    """
1959    errorInfo = "===========Sensors =============\n"
1960    sensorsCollected = False
1961    output={}
1962    try:
1963        d = vars(args)
1964        d['json'] = False
1965    except Exception as e:
1966        errorInfo += "Failed to set the json flag to False \n Exception: {eInfo}\n".format(eInfo=e)
1967        exc_type, exc_obj, exc_tb = sys.exc_info()
1968        fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
1969        errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
1970        errorInfo += traceback.format_exc()
1971
1972    try:
1973        for i in range(3):
1974            sensorReadings = sensor(host, args, session)
1975            if 'OCC0' in sensorReadings:
1976                sensorsCollected = True
1977                break
1978            else:
1979                errorInfo += sensorReadings
1980    except Exception as e:
1981        errorInfo += "Sensor reading collection exception: {eInfo}\n".format(eInfo=e)
1982        exc_type, exc_obj, exc_tb = sys.exc_info()
1983        fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
1984        errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
1985        errorInfo += traceback.format_exc()
1986    if sensorsCollected:
1987        try:
1988            with open(fileDir +os.sep+'sensorReadings.txt', 'w') as f:
1989                f.write(sensorReadings)
1990            print("Sensor readings collected and stored in " + fileDir + os.sep+ "sensorReadings.txt")
1991            output['fileLoc'] = fileDir+os.sep+'sensorReadings.txt'
1992        except Exception as e:
1993            print("Failed to write sensor readings to file system.")
1994            errorInfo += "Error writing sensor readings to the file. 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    output['errors'] = errorInfo
2001    return output
2002
2003def csdLEDs(host,args, session, fileDir):
2004    """
2005        Collects the BMC LED status, retrying if necessary
2006
2007         @param host: string, the hostname or IP address of the bmc
2008         @param args: contains additional arguments used by the collectServiceData sub command
2009         @param session: the active session to use
2010         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
2011         @param fileDir: string representation of the path to use for putting files created
2012    """
2013    errorInfo = "===========LEDs =============\n"
2014    ledsCollected = False
2015    output={}
2016    try:
2017        d = vars(args)
2018        d['json'] = True
2019    except Exception as e:
2020        errorInfo += "Failed to set the json flag to False \n Exception: {eInfo}\n".format(eInfo=e)
2021        exc_type, exc_obj, exc_tb = sys.exc_info()
2022        fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2023        errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2024        errorInfo += traceback.format_exc()
2025    try:
2026        url="https://"+host+"/xyz/openbmc_project/led/enumerate"
2027        httpHeader = {'Content-Type':'application/json'}
2028        for i in range(3):
2029            try:
2030                ledRes = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
2031                if ledRes.status_code == 200:
2032                    ledsCollected = True
2033                    leds = ledRes.json()['data']
2034                    break
2035                else:
2036                    errorInfo += ledRes.text
2037            except(requests.exceptions.Timeout):
2038                errorInfo+=json.dumps( connectionErrHandler(args.json, "Timeout", None), sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False) + '\n'
2039            except(requests.exceptions.ConnectionError) as err:
2040                errorInfo += json.dumps(connectionErrHandler(args.json, "ConnectionError", err), sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False) + '\n'
2041                exc_type, exc_obj, exc_tb = sys.exc_info()
2042                fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2043                errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2044                errorInfo += traceback.format_exc()
2045    except Exception as e:
2046        errorInfo += "LED status collection exception: {eInfo}\n".format(eInfo=e)
2047        exc_type, exc_obj, exc_tb = sys.exc_info()
2048        fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2049        errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2050        errorInfo += traceback.format_exc()
2051
2052    if ledsCollected:
2053        try:
2054            with open(fileDir +os.sep+'ledStatus.txt', 'w') as f:
2055                f.write(json.dumps(leds, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False) + '\n')
2056            print("LED status collected and stored in " + fileDir + os.sep+ "ledStatus.txt")
2057            output['fileLoc'] = fileDir+os.sep+'ledStatus.txt'
2058        except Exception as e:
2059            print("Failed to write LED status to file system.")
2060            errorInfo += "Error writing LED status to the file. Exception: {eInfo}\n".format(eInfo=e)
2061            exc_type, exc_obj, exc_tb = sys.exc_info()
2062            fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2063            errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2064            errorInfo += traceback.format_exc()
2065
2066    output['errors'] = errorInfo
2067    return output
2068
2069def csdSelShortList(host, args, session, fileDir):
2070    """
2071        Collects the BMC log entries, retrying if necessary
2072
2073         @param host: string, the hostname or IP address of the bmc
2074         @param args: contains additional arguments used by the collectServiceData sub command
2075         @param session: the active session to use
2076         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
2077         @param fileDir: string representation of the path to use for putting files created
2078    """
2079    errorInfo = "===========SEL Short List =============\n"
2080    selsCollected = False
2081    output={}
2082    try:
2083        d = vars(args)
2084        d['json'] = False
2085    except Exception as e:
2086        errorInfo += "Failed to set the json flag to False \n Exception: {eInfo}\n".format(eInfo=e)
2087        exc_type, exc_obj, exc_tb = sys.exc_info()
2088        fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2089        errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2090        errorInfo += traceback.format_exc()
2091
2092    try:
2093        for i in range(3):
2094            sels = selPrint(host,args,session)
2095            if '----Active Alerts----' in sels or 'No log entries found' in sels or '----Historical Alerts----' in sels:
2096                selsCollected = True
2097                break
2098            else:
2099                errorInfo += sels + '\n'
2100    except Exception as e:
2101        errorInfo += "SEL short list collection exception: {eInfo}\n".format(eInfo=e)
2102        exc_type, exc_obj, exc_tb = sys.exc_info()
2103        fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2104        errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2105        errorInfo += traceback.format_exc()
2106
2107    if selsCollected:
2108        try:
2109            with open(fileDir +os.sep+'SELshortlist.txt', 'w') as f:
2110                f.write(sels)
2111            print("SEL short list collected and stored in " + fileDir + os.sep+ "SELshortlist.txt")
2112            output['fileLoc'] = fileDir+os.sep+'SELshortlist.txt'
2113        except Exception as e:
2114            print("Failed to write SEL short list to file system.")
2115            errorInfo += "Error writing SEL short list to the file. Exception: {eInfo}\n".format(eInfo=e)
2116            exc_type, exc_obj, exc_tb = sys.exc_info()
2117            fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2118            errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2119            errorInfo += traceback.format_exc()
2120
2121    output['errors'] = errorInfo
2122    return output
2123
2124def csdParsedSels(host, args, session, fileDir):
2125    """
2126        Collects the BMC log entries, retrying if necessary
2127
2128         @param host: string, the hostname or IP address of the bmc
2129         @param args: contains additional arguments used by the collectServiceData sub command
2130         @param session: the active session to use
2131         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
2132         @param fileDir: string representation of the path to use for putting files created
2133    """
2134    errorInfo = "===========SEL Parsed List =============\n"
2135    selsCollected = False
2136    output={}
2137    try:
2138        d = vars(args)
2139        d['json'] = True
2140        d['fullEsel'] = True
2141    except Exception as e:
2142        errorInfo += "Failed to set the json flag to True \n Exception: {eInfo}\n".format(eInfo=e)
2143        exc_type, exc_obj, exc_tb = sys.exc_info()
2144        fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2145        errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2146        errorInfo += traceback.format_exc()
2147
2148    try:
2149        for i in range(3):
2150            parsedfullsels = json.loads(selPrint(host,args,session))
2151            if 'numAlerts' in parsedfullsels:
2152                selsCollected = True
2153                break
2154            else:
2155                errorInfo += parsedfullsels + '\n'
2156    except Exception as e:
2157        errorInfo += "Parsed full SELs collection exception: {eInfo}\n".format(eInfo=e)
2158        exc_type, exc_obj, exc_tb = sys.exc_info()
2159        fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2160        errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2161        errorInfo += traceback.format_exc()
2162
2163    if selsCollected:
2164        try:
2165            sortedSELs = sortSELs(parsedfullsels)
2166            with open(fileDir +os.sep+'parsedSELs.txt', 'w') as f:
2167                for log in sortedSELs[0]:
2168                    esel = ""
2169                    parsedfullsels[sortedSELs[1][str(log)]]['timestamp'] = datetime.datetime.fromtimestamp(int(parsedfullsels[sortedSELs[1][str(log)]]['timestamp']/1000)).strftime("%Y-%m-%d %H:%M:%S")
2170                    if ('raweSEL' in parsedfullsels[sortedSELs[1][str(log)]] and args.devdebug):
2171                        esel = parsedfullsels[sortedSELs[1][str(log)]]['raweSEL']
2172                        del parsedfullsels[sortedSELs[1][str(log)]]['raweSEL']
2173                    f.write(json.dumps(parsedfullsels[sortedSELs[1][str(log)]],sort_keys=True, indent=4, separators=(',', ': ')))
2174                    if(args.devdebug and esel != ""):
2175                        f.write(parseESEL(args, esel))
2176            print("Parsed SELs collected and stored in " + fileDir + os.sep+ "parsedSELs.txt")
2177            output['fileLoc'] = fileDir+os.sep+'parsedSELs.txt'
2178        except Exception as e:
2179            print("Failed to write fully parsed SELs to file system.")
2180            errorInfo += "Error writing fully parsed SELs to the file. Exception: {eInfo}\n".format(eInfo=e)
2181            exc_type, exc_obj, exc_tb = sys.exc_info()
2182            fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2183            errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2184            errorInfo += traceback.format_exc()
2185
2186    output['errors'] = errorInfo
2187    return output
2188
2189def csdFullEnumeration(host, args, session, fileDir):
2190    """
2191        Collects a full enumeration of /xyz/openbmc_project/, retrying if necessary
2192
2193         @param host: string, the hostname or IP address of the bmc
2194         @param args: contains additional arguments used by the collectServiceData sub command
2195         @param session: the active session to use
2196         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
2197         @param fileDir: string representation of the path to use for putting files created
2198    """
2199    errorInfo = "===========BMC Full Enumeration =============\n"
2200    bmcFullCollected = False
2201    output={}
2202    try:
2203        d = vars(args)
2204        d['json'] = True
2205    except Exception as e:
2206        errorInfo += "Failed to set the json flag to False \n Exception: {eInfo}\n".format(eInfo=e)
2207        exc_type, exc_obj, exc_tb = sys.exc_info()
2208        fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2209        errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2210        errorInfo += traceback.format_exc()
2211    try:
2212        print("Attempting to get a full BMC enumeration")
2213        url="https://"+host+"/xyz/openbmc_project/enumerate"
2214        httpHeader = {'Content-Type':'application/json'}
2215        for i in range(3):
2216            try:
2217                bmcRes = session.get(url, headers=jsonHeader, verify=False, timeout=180)
2218                if bmcRes.status_code == 200:
2219                    bmcFullCollected = True
2220                    fullEnumeration = bmcRes.json()
2221                    break
2222                else:
2223                    errorInfo += bmcRes.text
2224            except(requests.exceptions.Timeout):
2225                errorInfo+=json.dumps( connectionErrHandler(args.json, "Timeout", None), sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False) + '\n'
2226            except(requests.exceptions.ConnectionError) as err:
2227                errorInfo += json.dumps(connectionErrHandler(args.json, "ConnectionError", err), sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False) + '\n'
2228                exc_type, exc_obj, exc_tb = sys.exc_info()
2229                fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2230                errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2231                errorInfo += traceback.format_exc()
2232    except Exception as e:
2233        errorInfo += "RAW BMC data collection exception: {eInfo}\n".format(eInfo=e)
2234        exc_type, exc_obj, exc_tb = sys.exc_info()
2235        fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2236        errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2237        errorInfo += traceback.format_exc()
2238
2239    if bmcFullCollected:
2240        try:
2241            with open(fileDir +os.sep+'bmcFullRaw.txt', 'w') as f:
2242                f.write(json.dumps(fullEnumeration, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False) + '\n')
2243            print("RAW BMC data collected and saved into " + fileDir + os.sep+ "bmcFullRaw.txt")
2244            output['fileLoc'] = fileDir+os.sep+'bmcFullRaw.txt'
2245        except Exception as e:
2246            print("Failed to write RAW BMC data  to file system.")
2247            errorInfo += "Error writing RAW BMC data collection to the file. Exception: {eInfo}\n".format(eInfo=e)
2248            exc_type, exc_obj, exc_tb = sys.exc_info()
2249            fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2250            errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2251            errorInfo += traceback.format_exc()
2252
2253    output['errors'] = errorInfo
2254    return output
2255
2256def csdCollectAllDumps(host, args, session, fileDir):
2257    """
2258        Collects all of the bmc dump files and stores them in fileDir
2259
2260        @param host: string, the hostname or IP address of the bmc
2261        @param args: contains additional arguments used by the collectServiceData sub command
2262        @param session: the active session to use
2263        @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
2264        @param fileDir: string representation of the path to use for putting files created
2265    """
2266
2267    errorInfo = "===========BMC Dump Collection =============\n"
2268    dumpListCollected = False
2269    output={}
2270    dumpList = {}
2271    try:
2272        d = vars(args)
2273        d['json'] = True
2274        d['dumpSaveLoc'] = fileDir
2275    except Exception as e:
2276        errorInfo += "Failed to set the json flag to True, or failed to set the dumpSave Location \n Exception: {eInfo}\n".format(eInfo=e)
2277        exc_type, exc_obj, exc_tb = sys.exc_info()
2278        fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2279        errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2280        errorInfo += traceback.format_exc()
2281
2282    print('Collecting bmc dump files')
2283
2284    try:
2285        for i in range(3):
2286            dumpResp = bmcDumpList(host, args, session)
2287            if 'message' in dumpResp:
2288                if 'ok' in dumpResp['message'].lower():
2289                    dumpList = dumpResp['data']
2290                    dumpListCollected = True
2291                    break
2292                else:
2293                    errorInfo += "Status was not OK when retrieving the list of dumps available. \n Response: \n{resp}\n".format(resp=dumpResp)
2294            else:
2295                errorInfo += "Invalid response received from the BMC while retrieving the list of dumps available.\n {resp}\n".format(resp=dumpResp)
2296    except Exception as e:
2297        errorInfo += "BMC dump list exception: {eInfo}\n".format(eInfo=e)
2298        exc_type, exc_obj, exc_tb = sys.exc_info()
2299        fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2300        errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2301        errorInfo += traceback.format_exc()
2302
2303    if dumpListCollected:
2304        output['fileList'] = []
2305        for dump in dumpList:
2306            try:
2307                if '/xyz/openbmc_project/dump/internal/manager' not in dump:
2308                    d['dumpNum'] = int(dump.strip().split('/')[-1])
2309                    print('retrieving dump file ' + str(d['dumpNum']))
2310                    filename = bmcDumpRetrieve(host, args, session).split('Saved as ')[-1]
2311                    output['fileList'].append(filename)
2312            except Exception as e:
2313                print("Unable to collect dump: {dumpInfo}".format(dumpInfo=dump))
2314                errorInfo += "Exception collecting a bmc dump {dumpInfo}\n {eInfo}\n".format(dumpInfo=dump, eInfo=e)
2315                exc_type, exc_obj, exc_tb = sys.exc_info()
2316                fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2317                errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2318                errorInfo += traceback.format_exc()
2319    output['errors'] = errorInfo
2320    return output
2321
2322def collectServiceData(host, args, session):
2323    """
2324         Collects all data needed for service from the BMC
2325
2326         @param host: string, the hostname or IP address of the bmc
2327         @param args: contains additional arguments used by the collectServiceData sub command
2328         @param session: the active session to use
2329         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
2330    """
2331
2332    global toolVersion
2333    filelist = []
2334    errorInfo = ""
2335
2336    #get current number of bmc dumps and create a new bmc dump
2337    dumpInitdata = csdDumpInitiate(host, args, session)
2338    if 'dumpFailure' in dumpInitdata:
2339        return 'Collect service data is stopping due to not being able to create a new dump. No service data was collected.'
2340    dumpcount = dumpInitdata['dumpcount']
2341    errorInfo += dumpInitdata['errors']
2342    #create the directory to put files
2343    try:
2344        args.silent = True
2345        myDir = tempfile.gettempdir()+os.sep + host + "--" + datetime.datetime.now().strftime("%Y-%m-%d_%H.%M.%S")
2346        os.makedirs(myDir)
2347
2348    except Exception as e:
2349        print('Unable to create the temporary directory for data collection. Ensure sufficient privileges to create temporary directory. Aborting.')
2350        exc_type, exc_obj, exc_tb = sys.exc_info()
2351        fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
2352        errorInfo += "Exception: Error: {err}, Details: {etype}, {fname}, {lineno}\n".format(err=e, etype=exc_type, fname=fname, lineno=exc_tb.tb_lineno)
2353        errorInfo += traceback.format_exc()
2354        return("Python exception: {eInfo}".format(eInfo = e))
2355
2356    #Collect Inventory
2357    inventoryData = csdInventory(host, args, session, myDir)
2358    if 'fileLoc' in inventoryData:
2359        filelist.append(inventoryData['fileLoc'])
2360    errorInfo += inventoryData['errors']
2361    #Read all the sensor and OCC status
2362    sensorData = csdSensors(host,args,session,myDir)
2363    if 'fileLoc' in sensorData:
2364        filelist.append(sensorData['fileLoc'])
2365    errorInfo += sensorData['errors']
2366    #Collect all of the LEDs status
2367    ledStatus = csdLEDs(host, args, session, myDir)
2368    if 'fileLoc' in ledStatus:
2369        filelist.append(ledStatus['fileLoc'])
2370    errorInfo += ledStatus['errors']
2371
2372    #Collect the bmc logs
2373    selShort = csdSelShortList(host, args, session, myDir)
2374    if 'fileLoc' in selShort:
2375        filelist.append(selShort['fileLoc'])
2376    errorInfo += selShort['errors']
2377
2378    parsedSELs = csdParsedSels(host, args, session, myDir)
2379    if 'fileLoc' in parsedSELs:
2380        filelist.append(parsedSELs['fileLoc'])
2381    errorInfo += parsedSELs['errors']
2382
2383    #collect RAW bmc enumeration
2384    bmcRaw = csdFullEnumeration(host, args, session, myDir)
2385    if 'fileLoc' in bmcRaw:
2386        filelist.append(bmcRaw['fileLoc'])
2387    errorInfo += bmcRaw['errors']
2388
2389    #wait for new dump to finish being created
2390    waitingForNewDump = True
2391    count = 0;
2392    print("Waiting for new BMC dump to finish being created. Wait time could be up to 5 minutes")
2393    while(waitingForNewDump):
2394        dumpList = bmcDumpList(host, args, session)['data']
2395        if len(dumpList) > dumpcount:
2396            waitingForNewDump = False
2397            break;
2398        elif(count>150):
2399            print("Timed out waiting for bmc to make a new dump file. Continuing without it.")
2400            break;
2401        else:
2402            time.sleep(2)
2403        count += 1
2404
2405    #collect all of the dump files
2406    getBMCDumps = csdCollectAllDumps(host, args, session, myDir)
2407    if 'fileList' in getBMCDumps:
2408        filelist+= getBMCDumps['fileList']
2409    errorInfo += getBMCDumps['errors']
2410
2411    #write the runtime errors to a file
2412    try:
2413        with open(myDir +os.sep+'openbmctoolRuntimeErrors.txt', 'w') as f:
2414            f.write(errorInfo)
2415        print("OpenBMC tool runtime errors collected and stored in " + myDir + os.sep+ "openbmctoolRuntimeErrors.txt")
2416        filelist.append(myDir+os.sep+'openbmctoolRuntimeErrors.txt')
2417    except Exception as e:
2418        print("Failed to write OpenBMC tool runtime errors to file system.")
2419
2420    #create the zip file
2421    try:
2422        filename = myDir.split(tempfile.gettempdir()+os.sep)[-1] + "_" + toolVersion + '_openbmc.zip'
2423        zf = zipfile.ZipFile(myDir+os.sep + filename, 'w')
2424        for myfile in filelist:
2425            zf.write(myfile, os.path.basename(myfile))
2426        zf.close()
2427        print("Zip file with all collected data created and stored in: {fileInfo}".format(fileInfo=myDir+os.sep+filename))
2428    except Exception as e:
2429        print("Failed to create zip file with collected information")
2430    return "data collection finished"
2431
2432
2433def healthCheck(host, args, session):
2434    """
2435         runs a health check on the platform
2436
2437         @param host: string, the hostname or IP address of the bmc
2438         @param args: contains additional arguments used by the bmc sub command
2439         @param session: the active session to use
2440         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
2441    """
2442    #check fru status and get as json to easily work through
2443    d = vars(args)
2444    useJson = d['json']
2445    d['json'] = True
2446    d['verbose']= False
2447
2448    frus = json.loads(fruStatus(host, args, session))
2449
2450    hwStatus= "OK"
2451    performanceStatus = "OK"
2452    for key in frus:
2453        if frus[key]["Functional"] == "No" and frus[key]["Present"] == "Yes":
2454            hwStatus= "Degraded"
2455            if("power_supply" in key or "powersupply" in key):
2456                gpuCount =0
2457                for comp in frus:
2458                    if "gv100card" in comp:
2459                        gpuCount +=1
2460                if gpuCount > 4:
2461                    hwStatus = "Critical"
2462                    performanceStatus="Degraded"
2463                    break;
2464            elif("fan" in key):
2465                hwStatus = "Degraded"
2466            else:
2467                performanceStatus = "Degraded"
2468    if useJson:
2469        output = {"Hardware Status": hwStatus, "Performance": performanceStatus}
2470        output = json.dumps(output, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False)
2471    else:
2472        output = ("Hardware Status: " + hwStatus +
2473                  "\nPerformance: " +performanceStatus )
2474
2475
2476    #SW407886: Clear the duplicate entries
2477    #collect the dups
2478    d['devdebug'] = False
2479    sels = json.loads(selPrint(host, args, session))
2480    logNums2Clr = []
2481    oldestLogNum={"logNum": "bogus" ,"key" : ""}
2482    count = 0
2483    if sels['numAlerts'] > 0:
2484        for key in sels:
2485            if "numAlerts" in key:
2486                continue
2487            try:
2488                if "slave@00:00/00:00:00:06/sbefifo1-dev0/occ1-dev0" in sels[key]['Message']:
2489                    count += 1
2490                    if count > 1:
2491                        #preserve first occurrence
2492                        if sels[key]['timestamp'] < sels[oldestLogNum['key']]['timestamp']:
2493                            oldestLogNum['key']=key
2494                            oldestLogNum['logNum'] = sels[key]['logNum']
2495                    else:
2496                        oldestLogNum['key']=key
2497                        oldestLogNum['logNum'] = sels[key]['logNum']
2498                    logNums2Clr.append(sels[key]['logNum'])
2499            except KeyError:
2500                continue
2501        if(count >0):
2502            logNums2Clr.remove(oldestLogNum['logNum'])
2503        #delete the dups
2504        if count >1:
2505            data = "{\"data\": [] }"
2506            for logNum in logNums2Clr:
2507                    url = "https://"+ host+ "/xyz/openbmc_project/logging/entry/"+logNum+"/action/Delete"
2508                    try:
2509                        session.post(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
2510                    except(requests.exceptions.Timeout):
2511                        deleteFailed = True
2512                    except(requests.exceptions.ConnectionError) as err:
2513                        deleteFailed = True
2514    #End of defect resolve code
2515    d['json'] = useJson
2516    return output
2517
2518
2519
2520def bmc(host, args, session):
2521    """
2522         handles various bmc level commands, currently bmc rebooting
2523
2524         @param host: string, the hostname or IP address of the bmc
2525         @param args: contains additional arguments used by the bmc sub command
2526         @param session: the active session to use
2527         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
2528    """
2529    if(args.type is not None):
2530        return bmcReset(host, args, session)
2531    if(args.info):
2532        return "Not implemented at this time"
2533
2534
2535
2536def bmcReset(host, args, session):
2537    """
2538         controls resetting the bmc. warm reset reboots the bmc, cold reset removes the configuration and reboots.
2539
2540         @param host: string, the hostname or IP address of the bmc
2541         @param args: contains additional arguments used by the bmcReset sub command
2542         @param session: the active session to use
2543         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
2544    """
2545    if checkFWactivation(host, args, session):
2546        return ("BMC reset control disabled during firmware activation")
2547    if(args.type == "warm"):
2548        print("\nAttempting to reboot the BMC...:")
2549        url="https://"+host+"/xyz/openbmc_project/state/bmc0/attr/RequestedBMCTransition"
2550        data = '{"data":"xyz.openbmc_project.State.BMC.Transition.Reboot"}'
2551        res = session.put(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
2552        return res.text
2553    elif(args.type =="cold"):
2554        print("\nAttempting to reboot the BMC...:")
2555        url="https://"+host+"/xyz/openbmc_project/state/bmc0/attr/RequestedBMCTransition"
2556        data = '{"data":"xyz.openbmc_project.State.BMC.Transition.Reboot"}'
2557        res = session.put(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
2558        return res.text
2559    else:
2560        return "invalid command"
2561
2562def gardClear(host, args, session):
2563    """
2564         clears the gard records from the bmc
2565
2566         @param host: string, the hostname or IP address of the bmc
2567         @param args: contains additional arguments used by the gardClear sub command
2568         @param session: the active session to use
2569    """
2570    url="https://"+host+"/org/open_power/control/gard/action/Reset"
2571    data = '{"data":[]}'
2572    try:
2573
2574        res = session.post(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
2575        if res.status_code == 404:
2576            return "Command not supported by this firmware version"
2577        else:
2578            return res.text
2579    except(requests.exceptions.Timeout):
2580        return connectionErrHandler(args.json, "Timeout", None)
2581    except(requests.exceptions.ConnectionError) as err:
2582        return connectionErrHandler(args.json, "ConnectionError", err)
2583
2584def activateFWImage(host, args, session):
2585    """
2586         activates a firmware image on the bmc
2587
2588         @param host: string, the hostname or IP address of the bmc
2589         @param args: contains additional arguments used by the fwflash sub command
2590         @param session: the active session to use
2591         @param fwID: the unique ID of the fw image to activate
2592    """
2593    fwID = args.imageID
2594
2595    #determine the existing versions
2596    url="https://"+host+"/xyz/openbmc_project/software/enumerate"
2597    try:
2598        resp = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
2599    except(requests.exceptions.Timeout):
2600        return connectionErrHandler(args.json, "Timeout", None)
2601    except(requests.exceptions.ConnectionError) as err:
2602        return connectionErrHandler(args.json, "ConnectionError", err)
2603    existingSoftware = json.loads(resp.text)['data']
2604    altVersionID = ''
2605    versionType = ''
2606    imageKey = '/xyz/openbmc_project/software/'+fwID
2607    if imageKey in existingSoftware:
2608        versionType = existingSoftware[imageKey]['Purpose']
2609    for key in existingSoftware:
2610        if imageKey == key:
2611            continue
2612        if 'Purpose' in existingSoftware[key]:
2613            if versionType == existingSoftware[key]['Purpose']:
2614                altVersionID = key.split('/')[-1]
2615
2616
2617
2618
2619    url="https://"+host+"/xyz/openbmc_project/software/"+ fwID + "/attr/Priority"
2620    url1="https://"+host+"/xyz/openbmc_project/software/"+ altVersionID + "/attr/Priority"
2621    data = "{\"data\": 0}"
2622    data1 = "{\"data\": 1 }"
2623    try:
2624        resp = session.put(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
2625        resp1 = session.put(url1, headers=jsonHeader, data=data1, verify=False, timeout=baseTimeout)
2626    except(requests.exceptions.Timeout):
2627        return connectionErrHandler(args.json, "Timeout", None)
2628    except(requests.exceptions.ConnectionError) as err:
2629        return connectionErrHandler(args.json, "ConnectionError", err)
2630    if(not args.json):
2631        if resp.status_code == 200 and resp1.status_code == 200:
2632            return 'Firmware flash and activation completed. Please reboot the bmc and then boot the host OS for the changes to take effect. '
2633        else:
2634            return "Firmware activation failed."
2635    else:
2636        return resp.text + resp1.text
2637
2638def activateStatus(host, args, session):
2639    if checkFWactivation(host, args, session):
2640        return("Firmware is currently being activated. Do not reboot the BMC or start the Host OS")
2641    else:
2642        return("No firmware activations are pending")
2643
2644def extractFWimage(path, imageType):
2645    """
2646         extracts the bmc image and returns information about the package
2647
2648         @param path: the path and file name of the firmware image
2649         @param imageType: The type of image the user is trying to flash. Host or BMC
2650         @return: the image id associated with the package. returns an empty string on error.
2651    """
2652    f = tempfile.TemporaryFile()
2653    tmpDir = tempfile.gettempdir()
2654    newImageID = ""
2655    if os.path.exists(path):
2656        try:
2657            imageFile = tarfile.open(path,'r')
2658            contents = imageFile.getmembers()
2659            for tf in contents:
2660                if 'MANIFEST' in tf.name:
2661                    imageFile.extract(tf.name, path=tmpDir)
2662                    with open(tempfile.gettempdir() +os.sep+ tf.name, 'r') as imageInfo:
2663                        for line in imageInfo:
2664                            if 'purpose' in line:
2665                                purpose = line.split('=')[1]
2666                                if imageType not in purpose.split('.')[-1]:
2667                                    print('The specified image is not for ' + imageType)
2668                                    print('Please try again with the image for ' + imageType)
2669                                    return ""
2670                            if 'version' == line.split('=')[0]:
2671                                version = line.split('=')[1].strip().encode('utf-8')
2672                                m = hashlib.sha512()
2673                                m.update(version)
2674                                newImageID = m.hexdigest()[:8]
2675                                break
2676                    try:
2677                        os.remove(tempfile.gettempdir() +os.sep+ tf.name)
2678                    except OSError:
2679                        pass
2680                    return newImageID
2681        except tarfile.ExtractError as e:
2682            print('Unable to extract information from the firmware file.')
2683            print('Ensure you have write access to the directory: ' + tmpDir)
2684            return newImageID
2685        except tarfile.TarError as e:
2686            print('This is not a valid firmware file.')
2687            return newImageID
2688        print("This is not a valid firmware file.")
2689        return newImageID
2690    else:
2691        print('The filename and path provided are not valid.')
2692        return newImageID
2693
2694def getAllFWImageIDs(fwInvDict):
2695    """
2696         gets a list of all the firmware image IDs
2697
2698         @param fwInvDict: the dictionary to search for FW image IDs
2699         @return: list containing string representation of the found image ids
2700    """
2701    idList = []
2702    for key in fwInvDict:
2703        if 'Version' in fwInvDict[key]:
2704            idList.append(key.split('/')[-1])
2705    return idList
2706
2707def fwFlash(host, args, session):
2708    """
2709         updates the bmc firmware and pnor firmware
2710
2711         @param host: string, the hostname or IP address of the bmc
2712         @param args: contains additional arguments used by the fwflash sub command
2713         @param session: the active session to use
2714    """
2715    d = vars(args)
2716    if(args.type == 'bmc'):
2717        purp = 'BMC'
2718    else:
2719        purp = 'Host'
2720
2721    #check power state of the machine. No concurrent FW updates allowed
2722    d['powcmd'] = 'status'
2723    powerstate = chassisPower(host, args, session)
2724    if 'Chassis Power State: On' in powerstate:
2725        return("Aborting firmware update. Host is powered on. Please turn off the host and try again.")
2726
2727    #determine the existing images on the bmc
2728    url="https://"+host+"/xyz/openbmc_project/software/enumerate"
2729    try:
2730        resp = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
2731    except(requests.exceptions.Timeout):
2732        return connectionErrHandler(args.json, "Timeout", None)
2733    except(requests.exceptions.ConnectionError) as err:
2734        return connectionErrHandler(args.json, "ConnectionError", err)
2735    oldsoftware = json.loads(resp.text)['data']
2736
2737    #Extract the tar and get information from the manifest file
2738    newversionID = extractFWimage(args.fileloc, purp)
2739    if  newversionID == "":
2740        return "Unable to verify FW image."
2741
2742
2743    #check if the new image is already on the bmc
2744    if newversionID not in getAllFWImageIDs(oldsoftware):
2745
2746        #upload the file
2747        httpHeader = {'Content-Type':'application/octet-stream'}
2748        httpHeader.update(xAuthHeader)
2749        url="https://"+host+"/upload/image"
2750        data=open(args.fileloc,'rb').read()
2751        print("Uploading file to BMC")
2752        try:
2753            resp = session.post(url, headers=httpHeader, data=data, verify=False)
2754        except(requests.exceptions.Timeout):
2755            return connectionErrHandler(args.json, "Timeout", None)
2756        except(requests.exceptions.ConnectionError) as err:
2757            return connectionErrHandler(args.json, "ConnectionError", err)
2758        if resp.status_code != 200:
2759            return "Failed to upload the file to the bmc"
2760        else:
2761            print("Upload complete.")
2762
2763        #verify bmc processed the image
2764        software ={}
2765        for i in range(0, 5):
2766            url="https://"+host+"/xyz/openbmc_project/software/enumerate"
2767            try:
2768                resp = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
2769            except(requests.exceptions.Timeout):
2770                return connectionErrHandler(args.json, "Timeout", None)
2771            except(requests.exceptions.ConnectionError) as err:
2772                return connectionErrHandler(args.json, "ConnectionError", err)
2773            software = json.loads(resp.text)['data']
2774            #check if bmc is done processing the new image
2775            if (newversionID in getAllFWImageIDs(software)):
2776                break
2777            else:
2778                time.sleep(15)
2779
2780        #activate the new image
2781        print("Activating new image: "+newversionID)
2782        url="https://"+host+"/xyz/openbmc_project/software/"+ newversionID + "/attr/RequestedActivation"
2783        data = '{"data":"xyz.openbmc_project.Software.Activation.RequestedActivations.Active"}'
2784        try:
2785            resp = session.put(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
2786        except(requests.exceptions.Timeout):
2787            return connectionErrHandler(args.json, "Timeout", None)
2788        except(requests.exceptions.ConnectionError) as err:
2789            return connectionErrHandler(args.json, "ConnectionError", err)
2790
2791        #wait for the activation to complete, timeout after ~1 hour
2792        i=0
2793        while i < 360:
2794            url="https://"+host+"/xyz/openbmc_project/software/"+ newversionID
2795            data = '{"data":"xyz.openbmc_project.Software.Activation.RequestedActivations.Active"}'
2796            try:
2797                resp = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
2798            except(requests.exceptions.Timeout):
2799                return connectionErrHandler(args.json, "Timeout", None)
2800            except(requests.exceptions.ConnectionError) as err:
2801                return connectionErrHandler(args.json, "ConnectionError", err)
2802            fwInfo = json.loads(resp.text)['data']
2803            if 'Activating' not in fwInfo['Activation'] and 'Activating' not in fwInfo['RequestedActivation']:
2804                print('')
2805                break
2806            else:
2807                sys.stdout.write('.')
2808                sys.stdout.flush()
2809                time.sleep(10) #check every 10 seconds
2810        return "Firmware flash and activation completed. Please reboot the bmc and then boot the host OS for the changes to take effect. "
2811    else:
2812        print("This image has been found on the bmc. Activating image: " + newversionID)
2813
2814        d['imageID'] = newversionID
2815        return activateFWImage(host, args, session)
2816
2817def getFWInventoryAttributes(rawFWInvItem, ID):
2818    """
2819         gets and lists all of the firmware in the system.
2820
2821         @return: returns a dictionary containing the image attributes
2822    """
2823    reqActivation = rawFWInvItem["RequestedActivation"].split('.')[-1]
2824    pendingActivation = ""
2825    if reqActivation == "None":
2826        pendingActivation = "No"
2827    else:
2828        pendingActivation = "Yes"
2829    firmwareAttr = {ID: {
2830        "Purpose": rawFWInvItem["Purpose"].split('.')[-1],
2831        "Version": rawFWInvItem["Version"],
2832        "RequestedActivation": pendingActivation,
2833        "ID": ID}}
2834
2835    if "ExtendedVersion" in rawFWInvItem:
2836        firmwareAttr[ID]['ExtendedVersion'] = rawFWInvItem['ExtendedVersion'].split(',')
2837    else:
2838        firmwareAttr[ID]['ExtendedVersion'] = ""
2839    return firmwareAttr
2840
2841def parseFWdata(firmwareDict):
2842    """
2843         creates a dictionary with parsed firmware data
2844
2845         @return: returns a dictionary containing the image attributes
2846    """
2847    firmwareInfoDict = {"Functional": {}, "Activated":{}, "NeedsActivated":{}}
2848    for key in firmwareDict['data']:
2849        #check for valid endpoint
2850        if "Purpose" in firmwareDict['data'][key]:
2851            id = key.split('/')[-1]
2852            if firmwareDict['data'][key]['Activation'].split('.')[-1] == "Active":
2853                fwActivated = True
2854            else:
2855                fwActivated = False
2856            if 'Priority' in firmwareDict['data'][key]:
2857                if firmwareDict['data'][key]['Priority'] == 0:
2858                    firmwareInfoDict['Functional'].update(getFWInventoryAttributes(firmwareDict['data'][key], id))
2859                elif firmwareDict['data'][key]['Priority'] >= 0 and fwActivated:
2860                    firmwareInfoDict['Activated'].update(getFWInventoryAttributes(firmwareDict['data'][key], id))
2861                else:
2862                    firmwareInfoDict['NeedsActivated'].update(getFWInventoryAttributes(firmwareDict['data'][key], id))
2863            else:
2864                firmwareInfoDict['NeedsActivated'].update(getFWInventoryAttributes(firmwareDict['data'][key], id))
2865    emptySections = []
2866    for key in firmwareInfoDict:
2867        if len(firmwareInfoDict[key])<=0:
2868            emptySections.append(key)
2869    for key in emptySections:
2870        del firmwareInfoDict[key]
2871    return firmwareInfoDict
2872
2873def displayFWInvenory(firmwareInfoDict, args):
2874    """
2875         gets and lists all of the firmware in the system.
2876
2877         @return: returns a string containing all of the firmware information
2878    """
2879    output = ""
2880    if not args.json:
2881        for key in firmwareInfoDict:
2882            for subkey in firmwareInfoDict[key]:
2883                firmwareInfoDict[key][subkey]['ExtendedVersion'] = str(firmwareInfoDict[key][subkey]['ExtendedVersion'])
2884        if not args.verbose:
2885            output = "---Running Images---\n"
2886            colNames = ["Purpose", "Version", "ID"]
2887            keylist = ["Purpose", "Version", "ID"]
2888            output += tableDisplay(keylist, colNames, firmwareInfoDict["Functional"])
2889            if "Activated" in firmwareInfoDict:
2890                output += "\n---Available Images---\n"
2891                output += tableDisplay(keylist, colNames, firmwareInfoDict["Activated"])
2892            if "NeedsActivated" in firmwareInfoDict:
2893                output += "\n---Needs Activated Images---\n"
2894                output += tableDisplay(keylist, colNames, firmwareInfoDict["NeedsActivated"])
2895
2896        else:
2897            output = "---Running Images---\n"
2898            colNames = ["Purpose", "Version", "ID", "Pending Activation", "Extended Version"]
2899            keylist = ["Purpose", "Version", "ID", "RequestedActivation", "ExtendedVersion"]
2900            output += tableDisplay(keylist, colNames, firmwareInfoDict["Functional"])
2901            if "Activated" in firmwareInfoDict:
2902                output += "\n---Available Images---\n"
2903                output += tableDisplay(keylist, colNames, firmwareInfoDict["Activated"])
2904            if "NeedsActivated" in firmwareInfoDict:
2905                output += "\n---Needs Activated Images---\n"
2906                output += tableDisplay(keylist, colNames, firmwareInfoDict["NeedsActivated"])
2907        return output
2908    else:
2909        return str(json.dumps(firmwareInfoDict, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False))
2910
2911def firmwareList(host, args, session):
2912    """
2913         gets and lists all of the firmware in the system.
2914
2915         @return: returns a string containing all of the firmware information
2916    """
2917    url="https://{hostname}/xyz/openbmc_project/software/enumerate".format(hostname=host)
2918    try:
2919        res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
2920    except(requests.exceptions.Timeout):
2921        return(connectionErrHandler(args.json, "Timeout", None))
2922    firmwareDict = json.loads(res.text)
2923
2924    #sort the received information
2925    firmwareInfoDict = parseFWdata(firmwareDict)
2926
2927    #display the information
2928    return displayFWInvenory(firmwareInfoDict, args)
2929
2930
2931def deleteFWVersion(host, args, session):
2932    """
2933         deletes a firmware version on the BMC
2934
2935         @param host: string, the hostname or IP address of the BMC
2936         @param args: contains additional arguments used by the fwflash sub command
2937         @param session: the active session to use
2938         @param fwID: the unique ID of the fw version to delete
2939    """
2940    fwID = args.versionID
2941
2942    print("Deleting version: "+fwID)
2943    url="https://"+host+"/xyz/openbmc_project/software/"+ fwID + "/action/Delete"
2944    data = "{\"data\": [] }"
2945
2946    try:
2947        res = session.post(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
2948    except(requests.exceptions.Timeout):
2949        return(connectionErrHandler(args.json, "Timeout", None))
2950    if res.status_code == 200:
2951        return ('The firmware version has been deleted')
2952    else:
2953        return ('Unable to delete the specified firmware version')
2954
2955
2956def restLogging(host, args, session):
2957    """
2958         Called by the logging function. Turns REST API logging on/off.
2959
2960         @param host: string, the hostname or IP address of the bmc
2961         @param args: contains additional arguments used by the logging sub command
2962         @param session: the active session to use
2963         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
2964    """
2965    url="https://"+host+"/xyz/openbmc_project/logging/rest_api_logs/attr/Enabled"
2966
2967    if(args.rest_logging == 'on'):
2968        data = '{"data": 1}'
2969    elif(args.rest_logging == 'off'):
2970        data = '{"data": 0}'
2971    else:
2972        return "Invalid logging rest_api command"
2973
2974    try:
2975        res = session.put(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
2976    except(requests.exceptions.Timeout):
2977        return(connectionErrHandler(args.json, "Timeout", None))
2978    return res.text
2979
2980
2981def remoteLogging(host, args, session):
2982    """
2983         Called by the logging function. View config information for/disable remote logging (rsyslog).
2984
2985         @param host: string, the hostname or IP address of the bmc
2986         @param args: contains additional arguments used by the logging sub command
2987         @param session: the active session to use
2988         @param args.json: boolean, if this flag is set to true, the output will be provided in json format for programmatic consumption
2989    """
2990
2991    url="https://"+host+"/xyz/openbmc_project/logging/config/remote"
2992
2993    try:
2994        if(args.remote_logging == 'view'):
2995            res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
2996        elif(args.remote_logging == 'disable'):
2997            res = session.put(url + '/attr/Port', headers=jsonHeader, json = {"data": 0}, verify=False, timeout=baseTimeout)
2998            res = session.put(url + '/attr/Address', headers=jsonHeader, json = {"data": ""}, verify=False, timeout=baseTimeout)
2999        else:
3000            return "Invalid logging remote_logging command"
3001    except(requests.exceptions.Timeout):
3002        return(connectionErrHandler(args.json, "Timeout", None))
3003    return res.text
3004
3005
3006def remoteLoggingConfig(host, args, session):
3007    """
3008         Called by the logging function. Configures remote logging (rsyslog).
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
3016    url="https://"+host+"/xyz/openbmc_project/logging/config/remote"
3017
3018    try:
3019        res = session.put(url + '/attr/Port', headers=jsonHeader, json = {"data": args.port}, verify=False, timeout=baseTimeout)
3020        res = session.put(url + '/attr/Address', headers=jsonHeader, json = {"data": args.address}, verify=False, timeout=baseTimeout)
3021    except(requests.exceptions.Timeout):
3022        return(connectionErrHandler(args.json, "Timeout", None))
3023    return res.text
3024
3025def redfishSupportPresent(host, session):
3026    url = "https://" + host + "/redfish/v1"
3027    try:
3028        resp = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
3029    except(requests.exceptions.Timeout):
3030        return False
3031    except(requests.exceptions.ConnectionError) as err:
3032        return False
3033    if resp.status_code != 200:
3034        return False
3035    else:
3036       return True
3037
3038def certificateUpdate(host, args, session):
3039    """
3040         Called by certificate management function. update server/client/authority certificates
3041         Example:
3042         certificate update server https -f cert.pem
3043         certificate update authority ldap -f Root-CA.pem
3044         certificate update client ldap -f cert.pem
3045         @param host: string, the hostname or IP address of the bmc
3046         @param args: contains additional arguments used by the certificate update sub command
3047         @param session: the active session to use
3048    """
3049    httpHeader = {'Content-Type': 'application/octet-stream'}
3050    httpHeader.update(xAuthHeader)
3051    data = open(args.fileloc, 'r').read()
3052    try:
3053        if redfishSupportPresent(host, session):
3054            if(args.type.lower() == 'server' and args.service.lower() != "https"):
3055                return "Invalid service type"
3056            if(args.type.lower() == 'client' and args.service.lower() != "ldap"):
3057                return "Invalid service type"
3058            if(args.type.lower() == 'authority' and args.service.lower() != "ldap"):
3059                return "Invalid service type"
3060            url = "";
3061            if(args.type.lower() == 'server'):
3062                url = "https://" + host + \
3063                    "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"
3064            elif(args.type.lower() == 'client'):
3065                url = "https://" + host + \
3066                    "/redfish/v1/AccountService/LDAP/Certificates"
3067            elif(args.type.lower() == 'authority'):
3068                url = "https://" + host + \
3069                "/redfish/v1/Managers/bmc/Truststore/Certificates"
3070            else:
3071                return "Unsupported certificate type"
3072            resp = session.post(url, headers=httpHeader, data=data,
3073                        verify=False)
3074        else:
3075            url = "https://" + host + "/xyz/openbmc_project/certs/" + \
3076                args.type.lower() + "/" + args.service.lower()
3077            resp = session.put(url, headers=httpHeader, data=data, verify=False)
3078    except(requests.exceptions.Timeout):
3079        return(connectionErrHandler(args.json, "Timeout", None))
3080    except(requests.exceptions.ConnectionError) as err:
3081        return connectionErrHandler(args.json, "ConnectionError", err)
3082    if resp.status_code != 200:
3083        print(resp.text)
3084        return "Failed to update the certificate"
3085    else:
3086        print("Update complete.")
3087
3088def certificateDelete(host, args, session):
3089    """
3090         Called by certificate management function to delete certificate
3091         Example:
3092         certificate delete server https
3093         certificate delete authority ldap
3094         certificate delete client ldap
3095         @param host: string, the hostname or IP address of the bmc
3096         @param args: contains additional arguments used by the certificate delete sub command
3097         @param session: the active session to use
3098    """
3099    if redfishSupportPresent(host, session):
3100        return "Not supported, please use certificate replace instead";
3101    httpHeader = {'Content-Type': 'multipart/form-data'}
3102    httpHeader.update(xAuthHeader)
3103    url = "https://" + host + "/xyz/openbmc_project/certs/" + args.type.lower() + "/" + args.service.lower()
3104    print("Deleting certificate url=" + url)
3105    try:
3106        resp = session.delete(url, headers=httpHeader)
3107    except(requests.exceptions.Timeout):
3108        return(connectionErrHandler(args.json, "Timeout", None))
3109    except(requests.exceptions.ConnectionError) as err:
3110        return connectionErrHandler(args.json, "ConnectionError", err)
3111    if resp.status_code != 200:
3112        print(resp.text)
3113        return "Failed to delete the certificate"
3114    else:
3115        print("Delete complete.")
3116
3117def certificateReplace(host, args, session):
3118    """
3119         Called by certificate management function. replace server/client/
3120         authority certificates
3121         Example:
3122         certificate replace server https -f cert.pem
3123         certificate replace authority ldap -f Root-CA.pem
3124         certificate replace client ldap -f cert.pem
3125         @param host: string, the hostname or IP address of the bmc
3126         @param args: contains additional arguments used by the certificate
3127                      replace sub command
3128         @param session: the active session to use
3129    """
3130    cert = open(args.fileloc, 'r').read()
3131    try:
3132        if redfishSupportPresent(host, session):
3133            httpHeader = {'Content-Type': 'application/json'}
3134            httpHeader.update(xAuthHeader)
3135            url = "";
3136            if(args.type.lower() == 'server' and args.service.lower() != "https"):
3137                return "Invalid service type"
3138            if(args.type.lower() == 'client' and args.service.lower() != "ldap"):
3139                return "Invalid service type"
3140            if(args.type.lower() == 'authority' and args.service.lower() != "ldap"):
3141                return "Invalid service type"
3142            if(args.type.lower() == 'server'):
3143                url = "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/1"
3144            elif(args.type.lower() == 'client'):
3145                url = "/redfish/v1/AccountService/LDAP/Certificates/1"
3146            elif(args.type.lower() == 'authority'):
3147                url = "/redfish/v1/Managers/bmc/Truststore/Certificates/1"
3148            replaceUrl = "https://" + host + \
3149                "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate"
3150            data ={"CertificateUri":{"@odata.id":url}, "CertificateType":"PEM",
3151                    "CertificateString":cert}
3152            resp = session.post(replaceUrl, headers=httpHeader, json=data, verify=False)
3153        else:
3154            httpHeader = {'Content-Type': 'application/octet-stream'}
3155            httpHeader.update(xAuthHeader)
3156            url = "https://" + host + "/xyz/openbmc_project/certs/" + \
3157                args.type.lower() + "/" + args.service.lower()
3158            resp = session.delete(url, headers=httpHeader)
3159            resp = session.put(url, headers=httpHeader, data=cert, verify=False)
3160    except(requests.exceptions.Timeout):
3161        return(connectionErrHandler(args.json, "Timeout", None))
3162    except(requests.exceptions.ConnectionError) as err:
3163        return connectionErrHandler(args.json, "ConnectionError", err)
3164    if resp.status_code != 200:
3165        print(resp.text)
3166        return "Failed to replace the certificate"
3167    else:
3168        print("Replace complete.")
3169    return resp.text
3170
3171def certificateDisplay(host, args, session):
3172    """
3173         Called by certificate management function. display server/client/
3174         authority certificates
3175         Example:
3176         certificate display server
3177         certificate display authority
3178         certificate display client
3179         @param host: string, the hostname or IP address of the bmc
3180         @param args: contains additional arguments used by the certificate
3181                      display sub command
3182         @param session: the active session to use
3183    """
3184    if not redfishSupportPresent(host, session):
3185        return "Not supported";
3186
3187    httpHeader = {'Content-Type': 'application/octet-stream'}
3188    httpHeader.update(xAuthHeader)
3189    if(args.type.lower() == 'server'):
3190        url = "https://" + host + \
3191            "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/1"
3192    elif(args.type.lower() == 'client'):
3193        url = "https://" + host + \
3194            "/redfish/v1/AccountService/LDAP/Certificates/1"
3195    elif(args.type.lower() == 'authority'):
3196        url = "https://" + host + \
3197            "/redfish/v1/Managers/bmc/Truststore/Certificates/1"
3198    try:
3199        resp = session.get(url, headers=httpHeader, verify=False)
3200    except(requests.exceptions.Timeout):
3201        return(connectionErrHandler(args.json, "Timeout", None))
3202    except(requests.exceptions.ConnectionError) as err:
3203        return connectionErrHandler(args.json, "ConnectionError", err)
3204    if resp.status_code != 200:
3205        print(resp.text)
3206        return "Failed to display the certificate"
3207    else:
3208        print("Display complete.")
3209    return resp.text
3210
3211def certificateList(host, args, session):
3212    """
3213         Called by certificate management function.
3214         Example:
3215         certificate list
3216         @param host: string, the hostname or IP address of the bmc
3217         @param args: contains additional arguments used by the certificate
3218                      list sub command
3219         @param session: the active session to use
3220    """
3221    if not redfishSupportPresent(host, session):
3222        return "Not supported";
3223
3224    httpHeader = {'Content-Type': 'application/octet-stream'}
3225    httpHeader.update(xAuthHeader)
3226    url = "https://" + host + \
3227        "/redfish/v1/CertificateService/CertificateLocations/"
3228    try:
3229        resp = session.get(url, headers=httpHeader, verify=False)
3230    except(requests.exceptions.Timeout):
3231        return(connectionErrHandler(args.json, "Timeout", None))
3232    except(requests.exceptions.ConnectionError) as err:
3233        return connectionErrHandler(args.json, "ConnectionError", err)
3234    if resp.status_code != 200:
3235        print(resp.text)
3236        return "Failed to list certificates"
3237    else:
3238        print("List certificates complete.")
3239    return resp.text
3240
3241def certificateGenerateCSR(host, args, session):
3242    """
3243        Called by certificate management function. Generate CSR for server/
3244        client certificates
3245        Example:
3246        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
3247        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
3248        @param host: string, the hostname or IP address of the bmc
3249        @param args: contains additional arguments used by the certificate replace sub command
3250        @param session: the active session to use
3251    """
3252    if not redfishSupportPresent(host, session):
3253        return "Not supported";
3254
3255    httpHeader = {'Content-Type': 'application/octet-stream'}
3256    httpHeader.update(xAuthHeader)
3257    url = "";
3258    if(args.type.lower() == 'server'):
3259        url = "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/"
3260        usage_list = ["ServerAuthentication"]
3261    elif(args.type.lower() == 'client'):
3262        url = "/redfish/v1/AccountService/LDAP/Certificates/"
3263        usage_list = ["ClientAuthentication"]
3264    elif(args.type.lower() == 'authority'):
3265        url = "/redfish/v1/Managers/bmc/Truststore/Certificates/"
3266    print("Generating CSR url=" + url)
3267    generateCSRUrl = "https://" + host + \
3268        "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR"
3269    try:
3270        alt_name_list = args.alternativeNames.split(",")
3271        data ={"CertificateCollection":{"@odata.id":url},
3272            "CommonName":args.commonName, "City":args.city,
3273            "Country":args.country, "Organization":args.organization,
3274            "OrganizationalUnit":args.organizationUnit, "State":args.state,
3275            "KeyPairAlgorithm":args.keyPairAlgorithm, "KeyCurveId":args.keyCurveId,
3276            "AlternativeNames":alt_name_list, "ContactPerson":args.contactPerson,
3277            "Email":args.email, "GivenName":args.givenname, "Initials":args.initials,
3278            "KeyUsage":usage_list, "Surname":args.surname,
3279            "UnstructuredName":args.unstructuredname}
3280        resp = session.post(generateCSRUrl, headers=httpHeader,
3281            json=data, verify=False)
3282    except(requests.exceptions.Timeout):
3283        return(connectionErrHandler(args.json, "Timeout", None))
3284    except(requests.exceptions.ConnectionError) as err:
3285        return connectionErrHandler(args.json, "ConnectionError", err)
3286    if resp.status_code != 200:
3287        print(resp.text)
3288        return "Failed to generate CSR"
3289    else:
3290        print("GenerateCSR complete.")
3291    return resp.text
3292
3293def enableLDAPConfig(host, args, session):
3294    """
3295         Called by the ldap function. Configures LDAP.
3296
3297         @param host: string, the hostname or IP address of the bmc
3298         @param args: contains additional arguments used by the ldap subcommand
3299         @param session: the active session to use
3300         @param args.json: boolean, if this flag is set to true, the output will
3301            be provided in json format for programmatic consumption
3302    """
3303
3304    if(isRedfishSupport):
3305        return enableLDAP(host, args, session)
3306    else:
3307        return enableLegacyLDAP(host, args, session)
3308
3309def enableLegacyLDAP(host, args, session):
3310    """
3311         Called by the ldap function. Configures LDAP on Lagecy systems.
3312
3313         @param host: string, the hostname or IP address of the bmc
3314         @param args: contains additional arguments used by the ldap subcommand
3315         @param session: the active session to use
3316         @param args.json: boolean, if this flag is set to true, the output will
3317            be provided in json format for programmatic consumption
3318    """
3319
3320    url='https://'+host+'/xyz/openbmc_project/user/ldap/action/CreateConfig'
3321    scope = {
3322             'sub' : 'xyz.openbmc_project.User.Ldap.Create.SearchScope.sub',
3323             'one' : 'xyz.openbmc_project.User.Ldap.Create.SearchScope.one',
3324             'base': 'xyz.openbmc_project.User.Ldap.Create.SearchScope.base'
3325            }
3326
3327    serverType = {
3328             'ActiveDirectory' : 'xyz.openbmc_project.User.Ldap.Create.Type.ActiveDirectory',
3329             'OpenLDAP' : 'xyz.openbmc_project.User.Ldap.Create.Type.OpenLdap'
3330            }
3331
3332    data = {"data": [args.uri, args.bindDN, args.baseDN, args.bindPassword, scope[args.scope], serverType[args.serverType]]}
3333
3334    try:
3335        res = session.post(url, headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
3336    except(requests.exceptions.Timeout):
3337        return(connectionErrHandler(args.json, "Timeout", None))
3338    except(requests.exceptions.ConnectionError) as err:
3339        return connectionErrHandler(args.json, "ConnectionError", err)
3340
3341    return res.text
3342
3343def enableLDAP(host, args, session):
3344    """
3345         Called by the ldap function. Configures LDAP for systems with latest user-manager design changes
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    scope = {
3355             'sub' : 'xyz.openbmc_project.User.Ldap.Config.SearchScope.sub',
3356             'one' : 'xyz.openbmc_project.User.Ldap.Config.SearchScope.one',
3357             'base': 'xyz.openbmc_project.User.Ldap.Config.SearchScope.base'
3358            }
3359
3360    serverType = {
3361            'ActiveDirectory' : 'xyz.openbmc_project.User.Ldap.Config.Type.ActiveDirectory',
3362            'OpenLDAP' : 'xyz.openbmc_project.User.Ldap.Config.Type.OpenLdap'
3363            }
3364
3365    url = "https://"+host+"/xyz/openbmc_project/user/ldap/"
3366
3367    serverTypeEnabled = getLDAPTypeEnabled(host,session)
3368    serverTypeToBeEnabled = args.serverType
3369
3370    #If the given LDAP type is already enabled, then return
3371    if (serverTypeToBeEnabled == serverTypeEnabled):
3372      return("Server type " + serverTypeToBeEnabled + " is already enabled...")
3373
3374    try:
3375
3376        #  Copy the role map from the currently enabled LDAP server type
3377        #  to the newly enabled server type
3378        #  Disable the currently enabled LDAP server type. Unless
3379        #  it is disabled, we cannot enable a new LDAP server type
3380        if (serverTypeEnabled is not None):
3381
3382            if (serverTypeToBeEnabled != serverTypeEnabled):
3383                res = syncRoleMap(host,args,session,serverTypeEnabled,serverTypeToBeEnabled)
3384
3385            data = "{\"data\": 0 }"
3386            res = session.put(url + serverTypeMap[serverTypeEnabled] + '/attr/Enabled', headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
3387
3388        data = {"data": args.baseDN}
3389        res = session.put(url + serverTypeMap[serverTypeToBeEnabled] + '/attr/LDAPBaseDN', headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
3390        if (res.status_code != requests.codes.ok):
3391            print("Updates to the property LDAPBaseDN failed...")
3392            return(res.text)
3393
3394        data = {"data": args.bindDN}
3395        res = session.put(url + serverTypeMap[serverTypeToBeEnabled] + '/attr/LDAPBindDN', headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
3396        if (res.status_code != requests.codes.ok):
3397           print("Updates to the property LDAPBindDN failed...")
3398           return(res.text)
3399
3400        data = {"data": args.bindPassword}
3401        res = session.put(url + serverTypeMap[serverTypeToBeEnabled] + '/attr/LDAPBindDNPassword', headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
3402        if (res.status_code != requests.codes.ok):
3403           print("Updates to the property LDAPBindDNPassword failed...")
3404           return(res.text)
3405
3406        data = {"data": scope[args.scope]}
3407        res = session.put(url + serverTypeMap[serverTypeToBeEnabled] + '/attr/LDAPSearchScope', headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
3408        if (res.status_code != requests.codes.ok):
3409           print("Updates to the property LDAPSearchScope failed...")
3410           return(res.text)
3411
3412        data = {"data": args.uri}
3413        res = session.put(url + serverTypeMap[serverTypeToBeEnabled] + '/attr/LDAPServerURI', headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
3414        if (res.status_code != requests.codes.ok):
3415           print("Updates to the property LDAPServerURI failed...")
3416           return(res.text)
3417
3418        data = {"data": args.groupAttrName}
3419        res = session.put(url + serverTypeMap[serverTypeToBeEnabled] + '/attr/GroupNameAttribute', headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
3420        if (res.status_code != requests.codes.ok):
3421           print("Updates to the property GroupNameAttribute failed...")
3422           return(res.text)
3423
3424        data = {"data": args.userAttrName}
3425        res = session.put(url + serverTypeMap[serverTypeToBeEnabled] + '/attr/UserNameAttribute', headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
3426        if (res.status_code != requests.codes.ok):
3427           print("Updates to the property UserNameAttribute failed...")
3428           return(res.text)
3429
3430        #After updating the properties, enable the new server type
3431        data = "{\"data\": 1 }"
3432        res = session.put(url + serverTypeMap[serverTypeToBeEnabled] + '/attr/Enabled', headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
3433
3434    except(requests.exceptions.Timeout):
3435        return(connectionErrHandler(args.json, "Timeout", None))
3436    except(requests.exceptions.ConnectionError) as err:
3437        return connectionErrHandler(args.json, "ConnectionError", err)
3438    return res.text
3439
3440def disableLDAP(host, args, session):
3441    """
3442         Called by the ldap function. Deletes the LDAP Configuration.
3443
3444         @param host: string, the hostname or IP address of the bmc
3445         @param args: contains additional arguments used by the ldap subcommand
3446         @param session: the active session to use
3447         @param args.json: boolean, if this flag is set to true, the output
3448            will be provided in json format for programmatic consumption
3449    """
3450
3451    try:
3452        if (isRedfishSupport) :
3453
3454            url = "https://"+host+"/xyz/openbmc_project/user/ldap/"
3455
3456            serverTypeEnabled = getLDAPTypeEnabled(host,session)
3457
3458            if (serverTypeEnabled is not None):
3459                #To keep the role map in sync,
3460                #If the server type being disabled has role map, then
3461                #   - copy the role map to the other server type(s)
3462                for serverType in serverTypeMap.keys():
3463                    if (serverType != serverTypeEnabled):
3464                        res = syncRoleMap(host,args,session,serverTypeEnabled,serverType)
3465
3466                #Disable the currently enabled LDAP server type
3467                data = "{\"data\": 0 }"
3468                res = session.put(url + serverTypeMap[serverTypeEnabled] + '/attr/Enabled', headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
3469
3470            else:
3471                return("LDAP server has not been enabled...")
3472
3473        else :
3474            url='https://'+host+'/xyz/openbmc_project/user/ldap/config/action/delete'
3475            data = {"data": []}
3476            res = session.post(url, headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
3477
3478    except(requests.exceptions.Timeout):
3479        return(connectionErrHandler(args.json, "Timeout", None))
3480    except(requests.exceptions.ConnectionError) as err:
3481        return connectionErrHandler(args.json, "ConnectionError", err)
3482
3483    return res.text
3484
3485def enableDHCP(host, args, session):
3486
3487    """
3488        Called by the network function. Enables DHCP.
3489
3490        @param host: string, the hostname or IP address of the bmc
3491        @param args: contains additional arguments used by the ldap subcommand
3492                args.json: boolean, if this flag is set to true, the output
3493                will be provided in json format for programmatic consumption
3494        @param session: the active session to use
3495    """
3496
3497    url = "https://"+host+"/xyz/openbmc_project/network/"+args.Interface+\
3498    "/attr/DHCPEnabled"
3499    data = "{\"data\": 1 }"
3500    try:
3501        res = session.put(url, headers=jsonHeader, data=data, verify=False,
3502                          timeout=baseTimeout)
3503
3504    except(requests.exceptions.Timeout):
3505        return(connectionErrHandler(args.json, "Timeout", None))
3506    except(requests.exceptions.ConnectionError) as err:
3507        return connectionErrHandler(args.json, "ConnectionError", err)
3508    if res.status_code == 403:
3509        return "The specified Interface"+"("+args.Interface+")"+\
3510        " doesn't exist"
3511
3512    return res.text
3513
3514
3515def disableDHCP(host, args, session):
3516    """
3517        Called by the network function. Disables DHCP.
3518
3519        @param host: string, the hostname or IP address of the bmc
3520        @param args: contains additional arguments used by the ldap subcommand
3521                args.json: boolean, if this flag is set to true, the output
3522                will be provided in json format for programmatic consumption
3523        @param session: the active session to use
3524    """
3525
3526    url = "https://"+host+"/xyz/openbmc_project/network/"+args.Interface+\
3527    "/attr/DHCPEnabled"
3528    data = "{\"data\": 0 }"
3529    try:
3530        res = session.put(url, headers=jsonHeader, data=data, verify=False,
3531                          timeout=baseTimeout)
3532    except(requests.exceptions.Timeout):
3533        return(connectionErrHandler(args.json, "Timeout", None))
3534    except(requests.exceptions.ConnectionError) as err:
3535        return connectionErrHandler(args.json, "ConnectionError", err)
3536    if res.status_code == 403:
3537        return "The specified Interface"+"("+args.Interface+")"+\
3538        " doesn't exist"
3539    return res.text
3540
3541
3542def getHostname(host, args, session):
3543
3544    """
3545        Called by the network function. Prints out the Hostname.
3546
3547        @param host: string, the hostname or IP address of the bmc
3548        @param args: contains additional arguments used by the ldap subcommand
3549                args.json: boolean, if this flag is set to true, the output
3550                will be provided in json format for programmatic consumption
3551        @param session: the active session to use
3552    """
3553
3554    url = "https://"+host+"/xyz/openbmc_project/network/config/attr/HostName"
3555
3556    try:
3557        res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
3558    except(requests.exceptions.Timeout):
3559        return(connectionErrHandler(args.json, "Timeout", None))
3560    except(requests.exceptions.ConnectionError) as err:
3561        return connectionErrHandler(args.json, "ConnectionError", err)
3562
3563    return res.text
3564
3565
3566def setHostname(host, args, session):
3567    """
3568        Called by the network function. Sets the Hostname.
3569
3570        @param host: string, the hostname or IP address of the bmc
3571        @param args: contains additional arguments used by the ldap subcommand
3572                args.json: boolean, if this flag is set to true, the output
3573                will be provided in json format for programmatic consumption
3574        @param session: the active session to use
3575    """
3576
3577    url = "https://"+host+"/xyz/openbmc_project/network/config/attr/HostName"
3578
3579    data = {"data": args.HostName}
3580
3581    try:
3582        res = session.put(url, headers=jsonHeader, json=data, verify=False,
3583                          timeout=baseTimeout)
3584    except(requests.exceptions.Timeout):
3585        return(connectionErrHandler(args.json, "Timeout", None))
3586    except(requests.exceptions.ConnectionError) as err:
3587        return connectionErrHandler(args.json, "ConnectionError", err)
3588
3589    return res.text
3590
3591
3592def getDomainName(host, args, session):
3593
3594    """
3595        Called by the network function. Prints out the DomainName.
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/"+args.Interface+\
3605    "/attr/DomainName"
3606
3607    try:
3608        res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
3609    except(requests.exceptions.Timeout):
3610        return(connectionErrHandler(args.json, "Timeout", None))
3611    except(requests.exceptions.ConnectionError) as err:
3612        return connectionErrHandler(args.json, "ConnectionError", err)
3613    if res.status_code == 404:
3614        return "The DomainName is not configured on Interface"+"("+args.Interface+")"
3615
3616    return res.text
3617
3618
3619def setDomainName(host, args, session):
3620    """
3621        Called by the network function. Sets the DomainName.
3622
3623        @param host: string, the hostname or IP address of the bmc
3624        @param args: contains additional arguments used by the ldap subcommand
3625                args.json: boolean, if this flag is set to true, the output
3626                will be provided in json format for programmatic consumption
3627        @param session: the active session to use
3628    """
3629
3630    url = "https://"+host+"/xyz/openbmc_project/network/"+args.Interface+\
3631    "/attr/DomainName"
3632
3633    data = {"data": args.DomainName.split(",")}
3634
3635    try:
3636        res = session.put(url, headers=jsonHeader, json=data, verify=False,
3637                          timeout=baseTimeout)
3638    except(requests.exceptions.Timeout):
3639        return(connectionErrHandler(args.json, "Timeout", None))
3640    except(requests.exceptions.ConnectionError) as err:
3641        return connectionErrHandler(args.json, "ConnectionError", err)
3642    if res.status_code == 403:
3643        return "Failed to set Domain Name"
3644
3645    return res.text
3646
3647
3648def getMACAddress(host, args, session):
3649
3650    """
3651        Called by the network function. Prints out the MACAddress.
3652
3653        @param host: string, the hostname or IP address of the bmc
3654        @param args: contains additional arguments used by the ldap subcommand
3655                args.json: boolean, if this flag is set to true, the output
3656                will be provided in json format for programmatic consumption
3657        @param session: the active session to use
3658    """
3659
3660    url = "https://"+host+"/xyz/openbmc_project/network/"+args.Interface+\
3661    "/attr/MACAddress"
3662
3663    try:
3664        res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
3665    except(requests.exceptions.Timeout):
3666        return(connectionErrHandler(args.json, "Timeout", None))
3667    except(requests.exceptions.ConnectionError) as err:
3668        return connectionErrHandler(args.json, "ConnectionError", err)
3669    if res.status_code == 404:
3670        return "Failed to get MACAddress"
3671
3672    return res.text
3673
3674
3675def setMACAddress(host, args, session):
3676    """
3677        Called by the network function. Sets the MACAddress.
3678
3679        @param host: string, the hostname or IP address of the bmc
3680        @param args: contains additional arguments used by the ldap subcommand
3681                args.json: boolean, if this flag is set to true, the output
3682                will be provided in json format for programmatic consumption
3683        @param session: the active session to use
3684    """
3685
3686    url = "https://"+host+"/xyz/openbmc_project/network/"+args.Interface+\
3687    "/attr/MACAddress"
3688
3689    data = {"data": args.MACAddress}
3690
3691    try:
3692        res = session.put(url, headers=jsonHeader, json=data, verify=False,
3693                          timeout=baseTimeout)
3694    except(requests.exceptions.Timeout):
3695        return(connectionErrHandler(args.json, "Timeout", None))
3696    except(requests.exceptions.ConnectionError) as err:
3697        return connectionErrHandler(args.json, "ConnectionError", err)
3698    if res.status_code == 403:
3699        return "Failed to set MACAddress"
3700
3701    return res.text
3702
3703
3704def getDefaultGateway(host, args, session):
3705
3706    """
3707        Called by the network function. Prints out the DefaultGateway.
3708
3709        @param host: string, the hostname or IP address of the bmc
3710        @param args: contains additional arguments used by the ldap subcommand
3711                args.json: boolean, if this flag is set to true, the output
3712                will be provided in json format for programmatic consumption
3713        @param session: the active session to use
3714    """
3715
3716    url = "https://"+host+"/xyz/openbmc_project/network/config/attr/DefaultGateway"
3717
3718    try:
3719        res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
3720    except(requests.exceptions.Timeout):
3721        return(connectionErrHandler(args.json, "Timeout", None))
3722    except(requests.exceptions.ConnectionError) as err:
3723        return connectionErrHandler(args.json, "ConnectionError", err)
3724    if res.status_code == 404:
3725        return "Failed to get Default Gateway info"
3726
3727    return res.text
3728
3729
3730def setDefaultGateway(host, args, session):
3731    """
3732        Called by the network function. Sets the DefaultGateway.
3733
3734        @param host: string, the hostname or IP address of the bmc
3735        @param args: contains additional arguments used by the ldap subcommand
3736                args.json: boolean, if this flag is set to true, the output
3737                will be provided in json format for programmatic consumption
3738        @param session: the active session to use
3739    """
3740
3741    url = "https://"+host+"/xyz/openbmc_project/network/config/attr/DefaultGateway"
3742
3743    data = {"data": args.DefaultGW}
3744
3745    try:
3746        res = session.put(url, headers=jsonHeader, json=data, verify=False,
3747                          timeout=baseTimeout)
3748    except(requests.exceptions.Timeout):
3749        return(connectionErrHandler(args.json, "Timeout", None))
3750    except(requests.exceptions.ConnectionError) as err:
3751        return connectionErrHandler(args.json, "ConnectionError", err)
3752    if res.status_code == 403:
3753        return "Failed to set Default Gateway"
3754
3755    return res.text
3756
3757
3758def viewNWConfig(host, args, session):
3759    """
3760         Called by the ldap function. Prints out network configured properties
3761
3762         @param host: string, the hostname or IP address of the bmc
3763         @param args: contains additional arguments used by the ldap subcommand
3764                args.json: boolean, if this flag is set to true, the output
3765                will be provided in json format for programmatic consumption
3766         @param session: the active session to use
3767         @return returns LDAP's configured properties.
3768    """
3769    url = "https://"+host+"/xyz/openbmc_project/network/enumerate"
3770    try:
3771        res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
3772    except(requests.exceptions.Timeout):
3773        return(connectionErrHandler(args.json, "Timeout", None))
3774    except(requests.exceptions.ConnectionError) as err:
3775        return connectionErrHandler(args.json, "ConnectionError", err)
3776    except(requests.exceptions.RequestException) as err:
3777        return connectionErrHandler(args.json, "RequestException", err)
3778    if res.status_code == 404:
3779        return "LDAP server config has not been created"
3780    return res.text
3781
3782
3783def getDNS(host, args, session):
3784
3785    """
3786        Called by the network function. Prints out DNS servers on the interface
3787
3788        @param host: string, the hostname or IP address of the bmc
3789        @param args: contains additional arguments used by the ldap subcommand
3790                args.json: boolean, if this flag is set to true, the output
3791                will be provided in json format for programmatic consumption
3792        @param session: the active session to use
3793    """
3794
3795    url = "https://" + host + "/xyz/openbmc_project/network/" + args.Interface\
3796        + "/attr/Nameservers"
3797
3798    try:
3799        res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
3800    except(requests.exceptions.Timeout):
3801        return(connectionErrHandler(args.json, "Timeout", None))
3802    except(requests.exceptions.ConnectionError) as err:
3803        return connectionErrHandler(args.json, "ConnectionError", err)
3804    if res.status_code == 404:
3805        return "The NameServer is not configured on Interface"+"("+args.Interface+")"
3806
3807    return res.text
3808
3809
3810def setDNS(host, args, session):
3811    """
3812        Called by the network function. Sets DNS servers on the interface.
3813
3814        @param host: string, the hostname or IP address of the bmc
3815        @param args: contains additional arguments used by the ldap subcommand
3816                args.json: boolean, if this flag is set to true, the output
3817                will be provided in json format for programmatic consumption
3818        @param session: the active session to use
3819    """
3820
3821    url = "https://" + host + "/xyz/openbmc_project/network/" + args.Interface\
3822        + "/attr/Nameservers"
3823
3824    data = {"data": args.DNSServers.split(",")}
3825
3826    try:
3827        res = session.put(url, headers=jsonHeader, json=data, verify=False,
3828                          timeout=baseTimeout)
3829    except(requests.exceptions.Timeout):
3830        return(connectionErrHandler(args.json, "Timeout", None))
3831    except(requests.exceptions.ConnectionError) as err:
3832        return connectionErrHandler(args.json, "ConnectionError", err)
3833    if res.status_code == 403:
3834        return "Failed to set DNS"
3835
3836    return res.text
3837
3838
3839def getNTP(host, args, session):
3840
3841    """
3842        Called by the network function. Prints out NTP servers on the interface
3843
3844        @param host: string, the hostname or IP address of the bmc
3845        @param args: contains additional arguments used by the ldap subcommand
3846                args.json: boolean, if this flag is set to true, the output
3847                will be provided in json format for programmatic consumption
3848        @param session: the active session to use
3849    """
3850
3851    url = "https://" + host + "/xyz/openbmc_project/network/" + args.Interface\
3852        + "/attr/NTPServers"
3853    try:
3854        res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
3855    except(requests.exceptions.Timeout):
3856        return(connectionErrHandler(args.json, "Timeout", None))
3857    except(requests.exceptions.ConnectionError) as err:
3858        return connectionErrHandler(args.json, "ConnectionError", err)
3859    if res.status_code == 404:
3860        return "The NTPServer is not configured on Interface"+"("+args.Interface+")"
3861
3862    return res.text
3863
3864
3865def setNTP(host, args, session):
3866    """
3867        Called by the network function. Sets NTP servers on the interface.
3868
3869        @param host: string, the hostname or IP address of the bmc
3870        @param args: contains additional arguments used by the ldap subcommand
3871                args.json: boolean, if this flag is set to true, the output
3872                will be provided in json format for programmatic consumption
3873        @param session: the active session to use
3874    """
3875
3876    url = "https://" + host + "/xyz/openbmc_project/network/" + args.Interface\
3877        + "/attr/NTPServers"
3878
3879    data = {"data": args.NTPServers.split(",")}
3880
3881    try:
3882        res = session.put(url, headers=jsonHeader, json=data, verify=False,
3883                          timeout=baseTimeout)
3884    except(requests.exceptions.Timeout):
3885        return(connectionErrHandler(args.json, "Timeout", None))
3886    except(requests.exceptions.ConnectionError) as err:
3887        return connectionErrHandler(args.json, "ConnectionError", err)
3888    if res.status_code == 403:
3889        return "Failed to set NTP"
3890
3891    return res.text
3892
3893
3894def addIP(host, args, session):
3895    """
3896        Called by the network function. Configures IP address on given interface
3897
3898        @param host: string, the hostname or IP address of the bmc
3899        @param args: contains additional arguments used by the ldap subcommand
3900                args.json: boolean, if this flag is set to true, the output
3901                will be provided in json format for programmatic consumption
3902        @param session: the active session to use
3903    """
3904
3905    url = "https://" + host + "/xyz/openbmc_project/network/" + args.Interface\
3906        + "/action/IP"
3907    protocol = {
3908             'ipv4': 'xyz.openbmc_project.Network.IP.Protocol.IPv4',
3909             'ipv6': 'xyz.openbmc_project.Network.IP.Protocol.IPv6'
3910            }
3911
3912    data = {"data": [protocol[args.type], args.address, int(args.prefixLength),
3913        args.gateway]}
3914
3915    try:
3916        res = session.post(url, headers=jsonHeader, json=data, verify=False,
3917                           timeout=baseTimeout)
3918    except(requests.exceptions.Timeout):
3919        return(connectionErrHandler(args.json, "Timeout", None))
3920    except(requests.exceptions.ConnectionError) as err:
3921        return connectionErrHandler(args.json, "ConnectionError", err)
3922    if res.status_code == 404:
3923        return "The specified Interface" + "(" + args.Interface + ")" +\
3924            " doesn't exist"
3925
3926    return res.text
3927
3928
3929def getIP(host, args, session):
3930    """
3931        Called by the network function. Prints out IP address of given interface
3932
3933        @param host: string, the hostname or IP address of the bmc
3934        @param args: contains additional arguments used by the ldap subcommand
3935                args.json: boolean, if this flag is set to true, the output
3936                will be provided in json format for programmatic consumption
3937        @param session: the active session to use
3938    """
3939
3940    url = "https://" + host+"/xyz/openbmc_project/network/" + args.Interface +\
3941        "/enumerate"
3942    try:
3943        res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
3944    except(requests.exceptions.Timeout):
3945        return(connectionErrHandler(args.json, "Timeout", None))
3946    except(requests.exceptions.ConnectionError) as err:
3947        return connectionErrHandler(args.json, "ConnectionError", err)
3948    if res.status_code == 404:
3949        return "The specified Interface" + "(" + args.Interface + ")" +\
3950            " doesn't exist"
3951
3952    return res.text
3953
3954
3955def deleteIP(host, args, session):
3956    """
3957        Called by the network function. Deletes the IP address from given Interface
3958
3959        @param host: string, the hostname or IP address of the bmc
3960        @param args: contains additional arguments used by the ldap subcommand
3961        @param session: the active session to use
3962        @param args.json: boolean, if this flag is set to true, the output
3963            will be provided in json format for programmatic consumption
3964    """
3965
3966    url = "https://"+host+"/xyz/openbmc_project/network/" + args.Interface+\
3967        "/enumerate"
3968    data = {"data": []}
3969    try:
3970        res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
3971    except(requests.exceptions.Timeout):
3972        return(connectionErrHandler(args.json, "Timeout", None))
3973    except(requests.exceptions.ConnectionError) as err:
3974        return connectionErrHandler(args.json, "ConnectionError", err)
3975    if res.status_code == 404:
3976        return "The specified Interface" + "(" + args.Interface + ")" +\
3977            " doesn't exist"
3978    objDict = json.loads(res.text)
3979    if not objDict['data']:
3980        return "No object found for given address on given Interface"
3981    for obj in objDict['data']:
3982        try:
3983            if args.address in objDict['data'][obj]['Address']:
3984                url = "https://"+host+obj+"/action/Delete"
3985                try:
3986                    res = session.post(url, headers=jsonHeader, json=data,
3987                                       verify=False, timeout=baseTimeout)
3988                except(requests.exceptions.Timeout):
3989                    return(connectionErrHandler(args.json, "Timeout", None))
3990                except(requests.exceptions.ConnectionError) as err:
3991                    return connectionErrHandler(args.json, "ConnectionError", err)
3992                return res.text
3993            else:
3994                continue
3995        except KeyError:
3996            continue
3997    return "No object found for address " + args.address + \
3998           " on Interface(" + args.Interface + ")"
3999
4000
4001def addVLAN(host, args, session):
4002    """
4003        Called by the network function. Creates VLAN on given interface.
4004
4005        @param host: string, the hostname or IP address of the bmc
4006        @param args: contains additional arguments used by the ldap subcommand
4007                args.json: boolean, if this flag is set to true, the output
4008                will be provided in json format for programmatic consumption
4009        @param session: the active session to use
4010    """
4011
4012    url = "https://" + host+"/xyz/openbmc_project/network/action/VLAN"
4013
4014    data = {"data": [args.Interface,int(args.Identifier)]}
4015    try:
4016        res = session.post(url, headers=jsonHeader, json=data, verify=False,
4017                           timeout=baseTimeout)
4018    except(requests.exceptions.Timeout):
4019        return(connectionErrHandler(args.json, "Timeout", None))
4020    except(requests.exceptions.ConnectionError) as err:
4021        return connectionErrHandler(args.json, "ConnectionError", err)
4022    if res.status_code == 400:
4023        return "Adding VLAN to interface" + "(" + args.Interface + ")" +\
4024            " failed"
4025
4026    return res.text
4027
4028
4029def deleteVLAN(host, args, session):
4030    """
4031        Called by the network function. Creates VLAN on given interface.
4032
4033        @param host: string, the hostname or IP address of the bmc
4034        @param args: contains additional arguments used by the ldap subcommand
4035                args.json: boolean, if this flag is set to true, the output
4036                will be provided in json format for programmatic consumption
4037        @param session: the active session to use
4038    """
4039
4040    url = "https://" + host+"/xyz/openbmc_project/network/"+args.Interface+"/action/Delete"
4041    data = {"data": []}
4042
4043    try:
4044        res = session.post(url, headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
4045    except(requests.exceptions.Timeout):
4046        return(connectionErrHandler(args.json, "Timeout", None))
4047    except(requests.exceptions.ConnectionError) as err:
4048        return connectionErrHandler(args.json, "ConnectionError", err)
4049    if res.status_code == 404:
4050        return "The specified VLAN"+"("+args.Interface+")" +" doesn't exist"
4051
4052    return res.text
4053
4054
4055def viewDHCPConfig(host, args, session):
4056    """
4057        Called by the network function. Shows DHCP configured Properties.
4058
4059        @param host: string, the hostname or IP address of the bmc
4060        @param args: contains additional arguments used by the ldap subcommand
4061                args.json: boolean, if this flag is set to true, the output
4062                will be provided in json format for programmatic consumption
4063        @param session: the active session to use
4064    """
4065
4066    url="https://"+host+"/xyz/openbmc_project/network/config/dhcp"
4067
4068    try:
4069        res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
4070    except(requests.exceptions.Timeout):
4071        return(connectionErrHandler(args.json, "Timeout", None))
4072    except(requests.exceptions.ConnectionError) as err:
4073        return connectionErrHandler(args.json, "ConnectionError", err)
4074
4075    return res.text
4076
4077
4078def configureDHCP(host, args, session):
4079    """
4080        Called by the network function. Configures/updates DHCP Properties.
4081
4082        @param host: string, the hostname or IP address of the bmc
4083        @param args: contains additional arguments used by the ldap subcommand
4084                args.json: boolean, if this flag is set to true, the output
4085                will be provided in json format for programmatic consumption
4086        @param session: the active session to use
4087    """
4088
4089
4090    try:
4091        url="https://"+host+"/xyz/openbmc_project/network/config/dhcp"
4092        if(args.DNSEnabled == True):
4093            data = '{"data": 1}'
4094        else:
4095            data = '{"data": 0}'
4096        res = session.put(url + '/attr/DNSEnabled', headers=jsonHeader,
4097                          data=data, verify=False, timeout=baseTimeout)
4098        if(args.HostNameEnabled == True):
4099            data = '{"data": 1}'
4100        else:
4101            data = '{"data": 0}'
4102        res = session.put(url + '/attr/HostNameEnabled', headers=jsonHeader,
4103                          data=data, verify=False, timeout=baseTimeout)
4104        if(args.NTPEnabled == True):
4105            data = '{"data": 1}'
4106        else:
4107            data = '{"data": 0}'
4108        res = session.put(url + '/attr/NTPEnabled', headers=jsonHeader,
4109                          data=data, verify=False, timeout=baseTimeout)
4110        if(args.SendHostNameEnabled == True):
4111            data = '{"data": 1}'
4112        else:
4113            data = '{"data": 0}'
4114        res = session.put(url + '/attr/SendHostNameEnabled', headers=jsonHeader,
4115                          data=data, verify=False, timeout=baseTimeout)
4116    except(requests.exceptions.Timeout):
4117        return(connectionErrHandler(args.json, "Timeout", None))
4118    except(requests.exceptions.ConnectionError) as err:
4119        return connectionErrHandler(args.json, "ConnectionError", err)
4120
4121    return res.text
4122
4123
4124def nwReset(host, args, session):
4125
4126    """
4127        Called by the network function. Resets networks setting to factory defaults.
4128
4129        @param host: string, the hostname or IP address of the bmc
4130        @param args: contains additional arguments used by the ldap subcommand
4131                args.json: boolean, if this flag is set to true, the output
4132                will be provided in json format for programmatic consumption
4133        @param session: the active session to use
4134    """
4135
4136    url = "https://"+host+"/xyz/openbmc_project/network/action/Reset"
4137    data = '{"data":[] }'
4138    try:
4139        res = session.post(url, headers=jsonHeader, data=data, verify=False,
4140                          timeout=baseTimeout)
4141
4142    except(requests.exceptions.Timeout):
4143        return(connectionErrHandler(args.json, "Timeout", None))
4144    except(requests.exceptions.ConnectionError) as err:
4145        return connectionErrHandler(args.json, "ConnectionError", err)
4146
4147    return res.text
4148
4149def getLDAPTypeEnabled(host,session):
4150
4151    """
4152        Called by LDAP related functions to find the LDAP server type that has been enabled.
4153        Returns None if LDAP has not been configured.
4154
4155        @param host: string, the hostname or IP address of the bmc
4156        @param session: the active session to use
4157    """
4158
4159    enabled = False
4160    url = 'https://'+host+'/xyz/openbmc_project/user/ldap/'
4161    for key,value in serverTypeMap.items():
4162        data = {"data": []}
4163        try:
4164            res = session.get(url + value + '/attr/Enabled', headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
4165        except(requests.exceptions.Timeout):
4166            print(connectionErrHandler(args.json, "Timeout", None))
4167            return
4168        except(requests.exceptions.ConnectionError) as err:
4169            print(connectionErrHandler(args.json, "ConnectionError", err))
4170            return
4171
4172        enabled = res.json()['data']
4173        if (enabled):
4174            return key
4175
4176def syncRoleMap(host,args,session,fromServerType,toServerType):
4177
4178    """
4179        Called by LDAP related functions to sync the role maps
4180        Returns False if LDAP has not been configured.
4181
4182        @param host: string, the hostname or IP address of the bmc
4183        @param session: the active session to use
4184        @param fromServerType : Server type whose role map has to be copied
4185        @param toServerType : Server type to which role map has to be copied
4186    """
4187
4188    url = "https://"+host+"/xyz/openbmc_project/user/ldap/"
4189
4190    try:
4191        #Note: If the fromServerType has no role map, then
4192        #the toServerType will not have any role map.
4193
4194        #delete the privilege mapping from the toServerType and
4195        #then copy the privilege mapping from fromServerType to
4196        #toServerType.
4197        args.serverType = toServerType
4198        res = deleteAllPrivilegeMapping(host, args, session)
4199
4200        data = {"data": []}
4201        res = session.get(url + serverTypeMap[fromServerType] + '/role_map/enumerate', headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
4202        #Previously enabled server type has no role map
4203        if (res.status_code != requests.codes.ok):
4204
4205            #fromServerType has no role map; So, no need to copy
4206            #role map to toServerType.
4207            return
4208
4209        objDict = json.loads(res.text)
4210        dataDict = objDict['data']
4211        for  key,value in dataDict.items():
4212            data = {"data": [value["GroupName"], value["Privilege"]]}
4213            res = session.post(url + serverTypeMap[toServerType] + '/action/Create', headers=jsonHeader, json = data, verify=False, timeout=baseTimeout)
4214
4215    except(requests.exceptions.Timeout):
4216        return(connectionErrHandler(args.json, "Timeout", None))
4217    except(requests.exceptions.ConnectionError) as err:
4218        return connectionErrHandler(args.json, "ConnectionError", err)
4219    return res.text
4220
4221
4222def createPrivilegeMapping(host, args, session):
4223    """
4224         Called by the ldap function. Creates the group and the privilege mapping.
4225
4226         @param host: string, the hostname or IP address of the bmc
4227         @param args: contains additional arguments used by the ldap subcommand
4228         @param session: the active session to use
4229         @param args.json: boolean, if this flag is set to true, the output
4230                will be provided in json format for programmatic consumption
4231    """
4232
4233    try:
4234        if (isRedfishSupport):
4235            url = 'https://'+host+'/xyz/openbmc_project/user/ldap/'
4236
4237            #To maintain the interface compatibility between op930 and op940, the server type has been made
4238            #optional. If the server type is not specified, then create the role-mapper for the currently
4239            #enabled server type.
4240            serverType = args.serverType
4241            if (serverType is None):
4242                serverType = getLDAPTypeEnabled(host,session)
4243                if (serverType is None):
4244                     return("LDAP server has not been enabled. Please specify LDAP serverType to proceed further...")
4245
4246            data = {"data": [args.groupName,args.privilege]}
4247            res = session.post(url + serverTypeMap[serverType] + '/action/Create', headers=jsonHeader, json = data, verify=False, timeout=baseTimeout)
4248
4249        else:
4250            url = 'https://'+host+'/xyz/openbmc_project/user/ldap/action/Create'
4251            data = {"data": [args.groupName,args.privilege]}
4252            res = session.post(url, headers=jsonHeader, json = data, verify=False, timeout=baseTimeout)
4253
4254    except(requests.exceptions.Timeout):
4255        return(connectionErrHandler(args.json, "Timeout", None))
4256    except(requests.exceptions.ConnectionError) as err:
4257        return connectionErrHandler(args.json, "ConnectionError", err)
4258    return res.text
4259
4260def listPrivilegeMapping(host, args, session):
4261    """
4262         Called by the ldap function. Lists the group and the privilege mapping.
4263
4264         @param host: string, the hostname or IP address of the bmc
4265         @param args: contains additional arguments used by the ldap subcommand
4266         @param session: the active session to use
4267         @param args.json: boolean, if this flag is set to true, the output
4268                will be provided in json format for programmatic consumption
4269    """
4270
4271    if (isRedfishSupport):
4272        serverType = args.serverType
4273        if (serverType is None):
4274            serverType = getLDAPTypeEnabled(host,session)
4275            if (serverType is None):
4276                return("LDAP has not been enabled. Please specify LDAP serverType to proceed further...")
4277
4278        url = 'https://'+host+'/xyz/openbmc_project/user/ldap/'+serverTypeMap[serverType]+'/role_map/enumerate'
4279
4280    else:
4281        url = 'https://'+host+'/xyz/openbmc_project/user/ldap/enumerate'
4282
4283    data = {"data": []}
4284
4285    try:
4286        res = session.get(url, headers=jsonHeader, json = data, verify=False, timeout=baseTimeout)
4287    except(requests.exceptions.Timeout):
4288        return(connectionErrHandler(args.json, "Timeout", None))
4289    except(requests.exceptions.ConnectionError) as err:
4290        return connectionErrHandler(args.json, "ConnectionError", err)
4291
4292    return res.text
4293
4294def deletePrivilegeMapping(host, args, session):
4295    """
4296         Called by the ldap function. Deletes the mapping associated with the group.
4297
4298         @param host: string, the hostname or IP address of the bmc
4299         @param args: contains additional arguments used by the ldap subcommand
4300         @param session: the active session to use
4301         @param args.json: boolean, if this flag is set to true, the output
4302                will be provided in json format for programmatic consumption
4303    """
4304
4305    ldapNameSpaceObjects = listPrivilegeMapping(host, args, session)
4306    ldapNameSpaceObjects = json.loads(ldapNameSpaceObjects)["data"]
4307    path = ''
4308    data = {"data": []}
4309
4310    if (isRedfishSupport):
4311        if (args.serverType is None):
4312            serverType = getLDAPTypeEnabled(host,session)
4313            if (serverType is None):
4314                return("LDAP has not been enabled. Please specify LDAP serverType to proceed further...")
4315        # search for the object having the mapping for the given group
4316        for key,value in ldapNameSpaceObjects.items():
4317            if value['GroupName'] == args.groupName:
4318                path = key
4319                break
4320
4321        if path == '':
4322            return "No privilege mapping found for this group."
4323
4324        # delete the object
4325        url = 'https://'+host+path+'/action/Delete'
4326
4327    else:
4328        # not interested in the config objet
4329        ldapNameSpaceObjects.pop('/xyz/openbmc_project/user/ldap/config', None)
4330
4331        # search for the object having the mapping for the given group
4332        for key,value in ldapNameSpaceObjects.items():
4333            if value['GroupName'] == args.groupName:
4334                path = key
4335                break
4336
4337        if path == '':
4338            return "No privilege mapping found for this group."
4339
4340        # delete the object
4341        url = 'https://'+host+path+'/action/delete'
4342
4343    try:
4344        res = session.post(url, headers=jsonHeader, json = data, verify=False, timeout=baseTimeout)
4345    except(requests.exceptions.Timeout):
4346        return(connectionErrHandler(args.json, "Timeout", None))
4347    except(requests.exceptions.ConnectionError) as err:
4348        return connectionErrHandler(args.json, "ConnectionError", err)
4349    return res.text
4350
4351def deleteAllPrivilegeMapping(host, args, session):
4352    """
4353         Called by the ldap function. Deletes all the privilege mapping and group defined.
4354         @param host: string, the hostname or IP address of the bmc
4355         @param args: contains additional arguments used by the ldap subcommand
4356         @param session: the active session to use
4357         @param args.json: boolean, if this flag is set to true, the output
4358                will be provided in json format for programmatic consumption
4359    """
4360
4361    ldapNameSpaceObjects = listPrivilegeMapping(host, args, session)
4362    ldapNameSpaceObjects = json.loads(ldapNameSpaceObjects)["data"]
4363    path = ''
4364    data = {"data": []}
4365
4366    if (isRedfishSupport):
4367        if (args.serverType is None):
4368            serverType = getLDAPTypeEnabled(host,session)
4369            if (serverType is None):
4370                return("LDAP has not been enabled. Please specify LDAP serverType to proceed further...")
4371
4372    else:
4373        # Remove the config object.
4374        ldapNameSpaceObjects.pop('/xyz/openbmc_project/user/ldap/config', None)
4375
4376    try:
4377        # search for GroupName property and delete if it is available.
4378        for path in ldapNameSpaceObjects.keys():
4379            # delete the object
4380            url = 'https://'+host+path+'/action/Delete'
4381            res = session.post(url, headers=jsonHeader, json = data, verify=False, timeout=baseTimeout)
4382
4383    except(requests.exceptions.Timeout):
4384        return(connectionErrHandler(args.json, "Timeout", None))
4385    except(requests.exceptions.ConnectionError) as err:
4386        return connectionErrHandler(args.json, "ConnectionError", err)
4387    return res.text
4388
4389def viewLDAPConfig(host, args, session):
4390    """
4391         Called by the ldap function. Prints out active LDAP configuration properties
4392
4393         @param host: string, the hostname or IP address of the bmc
4394         @param args: contains additional arguments used by the ldap subcommand
4395                args.json: boolean, if this flag is set to true, the output
4396                will be provided in json format for programmatic consumption
4397         @param session: the active session to use
4398         @return returns LDAP's configured properties.
4399    """
4400
4401    try:
4402        if (isRedfishSupport):
4403
4404            url = "https://"+host+"/xyz/openbmc_project/user/ldap/"
4405
4406            serverTypeEnabled = getLDAPTypeEnabled(host,session)
4407
4408            if (serverTypeEnabled is not None):
4409                data = {"data": []}
4410                res = session.get(url + serverTypeMap[serverTypeEnabled], headers=jsonHeader, json=data, verify=False, timeout=baseTimeout)
4411            else:
4412                return("LDAP server has not been enabled...")
4413
4414        else :
4415            url = "https://"+host+"/xyz/openbmc_project/user/ldap/config"
4416            res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
4417
4418    except(requests.exceptions.Timeout):
4419        return(connectionErrHandler(args.json, "Timeout", None))
4420    except(requests.exceptions.ConnectionError) as err:
4421        return connectionErrHandler(args.json, "ConnectionError", err)
4422    if res.status_code == 404:
4423        return "LDAP server config has not been created"
4424    return res.text
4425
4426def str2bool(v):
4427    if v.lower() in ('yes', 'true', 't', 'y', '1'):
4428        return True
4429    elif v.lower() in ('no', 'false', 'f', 'n', '0'):
4430        return False
4431    else:
4432        raise argparse.ArgumentTypeError('Boolean value expected.')
4433
4434def localUsers(host, args, session):
4435    """
4436        Enables and disables local BMC users.
4437
4438        @param host: string, the hostname or IP address of the bmc
4439        @param args: contains additional arguments used by the logging sub command
4440        @param session: the active session to use
4441    """
4442
4443    url="https://{hostname}/xyz/openbmc_project/user/enumerate".format(hostname=host)
4444    try:
4445        res = session.get(url, headers=jsonHeader, verify=False, timeout=baseTimeout)
4446    except(requests.exceptions.Timeout):
4447        return(connectionErrHandler(args.json, "Timeout", None))
4448    usersDict = json.loads(res.text)
4449
4450    if not usersDict['data']:
4451        return "No users found"
4452
4453    output = ""
4454    for user in usersDict['data']:
4455
4456        # Skip LDAP and another non-local users
4457        if 'UserEnabled' not in usersDict['data'][user]:
4458            continue
4459
4460        name = user.split('/')[-1]
4461        url = "https://{hostname}{user}/attr/UserEnabled".format(hostname=host, user=user)
4462
4463        if args.local_users == "queryenabled":
4464            try:
4465                res = session.get(url, headers=jsonHeader,verify=False, timeout=baseTimeout)
4466            except(requests.exceptions.Timeout):
4467                return(connectionErrHandler(args.json, "Timeout", None))
4468
4469            result = json.loads(res.text)
4470            output += ("User: {name}  Enabled: {result}\n").format(name=name, result=result['data'])
4471
4472        elif args.local_users in ["enableall", "disableall"]:
4473            action = ""
4474            if args.local_users == "enableall":
4475                data = '{"data": true}'
4476                action = "Enabling"
4477            else:
4478                data = '{"data": false}'
4479                action = "Disabling"
4480
4481            output += "{action} {name}\n".format(action=action, name=name)
4482
4483            try:
4484                resp = session.put(url, headers=jsonHeader, data=data, verify=False, timeout=baseTimeout)
4485            except(requests.exceptions.Timeout):
4486                return connectionErrHandler(args.json, "Timeout", None)
4487            except(requests.exceptions.ConnectionError) as err:
4488                return connectionErrHandler(args.json, "ConnectionError", err)
4489        else:
4490            return "Invalid local users argument"
4491
4492    return output
4493
4494def setPassword(host, args, session):
4495    """
4496         Set local user password
4497         @param host: string, the hostname or IP address of the bmc
4498         @param args: contains additional arguments used by the logging sub
4499                command
4500         @param session: the active session to use
4501         @param args.json: boolean, if this flag is set to true, the output
4502                will be provided in json format for programmatic consumption
4503         @return: Session object
4504    """
4505    try:
4506        if(isRedfishSupport):
4507            url = "https://" + host + "/redfish/v1/AccountService/Accounts/"+ \
4508                  args.user
4509            data = {"Password":args.password}
4510            res = session.patch(url, headers=jsonHeader, json=data,
4511                                verify=False, timeout=baseTimeout)
4512        else:
4513            url = "https://" + host + "/xyz/openbmc_project/user/" + args.user + \
4514                "/action/SetPassword"
4515            res = session.post(url, headers=jsonHeader,
4516                           json={"data": [args.password]}, verify=False,
4517                           timeout=baseTimeout)
4518    except(requests.exceptions.Timeout):
4519        return(connectionErrHandler(args.json, "Timeout", None))
4520    except(requests.exceptions.ConnectionError) as err:
4521        return connectionErrHandler(args.json, "ConnectionError", err)
4522    except(requests.exceptions.RequestException) as err:
4523        return connectionErrHandler(args.json, "RequestException", err)
4524    return res.status_code
4525
4526def getThermalZones(host, args, session):
4527    """
4528        Get the available thermal control zones
4529        @param host: string, the hostname or IP address of the bmc
4530        @param args: contains additional arguments used to get the thermal
4531               control zones
4532        @param session: the active session to use
4533        @return: Session object
4534    """
4535    url = "https://" + host + "/xyz/openbmc_project/control/thermal/enumerate"
4536
4537    try:
4538        res = session.get(url, headers=jsonHeader, verify=False, timeout=30)
4539    except(requests.exceptions.Timeout):
4540        return(connectionErrHandler(args.json, "Timeout", None))
4541    except(requests.exceptions.ConnectionError) as err:
4542        return connectionErrHandler(args.json, "ConnectionError", err)
4543    except(requests.exceptions.RequestException) as err:
4544        return connectionErrHandler(args.json, "RequestException", err)
4545
4546    if (res.status_code == 404):
4547        return "No thermal control zones found"
4548
4549    zonesDict = json.loads(res.text)
4550    if not zonesDict['data']:
4551        return "No thermal control zones found"
4552    for zone in zonesDict['data']:
4553        z = ",".join(str(zone.split('/')[-1]) for zone in zonesDict['data'])
4554
4555    return "Zones: [ " + z + " ]"
4556
4557
4558def getThermalMode(host, args, session):
4559    """
4560        Get thermal control mode
4561        @param host: string, the hostname or IP address of the bmc
4562        @param args: contains additional arguments used to get the thermal
4563               control mode
4564        @param session: the active session to use
4565        @param args.zone: the zone to get the mode on
4566        @return: Session object
4567    """
4568    url = "https://" + host + "/xyz/openbmc_project/control/thermal/" + \
4569        args.zone
4570
4571    try:
4572        res = session.get(url, headers=jsonHeader, verify=False, timeout=30)
4573    except(requests.exceptions.Timeout):
4574        return(connectionErrHandler(args.json, "Timeout", None))
4575    except(requests.exceptions.ConnectionError) as err:
4576        return connectionErrHandler(args.json, "ConnectionError", err)
4577    except(requests.exceptions.RequestException) as err:
4578        return connectionErrHandler(args.json, "RequestException", err)
4579
4580    if (res.status_code == 404):
4581        return "Thermal control zone(" + args.zone + ") not found"
4582
4583    propsDict = json.loads(res.text)
4584    if not propsDict['data']:
4585        return "No thermal control properties found on zone(" + args.zone + ")"
4586    curMode = "Current"
4587    supModes = "Supported"
4588    result = "\n"
4589    for prop in propsDict['data']:
4590        if (prop.casefold() == curMode.casefold()):
4591            result += curMode + " Mode: " + propsDict['data'][curMode] + "\n"
4592        if (prop.casefold() == supModes.casefold()):
4593            s = ", ".join(str(sup) for sup in propsDict['data'][supModes])
4594            result += supModes + " Modes: [ " + s + " ]\n"
4595
4596    return result
4597
4598def setThermalMode(host, args, session):
4599    """
4600        Set thermal control mode
4601        @param host: string, the hostname or IP address of the bmc
4602        @param args: contains additional arguments used for setting the thermal
4603               control mode
4604        @param session: the active session to use
4605        @param args.zone: the zone to set the mode on
4606        @param args.mode: the mode to enable
4607        @return: Session object
4608    """
4609    url = "https://" + host + "/xyz/openbmc_project/control/thermal/" + \
4610        args.zone + "/attr/Current"
4611
4612    # Check args.mode against supported modes using `getThermalMode` output
4613    modes = getThermalMode(host, args, session)
4614    modes = os.linesep.join([m for m in modes.splitlines() if m])
4615    modes = modes.replace("\n", ";").strip()
4616    modesDict = dict(m.split(': ') for m in modes.split(';'))
4617    sModes = ''.join(s for s in modesDict['Supported Modes'] if s not in '[ ]')
4618    if args.mode.casefold() not in \
4619            (m.casefold() for m in sModes.split(',')) or not args.mode:
4620        result = ("Unsupported mode('" + args.mode + "') given, " +
4621                  "select a supported mode: \n" +
4622                  getThermalMode(host, args, session))
4623        return result
4624
4625    data = '{"data":"' + args.mode + '"}'
4626    try:
4627        res = session.get(url, headers=jsonHeader, verify=False, timeout=30)
4628    except(requests.exceptions.Timeout):
4629        return(connectionErrHandler(args.json, "Timeout", None))
4630    except(requests.exceptions.ConnectionError) as err:
4631        return connectionErrHandler(args.json, "ConnectionError", err)
4632    except(requests.exceptions.RequestException) as err:
4633        return connectionErrHandler(args.json, "RequestException", err)
4634
4635    if (data and res.status_code != 404):
4636        try:
4637            res = session.put(url, headers=jsonHeader,
4638                              data=data, verify=False,
4639                              timeout=30)
4640        except(requests.exceptions.Timeout):
4641            return(connectionErrHandler(args.json, "Timeout", None))
4642        except(requests.exceptions.ConnectionError) as err:
4643            return connectionErrHandler(args.json, "ConnectionError", err)
4644        except(requests.exceptions.RequestException) as err:
4645            return connectionErrHandler(args.json, "RequestException", err)
4646
4647        if res.status_code == 403:
4648            return "The specified thermal control zone(" + args.zone + ")" + \
4649                " does not exist"
4650
4651        return res.text
4652    else:
4653        return "Setting thermal control mode(" + args.mode + ")" + \
4654            " not supported or operation not available"
4655
4656
4657def createCommandParser():
4658    """
4659         creates the parser for the command line along with help for each command and subcommand
4660
4661         @return: returns the parser for the command line
4662    """
4663    parser = argparse.ArgumentParser(description='Process arguments')
4664    parser.add_argument("-H", "--host", help='A hostname or IP for the BMC')
4665    parser.add_argument("-U", "--user", help='The username to login with')
4666    group = parser.add_mutually_exclusive_group()
4667    group.add_argument("-A", "--askpw", action='store_true', help='prompt for password')
4668    group.add_argument("-P", "--PW", help='Provide the password in-line')
4669    group.add_argument("-E", "--PWenvvar", action='store_true', help='Get password from envvar OPENBMCTOOL_PASSWORD')
4670    parser.add_argument('-j', '--json', action='store_true', help='output json data only')
4671    parser.add_argument('-t', '--policyTableLoc', help='The location of the policy table to parse alerts')
4672    parser.add_argument('-c', '--CerFormat', action='store_true', help=argparse.SUPPRESS)
4673    parser.add_argument('-T', '--procTime', action='store_true', help= argparse.SUPPRESS)
4674    parser.add_argument('-V', '--version', action='store_true', help='Display the version number of the openbmctool')
4675    subparsers = parser.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
4676
4677    #fru command
4678    parser_inv = subparsers.add_parser("fru", help='Work with platform inventory')
4679    inv_subparser = parser_inv.add_subparsers(title='subcommands', description='valid inventory actions', help="valid inventory actions", dest='command')
4680    inv_subparser.required = True
4681    #fru print
4682    inv_print = inv_subparser.add_parser("print", help="prints out a list of all FRUs")
4683    inv_print.set_defaults(func=fruPrint)
4684    #fru list [0....n]
4685    inv_list = inv_subparser.add_parser("list", help="print out details on selected FRUs. Specifying no items will list the entire inventory")
4686    inv_list.add_argument('items', nargs='?', help="print out details on selected FRUs. Specifying no items will list the entire inventory")
4687    inv_list.set_defaults(func=fruList)
4688    #fru status
4689    inv_status = inv_subparser.add_parser("status", help="prints out the status of all FRUs")
4690    inv_status.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
4691    inv_status.set_defaults(func=fruStatus)
4692
4693    #sensors command
4694    parser_sens = subparsers.add_parser("sensors", help="Work with platform sensors")
4695    sens_subparser=parser_sens.add_subparsers(title='subcommands', description='valid sensor actions', help='valid sensor actions', dest='command')
4696    sens_subparser.required = True
4697    #sensor print
4698    sens_print= sens_subparser.add_parser('print', help="prints out a list of all Sensors.")
4699    sens_print.set_defaults(func=sensor)
4700    #sensor list[0...n]
4701    sens_list=sens_subparser.add_parser("list", help="Lists all Sensors in the platform. Specify a sensor for full details. ")
4702    sens_list.add_argument("sensNum", nargs='?', help="The Sensor number to get full details on" )
4703    sens_list.set_defaults(func=sensor)
4704
4705    #thermal control commands
4706    parser_therm = subparsers.add_parser("thermal", help="Work with thermal control parameters")
4707    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')
4708    #thermal control zones
4709    parser_thermZones = therm_subparser.add_parser("zones", help="Get a list of available thermal control zones")
4710    parser_thermZones.set_defaults(func=getThermalZones)
4711    #thermal control modes
4712    parser_thermMode = therm_subparser.add_parser("modes", help="Work with thermal control modes")
4713    thermMode_sub = parser_thermMode.add_subparsers(title='subactions', description='Work with thermal control modes', help="Work with thermal control modes")
4714    #get thermal control mode
4715    parser_getThermMode = thermMode_sub.add_parser("get", help="Get current and supported thermal control modes")
4716    parser_getThermMode.add_argument('-z', '--zone', required=True, help='Thermal zone to work with')
4717    parser_getThermMode.set_defaults(func=getThermalMode)
4718    #set thermal control mode
4719    parser_setThermMode = thermMode_sub.add_parser("set", help="Set the thermal control mode")
4720    parser_setThermMode.add_argument('-z', '--zone', required=True, help='Thermal zone to work with')
4721    parser_setThermMode.add_argument('-m', '--mode', required=True, help='The supported thermal control mode')
4722    parser_setThermMode.set_defaults(func=setThermalMode)
4723
4724    #sel command
4725    parser_sel = subparsers.add_parser("sel", help="Work with platform alerts")
4726    sel_subparser = parser_sel.add_subparsers(title='subcommands', description='valid SEL actions', help = 'valid SEL actions', dest='command')
4727    sel_subparser.required = True
4728    #sel print
4729    sel_print = sel_subparser.add_parser("print", help="prints out a list of all sels in a condensed list")
4730    sel_print.add_argument('-d', '--devdebug', action='store_true', help=argparse.SUPPRESS)
4731    sel_print.add_argument('-v', '--verbose', action='store_true', help="Changes the output to being very verbose")
4732    sel_print.add_argument('-f', '--fileloc', help='Parse a file instead of the BMC output')
4733    sel_print.set_defaults(func=selPrint)
4734
4735    #sel list
4736    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")
4737    sel_list.add_argument("selNum", nargs='?', type=int, help="The SEL entry to get details on")
4738    sel_list.set_defaults(func=selList)
4739
4740    sel_get = sel_subparser.add_parser("get", help="Gets the verbose details of a specified SEL entry")
4741    sel_get.add_argument('selNum', type=int, help="the number of the SEL entry to get")
4742    sel_get.set_defaults(func=selList)
4743
4744    sel_clear = sel_subparser.add_parser("clear", help="Clears all entries from the SEL")
4745    sel_clear.set_defaults(func=selClear)
4746
4747    sel_setResolved = sel_subparser.add_parser("resolve", help="Sets the sel entry to resolved")
4748    sel_setResolved.add_argument('-n', '--selNum', type=int, help="the number of the SEL entry to resolve")
4749    sel_ResolveAll_sub = sel_setResolved.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
4750    sel_ResolveAll = sel_ResolveAll_sub.add_parser('all', help='Resolve all SEL entries')
4751    sel_ResolveAll.set_defaults(func=selResolveAll)
4752    sel_setResolved.set_defaults(func=selSetResolved)
4753
4754    parser_chassis = subparsers.add_parser("chassis", help="Work with chassis power and status")
4755    chas_sub = parser_chassis.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
4756
4757    parser_chassis.add_argument('status', action='store_true', help='Returns the current status of the platform')
4758    parser_chassis.set_defaults(func=chassis)
4759
4760    parser_chasPower = chas_sub.add_parser("power", help="Turn the chassis on or off, check the power state")
4761    parser_chasPower.add_argument('powcmd',  choices=['on','softoff', 'hardoff', 'status'], help='The value for the power command. on, off, or status')
4762    parser_chasPower.set_defaults(func=chassisPower)
4763
4764    #control the chassis identify led
4765    parser_chasIdent = chas_sub.add_parser("identify", help="Control the chassis identify led")
4766    parser_chasIdent.add_argument('identcmd', choices=['on', 'off', 'status'], help='The control option for the led: on, off, blink, status')
4767    parser_chasIdent.set_defaults(func=chassisIdent)
4768
4769    #collect service data
4770    parser_servData = subparsers.add_parser("collect_service_data", help="Collect all bmc data needed for service")
4771    parser_servData.add_argument('-d', '--devdebug', action='store_true', help=argparse.SUPPRESS)
4772    parser_servData.set_defaults(func=collectServiceData)
4773
4774    #system quick health check
4775    parser_healthChk = subparsers.add_parser("health_check", help="Work with platform sensors")
4776    parser_healthChk.set_defaults(func=healthCheck)
4777
4778    #work with dumps
4779    parser_bmcdump = subparsers.add_parser("dump", help="Work with dumps")
4780    parser_bmcdump.add_argument("-t", "--dumpType", default='bmc', choices=['bmc','SystemDump'],help="Type of dump")
4781    bmcDump_sub = parser_bmcdump.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
4782    bmcDump_sub.required = True
4783    dump_Create = bmcDump_sub.add_parser('create', help="Create a dump of given type")
4784    dump_Create.set_defaults(func=dumpCreate)
4785
4786    dump_list = bmcDump_sub.add_parser('list', help="list all dumps")
4787    dump_list.set_defaults(func=dumpList)
4788
4789    parserdumpdelete = bmcDump_sub.add_parser('delete', help="Delete dump")
4790    parserdumpdelete.add_argument("-n", "--dumpNum", nargs='*', type=int, help="The Dump entry to delete")
4791    parserdumpdelete.set_defaults(func=dumpDelete)
4792
4793    bmcDumpDelsub = parserdumpdelete.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
4794    deleteAllDumps = bmcDumpDelsub.add_parser('all', help='Delete all dumps')
4795    deleteAllDumps.set_defaults(func=dumpDeleteAll)
4796
4797    parser_dumpretrieve = bmcDump_sub.add_parser('retrieve', help='Retrieve a dump file')
4798    parser_dumpretrieve.add_argument("-n,", "--dumpNum", help="The Dump entry to retrieve")
4799    parser_dumpretrieve.add_argument("-s", "--dumpSaveLoc", help="The location to save the bmc dump file or file path for system dump")
4800    parser_dumpretrieve.set_defaults(func=dumpRetrieve)
4801
4802    #bmc command for reseting the bmc
4803    parser_bmc = subparsers.add_parser('bmc', help="Work with the bmc")
4804    bmc_sub = parser_bmc.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
4805    parser_BMCReset = bmc_sub.add_parser('reset', help='Reset the bmc' )
4806    parser_BMCReset.add_argument('type', choices=['warm','cold'], help="Warm: Reboot the BMC, Cold: CLEAR config and reboot bmc")
4807    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.")
4808    parser_bmc.set_defaults(func=bmc)
4809
4810    #add alias to the bmc command
4811    parser_mc = subparsers.add_parser('mc', help="Work with the management controller")
4812    mc_sub = parser_mc.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
4813    parser_MCReset = mc_sub.add_parser('reset', help='Reset the bmc' )
4814    parser_MCReset.add_argument('type', choices=['warm','cold'], help="Reboot the BMC")
4815    #parser_MCReset.add_argument('cold', action='store_true', help="Reboot the BMC and CLEAR the configuration")
4816    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.")
4817    parser_MCReset.set_defaults(func=bmcReset)
4818    parser_mc.set_defaults(func=bmc)
4819
4820    #gard clear
4821    parser_gc = subparsers.add_parser("gardclear", help="Used to clear gard records")
4822    parser_gc.set_defaults(func=gardClear)
4823
4824    #firmware_flash
4825    parser_fw = subparsers.add_parser("firmware", help="Work with the system firmware")
4826    fwflash_subproc = parser_fw.add_subparsers(title='subcommands', description='valid firmware commands', help='sub-command help', dest='command')
4827    fwflash_subproc.required = True
4828
4829    fwflash = fwflash_subproc.add_parser('flash', help="Flash the system firmware")
4830    fwflash.add_argument('type', choices=['bmc', 'pnor'], help="image type to flash")
4831    fwflash.add_argument('-f', '--fileloc', required=True, help="The absolute path to the firmware image")
4832    fwflash.set_defaults(func=fwFlash)
4833
4834    fwActivate = fwflash_subproc.add_parser('activate', help="Activate existing image on the bmc")
4835    fwActivate.add_argument('imageID', help="The image ID to activate from the firmware list. Ex: 63c95399")
4836    fwActivate.set_defaults(func=activateFWImage)
4837
4838    fwActivateStatus = fwflash_subproc.add_parser('activation_status', help="Check Status of activations")
4839    fwActivateStatus.set_defaults(func=activateStatus)
4840
4841    fwList = fwflash_subproc.add_parser('list', help="List all of the installed firmware")
4842    fwList.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
4843    fwList.set_defaults(func=firmwareList)
4844
4845    fwprint = fwflash_subproc.add_parser('print', help="List all of the installed firmware")
4846    fwprint.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
4847    fwprint.set_defaults(func=firmwareList)
4848
4849    fwDelete = fwflash_subproc.add_parser('delete', help="Delete an existing firmware version")
4850    fwDelete.add_argument('versionID', help="The version ID to delete from the firmware list. Ex: 63c95399")
4851    fwDelete.set_defaults(func=deleteFWVersion)
4852
4853    #logging
4854    parser_logging = subparsers.add_parser("logging", help="logging controls")
4855    logging_sub = parser_logging.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
4856
4857    #turn rest api logging on/off
4858    parser_rest_logging = logging_sub.add_parser("rest_api", help="turn rest api logging on/off")
4859    parser_rest_logging.add_argument('rest_logging', choices=['on', 'off'], help='The control option for rest logging: on, off')
4860    parser_rest_logging.set_defaults(func=restLogging)
4861
4862    #remote logging
4863    parser_remote_logging = logging_sub.add_parser("remote_logging", help="Remote logging (rsyslog) commands")
4864    parser_remote_logging.add_argument('remote_logging', choices=['view', 'disable'], help='Remote logging (rsyslog) commands')
4865    parser_remote_logging.set_defaults(func=remoteLogging)
4866
4867    #configure remote logging
4868    parser_remote_logging_config = logging_sub.add_parser("remote_logging_config", help="Configure remote logging (rsyslog)")
4869    parser_remote_logging_config.add_argument("-a", "--address", required=True, help="Set IP address of rsyslog server")
4870    parser_remote_logging_config.add_argument("-p", "--port", required=True, type=int, help="Set Port of rsyslog server")
4871    parser_remote_logging_config.set_defaults(func=remoteLoggingConfig)
4872
4873    #certificate management
4874    parser_cert = subparsers.add_parser("certificate", help="Certificate management")
4875    certMgmt_subproc = parser_cert.add_subparsers(title='subcommands', description='valid certificate commands', help='sub-command help', dest='command')
4876
4877    certUpdate = certMgmt_subproc.add_parser('update', help="Update the certificate")
4878    certUpdate.add_argument('type', choices=['server', 'client', 'authority'], help="certificate type to update")
4879    certUpdate.add_argument('service', choices=['https', 'ldap'], help="Service to update")
4880    certUpdate.add_argument('-f', '--fileloc', required=True, help="The absolute path to the certificate file")
4881    certUpdate.set_defaults(func=certificateUpdate)
4882
4883    certDelete = certMgmt_subproc.add_parser('delete', help="Delete the certificate")
4884    certDelete.add_argument('type', choices=['server', 'client', 'authority'], help="certificate type to delete")
4885    certDelete.add_argument('service', choices=['https', 'ldap'], help="Service to delete the certificate")
4886    certDelete.set_defaults(func=certificateDelete)
4887
4888    certReplace = certMgmt_subproc.add_parser('replace',
4889        help="Replace the certificate")
4890    certReplace.add_argument('type', choices=['server', 'client', 'authority'],
4891        help="certificate type to replace")
4892    certReplace.add_argument('service', choices=['https', 'ldap'],
4893        help="Service to replace the certificate")
4894    certReplace.add_argument('-f', '--fileloc', required=True,
4895        help="The absolute path to the certificate file")
4896    certReplace.set_defaults(func=certificateReplace)
4897
4898    certDisplay = certMgmt_subproc.add_parser('display',
4899        help="Print the certificate")
4900    certDisplay.add_argument('type', choices=['server', 'client', 'authority'],
4901        help="certificate type to display")
4902    certDisplay.set_defaults(func=certificateDisplay)
4903
4904    certList = certMgmt_subproc.add_parser('list',
4905        help="Certificate list")
4906    certList.set_defaults(func=certificateList)
4907
4908    certGenerateCSR = certMgmt_subproc.add_parser('generatecsr', help="Generate CSR")
4909    certGenerateCSR.add_argument('type', choices=['server', 'client', 'authority'],
4910        help="Generate CSR")
4911    certGenerateCSR.add_argument('city',
4912        help="The city or locality of the organization making the request")
4913    certGenerateCSR.add_argument('commonName',
4914        help="The fully qualified domain name of the component that is being secured.")
4915    certGenerateCSR.add_argument('country',
4916        help="The country of the organization making the request")
4917    certGenerateCSR.add_argument('organization',
4918        help="The name of the organization making the request.")
4919    certGenerateCSR.add_argument('organizationUnit',
4920        help="The name of the unit or division of the organization making the request.")
4921    certGenerateCSR.add_argument('state',
4922        help="The state, province, or region of the organization making the request.")
4923    certGenerateCSR.add_argument('keyPairAlgorithm',  choices=['RSA', 'EC'],
4924        help="The type of key pair for use with signing algorithms.")
4925    certGenerateCSR.add_argument('keyCurveId',
4926        help="The curve ID to be used with the key, if needed based on the value of the 'KeyPairAlgorithm' parameter.")
4927    certGenerateCSR.add_argument('contactPerson',
4928        help="The name of the user making the request")
4929    certGenerateCSR.add_argument('email',
4930        help="The email address of the contact within the organization")
4931    certGenerateCSR.add_argument('alternativeNames',
4932        help="Additional hostnames of the component that is being secured")
4933    certGenerateCSR.add_argument('givenname',
4934        help="The given name of the user making the request")
4935    certGenerateCSR.add_argument('surname',
4936        help="The surname of the user making the request")
4937    certGenerateCSR.add_argument('unstructuredname',
4938        help="he unstructured name of the subject")
4939    certGenerateCSR.add_argument('initials',
4940        help="The initials of the user making the request")
4941    certGenerateCSR.set_defaults(func=certificateGenerateCSR)
4942
4943    # local users
4944    parser_users = subparsers.add_parser("local_users", help="Work with local users")
4945    parser_users.add_argument('local_users', choices=['disableall','enableall', 'queryenabled'], help="Disable, enable or query local user accounts")
4946    parser_users.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
4947    parser_users.set_defaults(func=localUsers)
4948
4949    #LDAP
4950    parser_ldap = subparsers.add_parser("ldap", help="LDAP controls")
4951    ldap_sub = parser_ldap.add_subparsers(title='subcommands', description='valid subcommands',help="sub-command help", dest='command')
4952
4953    #configure and enable LDAP
4954    parser_ldap_config = ldap_sub.add_parser("enable", help="Configure and enables the LDAP")
4955    parser_ldap_config.add_argument("-a", "--uri", required=True, help="Set LDAP server URI")
4956    parser_ldap_config.add_argument("-B", "--bindDN", required=True, help="Set the bind DN of the LDAP server")
4957    parser_ldap_config.add_argument("-b", "--baseDN", required=True, help="Set the base DN of the LDAP server")
4958    parser_ldap_config.add_argument("-p", "--bindPassword", required=True, help="Set the bind password of the LDAP server")
4959    parser_ldap_config.add_argument("-S", "--scope", choices=['sub','one', 'base'],
4960            help='Specifies the search scope:subtree, one level or base object.')
4961    parser_ldap_config.add_argument("-t", "--serverType", required=True, choices=['ActiveDirectory','OpenLDAP'],
4962            help='Specifies the configured server is ActiveDirectory(AD) or OpenLdap')
4963    parser_ldap_config.add_argument("-g","--groupAttrName", required=False, default='', help="Group Attribute Name")
4964    parser_ldap_config.add_argument("-u","--userAttrName", required=False, default='', help="User Attribute Name")
4965    parser_ldap_config.set_defaults(func=enableLDAPConfig)
4966
4967    # disable LDAP
4968    parser_disable_ldap = ldap_sub.add_parser("disable", help="disables the LDAP")
4969    parser_disable_ldap.set_defaults(func=disableLDAP)
4970    # view-config
4971    parser_ldap_config = \
4972    ldap_sub.add_parser("view-config", help="prints out a list of all \
4973                        LDAPS's configured properties")
4974    parser_ldap_config.set_defaults(func=viewLDAPConfig)
4975
4976    #create group privilege mapping
4977    parser_ldap_mapper = ldap_sub.add_parser("privilege-mapper", help="LDAP group privilege controls")
4978    parser_ldap_mapper_sub = parser_ldap_mapper.add_subparsers(title='subcommands', description='valid subcommands',
4979            help="sub-command help", dest='command')
4980
4981    parser_ldap_mapper_create = parser_ldap_mapper_sub.add_parser("create", help="Create mapping of ldap group and privilege")
4982    parser_ldap_mapper_create.add_argument("-t", "--serverType", choices=['ActiveDirectory','OpenLDAP'],
4983            help='Specifies the configured server is ActiveDirectory(AD) or OpenLdap')
4984    parser_ldap_mapper_create.add_argument("-g","--groupName",required=True,help="Group Name")
4985    parser_ldap_mapper_create.add_argument("-p","--privilege",choices=['priv-admin','priv-operator','priv-user','priv-callback'],required=True,help="Privilege")
4986    parser_ldap_mapper_create.set_defaults(func=createPrivilegeMapping)
4987
4988    #list group privilege mapping
4989    parser_ldap_mapper_list = parser_ldap_mapper_sub.add_parser("list",help="List privilege mapping")
4990    parser_ldap_mapper_list.add_argument("-t", "--serverType", choices=['ActiveDirectory','OpenLDAP'],
4991            help='Specifies the configured server is ActiveDirectory(AD) or OpenLdap')
4992    parser_ldap_mapper_list.set_defaults(func=listPrivilegeMapping)
4993
4994    #delete group privilege mapping
4995    parser_ldap_mapper_delete = parser_ldap_mapper_sub.add_parser("delete",help="Delete privilege mapping")
4996    parser_ldap_mapper_delete.add_argument("-t", "--serverType", choices=['ActiveDirectory','OpenLDAP'],
4997            help='Specifies the configured server is ActiveDirectory(AD) or OpenLdap')
4998    parser_ldap_mapper_delete.add_argument("-g","--groupName",required=True,help="Group Name")
4999    parser_ldap_mapper_delete.set_defaults(func=deletePrivilegeMapping)
5000
5001    #deleteAll group privilege mapping
5002    parser_ldap_mapper_delete = parser_ldap_mapper_sub.add_parser("purge",help="Delete All privilege mapping")
5003    parser_ldap_mapper_delete.add_argument("-t", "--serverType", choices=['ActiveDirectory','OpenLDAP'],
5004            help='Specifies the configured server is ActiveDirectory(AD) or OpenLdap')
5005    parser_ldap_mapper_delete.set_defaults(func=deleteAllPrivilegeMapping)
5006
5007    # set local user password
5008    parser_set_password = subparsers.add_parser("set_password",
5009        help="Set password of local user")
5010    parser_set_password.add_argument( "-p", "--password", required=True,
5011        help="Password of local user")
5012    parser_set_password.set_defaults(func=setPassword)
5013
5014    # network
5015    parser_nw = subparsers.add_parser("network", help="network controls")
5016    nw_sub = parser_nw.add_subparsers(title='subcommands',
5017                                      description='valid subcommands',
5018                                      help="sub-command help",
5019                                      dest='command')
5020
5021    # enable DHCP
5022    parser_enable_dhcp = nw_sub.add_parser("enableDHCP",
5023                                           help="enables the DHCP on given "
5024                                           "Interface")
5025    parser_enable_dhcp.add_argument("-I", "--Interface", required=True,
5026                                    help="Name of the ethernet interface(it can"
5027                                    "be obtained by the "
5028                                    "command:network view-config)"
5029                                    "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5030    parser_enable_dhcp.set_defaults(func=enableDHCP)
5031
5032    # disable DHCP
5033    parser_disable_dhcp = nw_sub.add_parser("disableDHCP",
5034                                            help="disables the DHCP on given "
5035                                            "Interface")
5036    parser_disable_dhcp.add_argument("-I", "--Interface", required=True,
5037                                     help="Name of the ethernet interface(it can"
5038                                     "be obtained by the "
5039                                     "command:network view-config)"
5040                                     "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5041    parser_disable_dhcp.set_defaults(func=disableDHCP)
5042
5043    # get HostName
5044    parser_gethostname = nw_sub.add_parser("getHostName",
5045                                           help="prints out HostName")
5046    parser_gethostname.set_defaults(func=getHostname)
5047
5048    # set HostName
5049    parser_sethostname = nw_sub.add_parser("setHostName", help="sets HostName")
5050    parser_sethostname.add_argument("-H", "--HostName", required=True,
5051                                    help="A HostName for the BMC")
5052    parser_sethostname.set_defaults(func=setHostname)
5053
5054    # get domainname
5055    parser_getdomainname = nw_sub.add_parser("getDomainName",
5056                                             help="prints out DomainName of "
5057                                             "given Interface")
5058    parser_getdomainname.add_argument("-I", "--Interface", required=True,
5059                                      help="Name of the ethernet interface(it "
5060                                      "can be obtained by the "
5061                                      "command:network view-config)"
5062                                      "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5063    parser_getdomainname.set_defaults(func=getDomainName)
5064
5065    # set domainname
5066    parser_setdomainname = nw_sub.add_parser("setDomainName",
5067                                             help="sets DomainName of given "
5068                                             "Interface")
5069    parser_setdomainname.add_argument("-D", "--DomainName", required=True,
5070                                      help="Ex: DomainName=Domain1,Domain2,...")
5071    parser_setdomainname.add_argument("-I", "--Interface", required=True,
5072                                      help="Name of the ethernet interface(it "
5073                                      "can be obtained by the "
5074                                      "command:network view-config)"
5075                                      "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5076    parser_setdomainname.set_defaults(func=setDomainName)
5077
5078    # get MACAddress
5079    parser_getmacaddress = nw_sub.add_parser("getMACAddress",
5080                                             help="prints out MACAddress the "
5081                                             "given Interface")
5082    parser_getmacaddress.add_argument("-I", "--Interface", required=True,
5083                                      help="Name of the ethernet interface(it "
5084                                      "can be obtained by the "
5085                                      "command:network view-config)"
5086                                      "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5087    parser_getmacaddress.set_defaults(func=getMACAddress)
5088
5089    # set MACAddress
5090    parser_setmacaddress = nw_sub.add_parser("setMACAddress",
5091                                             help="sets MACAddress")
5092    parser_setmacaddress.add_argument("-MA", "--MACAddress", required=True,
5093                                      help="A MACAddress for the given "
5094                                      "Interface")
5095    parser_setmacaddress.add_argument("-I", "--Interface", required=True,
5096                                    help="Name of the ethernet interface(it can"
5097                                    "be obtained by the "
5098                                    "command:network view-config)"
5099                                    "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5100    parser_setmacaddress.set_defaults(func=setMACAddress)
5101
5102    # get DefaultGW
5103    parser_getdefaultgw = nw_sub.add_parser("getDefaultGW",
5104                                            help="prints out DefaultGateway "
5105                                            "the BMC")
5106    parser_getdefaultgw.set_defaults(func=getDefaultGateway)
5107
5108    # set DefaultGW
5109    parser_setdefaultgw = nw_sub.add_parser("setDefaultGW",
5110                                             help="sets DefaultGW")
5111    parser_setdefaultgw.add_argument("-GW", "--DefaultGW", required=True,
5112                                      help="A DefaultGateway for the BMC")
5113    parser_setdefaultgw.set_defaults(func=setDefaultGateway)
5114
5115    # view network Config
5116    parser_ldap_config = nw_sub.add_parser("view-config", help="prints out a "
5117                                           "list of all network's configured "
5118                                           "properties")
5119    parser_ldap_config.set_defaults(func=viewNWConfig)
5120
5121    # get DNS
5122    parser_getDNS = nw_sub.add_parser("getDNS",
5123                                      help="prints out DNS servers on the "
5124                                      "given interface")
5125    parser_getDNS.add_argument("-I", "--Interface", required=True,
5126                               help="Name of the ethernet interface(it can"
5127                               "be obtained by the "
5128                               "command:network view-config)"
5129                               "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5130    parser_getDNS.set_defaults(func=getDNS)
5131
5132    # set DNS
5133    parser_setDNS = nw_sub.add_parser("setDNS",
5134                                      help="sets DNS servers on the given "
5135                                      "interface")
5136    parser_setDNS.add_argument("-d", "--DNSServers", required=True,
5137                               help="Ex: DNSSERVERS=DNS1,DNS2,...")
5138    parser_setDNS.add_argument("-I", "--Interface", required=True,
5139                               help="Name of the ethernet interface(it can"
5140                               "be obtained by the "
5141                               "command:network view-config)"
5142                               "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5143    parser_setDNS.set_defaults(func=setDNS)
5144
5145    # get NTP
5146    parser_getNTP = nw_sub.add_parser("getNTP",
5147                                      help="prints out NTP servers on the "
5148                                      "given interface")
5149    parser_getNTP.add_argument("-I", "--Interface", required=True,
5150                               help="Name of the ethernet interface(it can"
5151                               "be obtained by the "
5152                               "command:network view-config)"
5153                               "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5154    parser_getNTP.set_defaults(func=getNTP)
5155
5156    # set NTP
5157    parser_setNTP = nw_sub.add_parser("setNTP",
5158                                      help="sets NTP servers on the given "
5159                                      "interface")
5160    parser_setNTP.add_argument("-N", "--NTPServers", required=True,
5161                               help="Ex: NTPSERVERS=NTP1,NTP2,...")
5162    parser_setNTP.add_argument("-I", "--Interface", required=True,
5163                               help="Name of the ethernet interface(it can"
5164                               "be obtained by the "
5165                               "command:network view-config)"
5166                               "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5167    parser_setNTP.set_defaults(func=setNTP)
5168
5169    # configure IP
5170    parser_ip_config = nw_sub.add_parser("addIP", help="Sets IP address to"
5171                                         "given interface")
5172    parser_ip_config.add_argument("-a", "--address", required=True,
5173                                  help="IP address of given interface")
5174    parser_ip_config.add_argument("-gw", "--gateway", required=False, default='',
5175                                  help="The gateway for given interface")
5176    parser_ip_config.add_argument("-l", "--prefixLength", required=True,
5177                                  help="The prefixLength of IP address")
5178    parser_ip_config.add_argument("-p", "--type", required=True,
5179                                  choices=['ipv4', 'ipv6'],
5180                                  help="The protocol type of the given"
5181                                  "IP address")
5182    parser_ip_config.add_argument("-I", "--Interface", required=True,
5183                                  help="Name of the ethernet interface(it can"
5184                                  "be obtained by the "
5185                                  "command:network view-config)"
5186                                  "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5187    parser_ip_config.set_defaults(func=addIP)
5188
5189    # getIP
5190    parser_getIP = nw_sub.add_parser("getIP", help="prints out IP address"
5191                                     "of given interface")
5192    parser_getIP.add_argument("-I", "--Interface", required=True,
5193                              help="Name of the ethernet interface(it can"
5194                              "be obtained by the command:network view-config)"
5195                              "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5196    parser_getIP.set_defaults(func=getIP)
5197
5198    # rmIP
5199    parser_rmIP = nw_sub.add_parser("rmIP", help="deletes IP address"
5200                                     "of given interface")
5201    parser_rmIP.add_argument("-a", "--address", required=True,
5202                                  help="IP address to remove form given Interface")
5203    parser_rmIP.add_argument("-I", "--Interface", required=True,
5204                             help="Name of the ethernet interface(it can"
5205                             "be obtained by the command:network view-config)"
5206                             "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5207    parser_rmIP.set_defaults(func=deleteIP)
5208
5209    # add VLAN
5210    parser_create_vlan = nw_sub.add_parser("addVLAN", help="enables VLAN "
5211                                           "on given interface with given "
5212                                           "VLAN Identifier")
5213    parser_create_vlan.add_argument("-I", "--Interface", required=True,
5214                                    choices=['eth0', 'eth1'],
5215                                    help="Name of the ethernet interface")
5216    parser_create_vlan.add_argument("-n", "--Identifier", required=True,
5217                                  help="VLAN Identifier")
5218    parser_create_vlan.set_defaults(func=addVLAN)
5219
5220    # delete VLAN
5221    parser_delete_vlan = nw_sub.add_parser("deleteVLAN", help="disables VLAN "
5222                                           "on given interface with given "
5223                                           "VLAN Identifier")
5224    parser_delete_vlan.add_argument("-I", "--Interface", required=True,
5225                                    help="Name of the ethernet interface(it can"
5226                                    "be obtained by the "
5227                                    "command:network view-config)"
5228                                    "Ex: eth0 or eth1 or VLAN(VLAN=eth0_50 etc)")
5229    parser_delete_vlan.set_defaults(func=deleteVLAN)
5230
5231    # viewDHCPConfig
5232    parser_viewDHCPConfig = nw_sub.add_parser("viewDHCPConfig",
5233                                              help="Shows DHCP configured "
5234                                              "Properties")
5235    parser_viewDHCPConfig.set_defaults(func=viewDHCPConfig)
5236
5237    # configureDHCP
5238    parser_configDHCP = nw_sub.add_parser("configureDHCP",
5239                                          help="Configures/updates DHCP "
5240                                          "Properties")
5241    parser_configDHCP.add_argument("-d", "--DNSEnabled", type=str2bool,
5242                                   required=True, help="Sets DNSEnabled property")
5243    parser_configDHCP.add_argument("-n", "--HostNameEnabled", type=str2bool,
5244                                   required=True,
5245                                   help="Sets HostNameEnabled property")
5246    parser_configDHCP.add_argument("-t", "--NTPEnabled", type=str2bool,
5247                                   required=True,
5248                                   help="Sets NTPEnabled property")
5249    parser_configDHCP.add_argument("-s", "--SendHostNameEnabled", type=str2bool,
5250                                   required=True,
5251                                   help="Sets SendHostNameEnabled property")
5252    parser_configDHCP.set_defaults(func=configureDHCP)
5253
5254    # network factory reset
5255    parser_nw_reset = nw_sub.add_parser("nwReset",
5256                                        help="Resets networks setting to "
5257                                        "factory defaults. "
5258                                        "note:Reset settings will be applied "
5259                                        "after BMC reboot")
5260    parser_nw_reset.set_defaults(func=nwReset)
5261
5262    return parser
5263
5264def main(argv=None):
5265    """
5266         main function for running the command line utility as a sub application
5267    """
5268    global toolVersion
5269    toolVersion = "1.19"
5270    global isRedfishSupport
5271
5272    parser = createCommandParser()
5273    args = parser.parse_args(argv)
5274
5275    totTimeStart = int(round(time.time()*1000))
5276
5277    if(sys.version_info < (3,0)):
5278        urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
5279    if sys.version_info >= (3,0):
5280        requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
5281    if (args.version):
5282        print("Version: "+ toolVersion)
5283        sys.exit(0)
5284    if (hasattr(args, 'fileloc') and args.fileloc is not None and 'print' in args.command):
5285        mysess = None
5286        print(selPrint('N/A', args, mysess))
5287    else:
5288        if(hasattr(args, 'host') and hasattr(args,'user')):
5289            if (args.askpw):
5290                pw = getpass.getpass()
5291            elif(args.PW is not None):
5292                pw = args.PW
5293            elif(args.PWenvvar):
5294                pw = os.environ['OPENBMCTOOL_PASSWORD']
5295            else:
5296                print("You must specify a password")
5297                sys.exit()
5298            logintimeStart = int(round(time.time()*1000))
5299            mysess = login(args.host, args.user, pw, args.json,
5300                           args.command == 'set_password')
5301            if(mysess == None):
5302                print("Login Failed!")
5303                sys.exit()
5304            if(sys.version_info < (3,0)):
5305                if isinstance(mysess, basestring):
5306                    print(mysess)
5307                    sys.exit(1)
5308            elif sys.version_info >= (3,0):
5309                if isinstance(mysess, str):
5310                    print(mysess)
5311                    sys.exit(1)
5312            logintimeStop = int(round(time.time()*1000))
5313            isRedfishSupport = redfishSupportPresent(args.host,mysess)
5314            commandTimeStart = int(round(time.time()*1000))
5315            output = args.func(args.host, args, mysess)
5316            commandTimeStop = int(round(time.time()*1000))
5317            if isinstance(output, dict):
5318                print(json.dumps(output, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False))
5319            else:
5320                print(output)
5321            if (mysess is not None):
5322                logout(args.host, args.user, pw, mysess, args.json)
5323            if(args.procTime):
5324                print("Total time: " + str(int(round(time.time()*1000))- totTimeStart))
5325                print("loginTime: " + str(logintimeStop - logintimeStart))
5326                print("command Time: " + str(commandTimeStop - commandTimeStart))
5327        else:
5328            print("usage:\n"
5329                  "  OPENBMCTOOL_PASSWORD=secret  # if using -E\n"
5330                  "  openbmctool.py [-h] -H HOST -U USER {-A | -P PW | -E} [-j]\n" +
5331                      "\t[-t POLICYTABLELOC] [-V]\n" +
5332                      "\t{fru,sensors,sel,chassis,collect_service_data, \
5333                          health_check,dump,bmc,mc,gardclear,firmware,logging}\n" +
5334                      "\t...\n" +
5335                      "openbmctool.py: error: the following arguments are required: -H/--host, -U/--user")
5336            sys.exit()
5337
5338if __name__ == '__main__':
5339    """
5340         main function when called from the command line
5341
5342    """
5343    import sys
5344
5345    isTTY = sys.stdout.isatty()
5346    assert sys.version_info >= (2,7)
5347    main()
5348