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