
# PLEASE READ DISCLAIMER
#
#    This class demonstrates sample IxNetwork REST API usage for
#    demo and reference purpose only.
#    It is subject to change for updates without warning.
#
# REQUIREMENTS
#    - Python 2.7 (Supports Python 2 and 3)
#    - Python modules: requests
#

from __future__ import absolute_import, print_function, division
import os, re, sys, requests, json, time, subprocess, traceback, time, datetime, platform

class IxNetRestApiException(Exception):
    def __init__(self, msg=None):
        if platform.python_version().startswith('3'):
            super().__init__(msg)

        if platform.python_version().startswith('2'):
            super(IxNetRestApiException, self). __init__(msg)

        if Connect.robotStdout is not None:
            Connect.robotStdout.log_to_console(msg)

        showErrorMsg = '\nIxNetRestApiException error: {0}\n\n'.format(msg)
        print(showErrorMsg)
        if Connect.enableDebugLogFile:
            with open(Connect.debugLogFile, 'a') as restLogFile:
                restLogFile.write(showErrorMsg)

class Connect:
    # For IxNetRestApiException
    debugLogFile = None
    enableDebugLogFile = False
    robotStdout = None

    def __init__(self, apiServerIp=None, serverIpPort=None, serverOs='windows', connectToLinuxChassisIp=None, manageSessionMode=False,
                 webQuickTest=False, username=None, password='admin', licenseServerIp=None, licenseMode=None, licenseTier=None,
                 deleteSessionAfterTest=True, verifySslCert=False, includeDebugTraceback=True, sessionId=None, httpsSecured=False,
                 apiKey=None, generateLogFile=True, robotFrameworkStdout=False,proxies=None, trustEnv=True):
        """
        Description
           Initializing default parameters and making a connection to the API server

        Notes
            Starting IxNetwork 8.50, https will be enforced even for Windows connection.
            If you still want to use http, you need to add -restInsecure to the IxNetwork.exe appliaction under "target".

        Examples
            Right click on "IxNetwork API server", select properties and under target
            ixnetwork.exe -restInsecure -restPort 11009 -restOnAllInterfaces -tclPort 8009

        Parameters
           apiServerIp: (str): The API server IP address.
           serverIpPort: (str): The API server IP address socket port.
           serverOs: (str): windows|windowsConnectionMgr|linux
           connectToLinuxChassis: (str): Connect to a Linux OS chassis IP address.
           webQuickTest: (bool): True: Using IxNetwork Web Quick Test. Otherwise, using IxNetwork.
           includeDebugTraceback: (bool):
                                   True: Traceback messsages are included in raised exceptions.
                                   False: No traceback.  Less verbose for debugging.
           username: (str): The login username. For Linux API server only.
           password: (str): The login password. For Linux API server only.
           licenseServerIp: (str): The license server IP address.
           licenseMode: (str): subscription | perpetual | mixed
           licenseTier: (str): tier1 | tier2 | tier3
           deleteSessionAfterTest: (bool): True: Delete the session.
                                           False: Don't delete the session.
           verifySslCert: (str): Optional: Include your SSL certificate for added security.
           httpsSecured: (bool): This parameter is only used by Connection Mgr when user wants to connect to
                                 an existing session.
                                 True = IxNetwork ReST API server is using HTTPS.
                                 This parameter must also include sessionId and serverIpPort=<the ssl port number>

           serverOs: (str): Defaults to windows. windows|windowsConnectionMgr|linux.
           includeDebugTraceback: (bool): True: Include tracebacks in raised exceptions.
           sessionId: (str): The session ID on the Linux API server or Windows Connection Mgr to connect to.
           apiKey: (str): The Linux API server user account API-Key to use for the sessionId connection.
           generateLogFile: True|False|<log file name>.  If you want to generate a log file, provide
                                the log file name.
                                True = Then the log file default name is ixNetRestApi_debugLog.txt
                                False = Disable generating a log file.
                                <log file name> = The full path + file name of the log file to create.
           robotFrameworkStdout: (bool):  True = Print to stdout.
           httpInsecure: (bool): This parameter is only for Windows connections.
                                     True: Using http.  False: Using https.
                                     Starting 8.50: IxNetwork defaults to use https.
                                     If you are using versions prior to 8.50, it needs to be a http connection.
                                     In this case, set httpInsecure=True.

        Notes
            Class attributes
               self._session: The requests initial Session().
               self.serverOs: windows|windowsConnectionMgr|linux

               self.httpHeader: http://{apiServerIp}:{port}
               self.sessionId : http://{apiServerIp}:{port}/api/v1/sessions/{id}
               self.sessionUrl: http://{apiServerIp}:{port}/api/v1/sessions/{id}/ixnetwork
               self.headlessSessionId: /api/v1/sessions/{id}
               self.apiSessionId:      /api/v1/sessions/{id}/ixnetwork

               self.jsonHeader: The default header: {"content-type": "application/json"}
               self.apiKey: For Linux API server only. Automatically provided by the server when login
                            successfully authenticated.
                            You could also provide an API-Key to connect to an existing session.
                            Get the API-Key from the Linux API server user account.

        Examples:
           Steps to connect to Linux API server steps:
               1> POST: https://{apiServerIp}/api/v1/auth/session
                  DATA: {"username": "admin", "password": "admin"}
                  HEADERS: {'content-type': 'application/json'}

               2> POST: https://{apiServerIp:{port}/api/v1/sessions
                  DATA: {"applicationType": "ixnrest"}
                  HEADERS: {'content-type': 'application/json', 'x-api-key': 'd9f4da46f3c142f48dddfa464788hgee'}

               3> POST: https://{apiServerIp}:443/api/v1/sessions/4/operations/start
                  DATA: {}
                  HEADERS: {'content-type': 'application/json', 'x-api-key': 'd9f4da46f3c142f48dddfa464788hgee'}

               sessionId = https://{apiServerIp}:443/api/v1/sessions/{id}

           Steps to connect to Linux Web Quick Test:
               1> POST: https://{apiServerIp}:443/api/v1/auth/session
                  DATA: {"username": "admin", "password": "admin"}
                  HEADERS: {'content-type': 'application/json'}

               2> POST: https://{apiServeIp}:443/api/v1/sessions
                  DATA: {'applicationType': 'ixnetwork'}

               3> POST: https://{apiServerIp}:443/api/v1/sessions/2/operations/start
                  DATA: {'applicationType': 'ixnetwork'}

            sessionId = https://{apiServerIp}/ixnetworkweb/api/v1/sessions/{id}

           Notes
              To connect to an existing configuration.
                 Windows: Nothing special to include. The session ID is always "1".
                 Linux API server: Include the api-key and sessionId that you want to connect to.
                 Windows Connection Manager: Include just the sessionId: For example: 8021.
        """
        from requests.exceptions import ConnectionError
        from requests.packages.urllib3.connection import HTTPConnection

        # Disable SSL warnings
        requests.packages.urllib3.disable_warnings()

        # Disable non http connections.
        from requests.packages.urllib3.exceptions import InsecureRequestWarning
        requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

        self._session = requests.Session()

        self.httpScheme = 'http' ;# This will change to https in createWindowsSession and connectToLinuxApiServer
        if httpsSecured:
            # For Windows Connection Mgr only.
            # When creating a new session, there is no way to know by doing a POST for a new session to
            # understand if it's for http or https.  You must enter https for the POST to create a new session.
            self.httpScheme = 'https'

        self.serverOs = serverOs ;# windows|windowsConnectionMgr|linux
        self.jsonHeader = {"content-type": "application/json"}
        self.username = username
        self.password = password
        self.apiKey = apiKey
        self.verifySslCert = verifySslCert
        self.linuxApiServerIp = apiServerIp
        self.manageSessionMode = manageSessionMode
        self.apiServerPort = serverIpPort
        self.webQuickTest = webQuickTest
        self.generateLogFile = generateLogFile
        self.robotFrameworkStdout = robotFrameworkStdout
        self.connectToLinuxChassisIp = connectToLinuxChassisIp
        self.proxies = proxies
        self.trustEnv = trustEnv

        if generateLogFile:
            if generateLogFile == True:
                # Default the log file name
                self.restLogFile = 'ixNetRestApi_debugLog.txt'
                Connect.enableDebugLogFile = True
                Connect.debugLogFile = self.restLogFile

            if type(generateLogFile) != bool:
                self.restLogFile = generateLogFile

            # Instantiate a new log file here.
            with open(self.restLogFile, 'w') as restLogFile:
                restLogFile.write('Date: {0}\nTime: {1}\n\n'.format(self.getDate, self.getTime))

        # Make Robot print to stdout
        if self.robotFrameworkStdout:
            from robot.libraries.BuiltIn import _Misc
            self.robotStdout = _Misc()
            Connect.robotStdout = self.robotStdout

        if connectToLinuxChassisIp:
            self.connectToLinuxIxosChassis(self.connectToLinuxChassisIp, self.username, self.password)
            return

        if serverOs == 'windows':
            self.createWindowsSession(apiServerIp, serverIpPort)

        if serverOs == 'windowsConnectionMgr':
            # User connecting to existing sessionId
            # 
            # Starting 8.50, IxNetwork API server supports https on Windows.
            # If connecting to an existing session with the api server set to https::
            #     - Pass in apiServerIp, serverIpPort, httpsSecured and  sessionId
            #     - Although the serverIpPort default is 443 for https, this could change in the future.
            
            if sessionId:
                url = '{0}://{1}:{2}/api/v1/sessions/{3}'.format(self.httpScheme, apiServerIp, serverIpPort, str(sessionId))
                try:
                    response = self._session.request('GET', url, verify=self.verifySslCert, allow_redirects=False)
                    if '3' in str(response.status_code):
                        self.httpScheme = 'https'
                        # Here, needs to set to use https.
                        url = '{0}://{1}:{2}/api/v1/sessions/{3}'.format(self.httpScheme, apiServerIp, serverIpPort, str(sessionId))

                except requests.exceptions.RequestException as errMsg:
                    errMsg = 'Connecting to existing config failed on a GET: {0}'.format(errMsg)
                    raise IxNetRestApiException(errMsg)

                self.logInfo('Connecting to existing session: {}'.format(url))
                self.sessionUrl = url + '/ixnetwork'
                self.sessionId = '{0}://{1}:{2}/api/v1/sessions/{3}'.format(self.httpScheme, apiServerIp,
                                                                            serverIpPort, str(sessionId))
                self.apiSessionId = '/api/v1/sessions/{0}/ixnetwork'.format(str(sessionId))
                self.httpHeader = self.sessionUrl.split('/api')[0]
            else:
                # Create a new session
                self.createWindowsSession(apiServerIp, serverIpPort)

        if serverOs == 'linux':
            if self.apiServerPort == None:
                self.apiServerPort = 443

            # Connect to no session ID. Connects to a Linux API server to get and delete open sessions.
            if self.manageSessionMode:
                if self.apiKey == None:
                    response = self.post('https://{}:{}/api/v1/auth/session'.format(self.linuxApiServerIp, self.apiServerPort),
                                         data={"username": "admin", "password": "admin"},
                                         headers={'content-type': 'application/json'})

                self.jsonHeader = {'content-type': 'application/json', 'x-api-key': self.apiKey}
                self.sessionUrl = 'https://{0}:{1}/ixnetworkweb/api/v1/sessions'.format(self.linuxApiServerIp, self.apiServerPort)
                return

            # Connect to an existing session on the Linux API server
            if apiKey and sessionId:
                self.sessionId = 'https://{0}:{1}/api/v1/sessions/{2}'.format(self.linuxApiServerIp,
                                                                              self.apiServerPort, str(sessionId))
                self.sessionUrl = self.sessionId + '/ixnetwork'
                self.httpHeader = self.sessionUrl.split('/ixnetworkweb')[0]
                self.jsonHeader = {'content-type': 'application/json', 'x-api-key': self.apiKey}

                response = self.get('https://{0}:{1}/api/v1/sessions/{2}'.format(self.linuxApiServerIp, self.apiServerPort, str(sessionId)))

                if self.webQuickTest == False:
                    # https://192.168.70.108/ixnetworkweb/api/v1/sessions/4
                    self.sessionId = response.json()['links'][0]['href']
                    # Remove the redirect /ixnetworkweb from the URL. IxNetwork 8.50 will resolve this.
                    self.sessionId = self.sessionId.replace('ixnetworkweb/', '')

                    # https://192.168.70.108/ixnetworkweb/api/v1/sessions/4/ixnetork
                    self.sessionUrl = self.sessionId + '/ixnetwork'

                    # /api/v1/sessions/4/ixnetwork
                    match = re.match('.*(/api.*)', self.sessionId)
                    self.apiSessionId = match.group(1) + '/ixnetwork'

                    # https://10.10.10.1:443
                    matchHeader = re.match('(https://[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(:[0-9]+)?)', self.sessionId)
                    self.httpHeader = matchHeader.group(1)

                if self.webQuickTest:
                    self.sessionId = 'https://{0}:{1}/ixnetwork/api/v1/sessions/{2}'.format(self.linuxApiServerIp,
                                                                                               self.apiServerPort, str(sessionId))
                    self.sessionUrl = self.sessionId
                    self.httpHeader = self.sessionUrl.split('/ixnetworkweb')[0]

            # Create new session: connectToLinuxApiServer API knows whether to create a new session or connect to an existing
            #                     session by looking at the self.apiKey.
            if apiKey == None and sessionId == None:
                self.connectToLinuxApiServer(self.linuxApiServerIp, self.apiServerPort, username=username, password=password, verifySslCert=verifySslCert)

            if licenseServerIp or licenseMode or licenseTier:
                self.configLicenseServerDetails(licenseServerIp, licenseMode, licenseTier)

        # For Linux API Server and Windoww Connection Mgr only: Delete the session when script is done if deleteSessionAfterTest = True.
        self.deleteSessionAfterTest = deleteSessionAfterTest

        match = re.match('http.*(/api.*/sessions/[0-9]+)/ixnetwork', self.sessionUrl)
        self.headlessSessionId = match.group(1)

        if includeDebugTraceback == False:
            sys.tracebacklimit = 0

    def get(self, restApi, data={}, stream=False, silentMode=False, ignoreError=False):
        """
        Description
            A HTTP GET function to send REST APIs.

        Parameters
           restApi: (str): The REST API URL.
           data: (dict): The data payload for the URL.
           silentMode: (bool):  To display on stdout: URL, data and header info.
           ignoreError: (bool): True: Don't raise an exception.  False: The response will be returned.

        Syntax
            /api/v1/sessions/1/ixnetwork/operations
        """
        if silentMode is False:
            self.logInfo('\n\tGET: {0}\n\tHEADERS: {1}'.format(restApi, self.jsonHeader))

        ses = requests.Session()
        ses.headers=self.jsonHeader
        ses.verify=self.verifySslCert
        ses.allow_redirects=True
        ses.trust_env=self.trustEnv

        if stream:
            ses.stream=True

        if self.proxies != None:
            ses.proxies=self.proxies

        try:
            response = ses.get(restApi)

            if silentMode is False:
                for redirectStatus in response.history:
                    if '307' in str(response.history):
                        self.logInfo('\t{0}: {1}'.format(redirectStatus, response.url), timestamp=False)

                self.logInfo('\tSTATUS CODE: {0}'.format(response.status_code), timestamp=False)

            if not str(response.status_code).startswith('2'):
                if ignoreError == False:
                    if 'message' in response.json() and response.json()['messsage'] != None:
                        self.logWarning('\n%s' % response.json()['message'])

                    errMsg = 'GET Exception error: {0}'.format(response.text)
                    raise IxNetRestApiException(errMsg)

            return response

        except requests.exceptions.RequestException as errMsg:
            errMsg = 'GET Exception error: {0}'.format(errMsg)
            raise IxNetRestApiException(errMsg)

    def post(self, restApi, data={}, headers=None, silentMode=False, noDataJsonDumps=False, ignoreError=False):
        """
        Description
           A HTTP POST function to create and start operations.

        Parameters
           restApi: (str): The REST API URL.
           data: (dict): The data payload for the URL.
           headers: (str): The special header to use for the URL.
           silentMode: (bool):  To display on stdout: URL, data and header info.
           noDataJsonDumps: (bool): True: Use json dumps. False: Accept the data as-is.
           ignoreError: (bool): True: Don't raise an exception.  False: The response will be returned.
        """
        ses = requests.Session()
        ses.verify=self.verifySslCert
        ses.allow_redirects=True
        ses.trust_env=self.trustEnv
  
        if headers != None:
            originalJsonHeader = self.jsonHeader
            self.jsonHeader = headers
            ses.headers=headers
        else:
            ses.headers=self.jsonHeader

        if self.proxies != None:
            ses.proxies=self.proxies

        if noDataJsonDumps == True:
            data = data
        else:
            data = json.dumps(data)

        if silentMode == False:
            self.logInfo('\n\tPOST: {0}\n\tDATA: {1}\n\tHEADERS: {2}'.format(restApi, data, ses.headers))

        try:
            response = ses.post(restApi, data=data)

            # 200 or 201
            if silentMode == False:
                for redirectStatus in response.history:
                    if '307' in str(response.history):
                        self.logInfo('\t{0}: {1}'.format(redirectStatus, response.url), timestamp=False)

                self.logInfo('\tSTATUS CODE: %s' % response.status_code, timestamp=False)

            if str(response.status_code).startswith('2') == False:
                if ignoreError == False:
                    if 'errors' in response.json():
                        errMsg = 'POST Exception error: {0}\n'.format(response.json()['errors'])
                        raise IxNetRestApiException(errMsg)

                    raise IxNetRestApiException('POST error: {0}\n'.format(response.text))

            return response

        except requests.exceptions.RequestException as errMsg:
            errMsg = 'POST Exception error: {0}'.format(errMsg)
            raise IxNetRestApiException(errMsg)

    def patch(self, restApi, data={}, silentMode=False, ignoreError=False):
        """
        Description
           A HTTP PATCH function to modify configurations.

        Parameters
           restApi: (str): The REST API URL.
           data: (dict): The data payload for the URL.
           silentMode: (bool):  To display on stdout: URL, data and header info.
        """
        ses = requests.Session()
        ses.verify=self.verifySslCert
        data = json.dumps(data)
        ses.headers = self.jsonHeader
        ses.trust_env=self.trustEnv

        if self.proxies != None:
            ses.proxies=self.proxies

        if silentMode == False:
            self.logInfo('\n\tPATCH: {0}\n\tDATA: {1}\n\tHEADERS: {2}'.format(restApi, data, self.jsonHeader))

        try:
            response = ses.patch(restApi, data=data)

            if silentMode == False:
                for redirectStatus in response.history:
                    if '307' in str(response.history):
                        self.logInfo('\t{0}: {1}'.format(redirectStatus, response.url), timestamp=False)

                self.logInfo('\tSTATUS CODE: %s' % response.status_code, timestamp=False)

                if ignoreError == False:
                    if not str(response.status_code).startswith('2'):
                        if response.json() and  'errors' in response.json():
                            errMsg = 'PATCH Exception error: {0}\n'.format(response.json()['errors'])
                            raise IxNetRestApiException('PATCH error: {0}\n'.format(errMsg))

            return response

        except requests.exceptions.RequestException as errMsg:
            errMsg = 'PATCH Exception error: {0}\n'.format(errMsg)
            raise IxNetRestApiException(errMsg)

    def options(self, restApi, data={}, silentMode=False, ignoreError=False):
        """
        Description
            A HTTP OPTIONS function to send REST APIs.

        Parameters
           restApi: (str): The REST API URL.
           silentMode: (bool):  To display on stdout: URL, data and header info.
           ignoreError: (bool): True: Don't raise an exception.  False: The response will be returned.

        """
        ses = requests.Session()
        ses.verify=self.verifySslCert
        ses.headers = self.jsonHeader
        ses.trust_env=self.trustEnv

        if self.proxies != None:
            ses.proxies=self.proxies

        if silentMode is False:
            self.logInfo('\n\tOPTIONS: {0}\n\tHEADERS: {1}'.format(restApi, self.jsonHeader))

        try:
            # For binary file
            response = ses.get(restApi, data=data)

            if silentMode is False:
                for redirectStatus in response.history:
                    if '307' in str(response.history):
                        self.logInfo('\t{0}: {1}'.format(redirectStatus, response.url), timestamp=False)

                self.logInfo('\tSTATUS CODE: {0}'.format(response.status_code), timestamp=False)

            if not str(response.status_code).startswith('2'):
                if ignoreError == False:
                    if 'message' in response.json() and response.json()['messsage'] != None:
                        self.logWarning('\n%s' % response.json()['message'])

                    errMsg = 'OPTIONS Exception error: {0}'.format(response.text)
                    raise IxNetRestApiException(errMsg)
            return response

        except requests.exceptions.RequestException as errMsg:
            errMsg = 'OPTIONS Exception error: {0}'.format(errMsg)
            raise IxNetRestApiException(errMsg)

    def delete(self, restApi, data={}, headers=None):
        """
        Description
           A HTTP DELETE function to delete the session.
           For Linux and Windows Connection Mgr API server only.

        Paramters
           restApi: (str): The REST API URL.
           data: (dict): The data payload for the URL.
           headers: (str): The headers to use for the URL.
        """
        ses = requests.Session()
        ses.verify=self.verifySslCert
        ses.verify=self.verifySslCert
        ses.data = json.dumps(data)

        if headers != None:
            ses.headers = headers
        else:
            ses.headers = self.jsonHeader

        if self.proxies != None:
            ses.proxies=self.proxies

        self.logInfo('\n\tDELETE: {0}\n\tDATA: {1}\n\tHEADERS: {2}'.format(restApi, data, ses.headers))

        try:
            response = ses.delete(restApi, data=data)

            for redirectStatus in response.history:
                if '307' in str(response.history):
                    self.logInfo('\t{0}: {1}'.format(redirectStatus, response.url), timestamp=False)

            self.logInfo('\tSTATUS CODE: %s' % response.status_code, timestamp=False)
            if not str(response.status_code).startswith('2'):
                self.showErrorMessage()
                errMsg = 'DELETE Exception error: {0}\n'.format(response.text)
                self.logError(errMsg)
                raise IxNetRestApiException(errMsg)
            return response

        except requests.exceptions.RequestException as errMsg:
            errMsg = 'DELETE Exception error: {0}\n'.format(errMsg)
            self.logError(errMsg)
            raise IxNetRestApiException(errMsg)

    def getDate(self):
        dateAndTime = str(datetime.datetime.now()).split(' ')
        return dateAndTime[0]

    def getTime(self):
        dateAndTime = str(datetime.datetime.now()).split(' ')
        return dateAndTime[1]

    def getSelfObject(self):
        """
        Description
           For Robot Framework support only.

        Return
           The instance object.
        """
        return self

    def createWindowsSession(self, ixNetRestServerIp, ixNetRestServerPort='11009'):
        """
        Description
           Connect to a Windows IxNetwork API Server. This is
           for both Windows and Windows server with IxNetwork Connection Manager.
           This will set up the session URL to use throughout the test.

        Parameter
          ixNetRestServerIp: (str): The Windows IxNetwork API Server IP address.
          ixNetRestServerPort: (str): Default: 11009.  Provide a port number to connect to.
                               On a Linux API Server, a socket port is not needed. State "None".
        """
        # Handle __import__(IxNetRestApi) to not error out
        if ixNetRestServerIp == None: return

        url = 'http://{0}:{1}/api/v1/sessions'.format(ixNetRestServerIp, ixNetRestServerPort)

        if self.serverOs == 'windowsConnectionMgr':
            try:
                self.logInfo('Please wait while IxNetwork Connection Mgr starts up an IxNetwork session...')
                self.logInfo('\t{}'.format(url), timestamp=False)
                response = self._session.request('POST', url, data={}, verify=self.verifySslCert, allow_redirects=True)

            except requests.exceptions.RequestException as errMsg:
                errMsg = 'Creating new session failed: {0}'.format(errMsg)
                raise IxNetRestApiException(errMsg)

            sessionIdNumber = response.json()['links'][0]['href'].split('/')[-1]
            response = self._session.request('GET', url+'/'+str(sessionIdNumber), verify=self.verifySslCert, allow_redirects=False)

            if str(response.status_code).startswith('3'):
                # >= 8.50
                self.httpScheme = 'https'
                # https://192.168.70.3:443/api/v1/sessions/8020
                self.sessionId = response.headers['location']
                match = re.match('https://.*:([0-9]+)/api.*', self.sessionId)
                ixNetRestServerPort = match.group(1)
            else:
                # < 8.50
                self.sessionId = url + '/'+str(sessionIdNumber)

            self.sessionUrl = '{httpScheme}://{apiServer}:{port}/api/v1/sessions/{id}/ixnetwork'.format(
                httpScheme=self.httpScheme,
                apiServer=ixNetRestServerIp,
                port=ixNetRestServerPort,
                id=sessionIdNumber)

            counterStop = 10
            for counter in range(1, counterStop+1):
                response = self.get(self.sessionId)
                if type(response.json()) == list:
                    currentState = response.json()[0]['state']
                else:
                    currentState = response.json()['state']

                self.logInfo('\n\tNew Windows session current state: {0}'.format(currentState), timestamp=False)
                if currentState != 'ACTIVE' and counter < counterStop:
                    self.logInfo('\tWaiting {0}/{1} seconds'.format(counter, counterStop), timestamp=False)
                    time.sleep(1)

                if currentState != 'ACTIVE' and counter == counterStop:
                    raise IxNetRestApiException('New Windows session state failed to become ACTVIE state')

                if currentState == 'ACTIVE' and counter < counterStop:
                    break

            # Version < 8.50 requires more waiting time. And we cannot verify the version number at this point.
            time.sleep(25)

        if self.serverOs == 'windows':
            # windows sessionId is always 1 because it only supports one session.
            self.sessionUrl = url + '/1/ixnetwork'

            try:
                self.logInfo('\nVerifying API server connection: {}'.format(self.sessionUrl))
                response = self._session.request('GET', self.sessionUrl, allow_redirects=False, verify=self.verifySslCert)
                if '307' in str(response.status_code) and response.headers['location'].startswith('https'):
                    # Overwrite the sessionUrl with the redirected https URL
                    self.sessionUrl = response.headers['location']

            except requests.exceptions.RequestException as errMsg:
                errMsg = 'Connection failed: {0}'.format(errMsg)
                raise IxNetRestApiException(errMsg)

            # http://192.168.70.127:11009/api/v1/sessions/1
            self.sessionId = self.sessionUrl.split('/ixnetwork')[0]

        # http://192.168.70.127:11009
        self.httpHeader = self.sessionUrl.split('/api')[0]

        # /api/v1/sessions/{id}/ixnetwork
        self.apiSessionId = '/api/v1/sessions/{0}/ixnetwork'.format(str(self.sessionId))

        self.get(self.sessionUrl)

    def deleteSession(self):
        """
        Description
           Delete the instance session ID. For Linux and Windows Connection Manager only.
        """
        if self.deleteSessionAfterTest:
            self.delete(self.sessionId)

    def logInfo(self, msg, end='\n', timestamp=True):
        """
        Description
           An internal function to print info to stdout

        Parameters
           msg: (str): The message to print.
        """
        currentTime = self.getTime()

        if timestamp:
            msg = '\n' + currentTime + ': ' + msg
        else:
            # No timestamp and no newline are mainly for verifying states and status
            msg = msg

        print('{0}'.format(msg), end=end)
        if self.generateLogFile:
            with open(self.restLogFile, 'a') as restLogFile:
                restLogFile.write(msg+end)

        if self.robotFrameworkStdout:
            self.robotStdout.log_to_console(msg)

    def logWarning(self, msg, end='\n', timestamp=True):
        """
        Description
           An internal function to print warnings to stdout.

        Parameter
           msg: (str): The message to print.
        """
        currentTime = self.getTime()

        if timestamp:
            msg = '\n{0}: Warning: {1}'.format(currentTime, msg)
        else:
            # No timestamp and no newline are mainly for verifying states and status
            msg = msg

        print('{0}'.format(msg), end=end)
        if self.generateLogFile:
            with open(self.restLogFile, 'a') as restLogFile:
                restLogFile.write('Warning: '+msg+end)

        if self.robotFrameworkStdout:
            self.robotStdout.log_to_console(msg)

    def logError(self, msg, end='\n', timestamp=True):
        """
        Description
           An internal function to print error to stdout.

        Parameter
           msg: (str): The message to print.
        """
        currentTime = self.getTime()

        if timestamp:
            msg = '\n{0}: Error: {1}'.format(currentTime, msg)
        else:
            # No timestamp and no newline are mainly for verifying states and status
            msg = '\nError: {0}'.format(msg)

        print('{0}'.format(msg), end=end)
        if self.generateLogFile:
            with open(self.restLogFile, 'a') as restLogFile:
                restLogFile.write('Error: '+msg+end)

        if self.robotFrameworkStdout:
            self.robotStdout.log_to_console(msg)

    def getIxNetworkVersion(self):
        """
        Description
           Get the IxNetwork version.

        Syntax
            GET: /api/v1/sessions/{id}/globals
        """
        response = self.get(self.sessionUrl+'/globals', silentMode=True)
        return response.json()['buildNumber']

    def getAllSessionId(self):
        """
        Show all opened session IDs.

        Return
           A list of opened session IDs.

           {4: {'startedOn': '2018-10-06 12:09:18.333-07:00',
              'state': 'Active',
              'subState': 'Ready',
              'userName': 'admin'},
            5: {'startedOn': '2018-10-06 18:49:05.691-07:00',
              'state': 'Active',
              'subState': 'Ready',
              'userName': 'admin'}
           }
        """
        response = self.get(self.httpHeader+'/api/v1/sessions', silentMode=True)
        sessionId = {}
        for eachSessionId in response.json():
            sessionId.update({eachSessionId['id']: {'userName': eachSessionId['userName'],
                                                    'createdOn': eachSessionId['createdOn'].replace('T', ' '),
                                                    'state': eachSessionId['state'],
                                                    'subState': eachSessionId['subState']
                                                }
                          })

            print('\nSessionId: {}'.format(eachSessionId['id']))
            print('   UserName: {}'.format(eachSessionId['userName']))
            print('   StartedOn: {}'.format(eachSessionId['createdOn'].replace('T', ' ')))
            print('   State: {}'.format(eachSessionId['state']))
            print('   SubState: {}'.format(eachSessionId['subState']))

        print()
        return sessionId

    def showErrorMessage(self, silentMode=False):
        """
        Description
           Show all the error messages from IxNetwork.

        Parameter
          silentMode: (bool): True: Don't print the REST API on stdout.

        Syntax
            GET: /api/v1/sessions/{id}/globals/appErrors/error
        """
        errorList = []
        response = self.get(self.sessionUrl+'/globals/appErrors/error', silentMode=silentMode)
        print()
        for errorId in response.json():
            if errorId['errorLevel'] == 'kError':
                print('CurrentErrorMessage: {0}'.format(errorId['name']))
                print('\tDescription: {0}'.format(errorId['lastModified']))
                errorList.append(errorId['name'])
        print()
        return errorList

    def waitForComplete(self, response='', url='', silentMode=False, ignoreException=False, timeout=120):
        """
        Description
           Wait for an operation progress to complete.

        Parameters
           response: (json response/dict): The POST action response.  Generally, after an /operations action.
                         Such as /operations/startallprotocols, /operations/assignports.
           silentMode: (bool):  If True, display info messages on stdout.
           ignoreException: (bool): ignoreException is for assignPorts.  Don't want to exit test.
                            Verify port connectionStatus for: License Failed and Version Mismatch to report problem immediately.

           timeout: (int): The time allowed to wait for success completion in seconds.
        """
        if silentMode == False:
            self.logInfo('\nwaitForComplete:', timestamp=False)
            self.logInfo("\tState: %s " % response.json()["state"], timestamp=False)

        if response.json() == []:
            raise IxNetRestApiException('waitForComplete: response is empty.')

        if response.json() == '' or response.json()['state'] == 'SUCCESS':
            return response

        if 'errors' in response.json():
            raise IxNetRestApiException(response.json()["errors"][0])

        if response.json()['state'] in ["ERROR", "EXCEPTION"]:
            raise IxNetRestApiException('WaitForComplete: STATE=%s: %s' % (response.json()['state'], response.text))

        for counter in range(1,timeout+1):
            response = self.get(url, silentMode=True)
            state = response.json()["state"]
            if silentMode == False:
                if state != 'SUCCESS':
                    self.logInfo("\tState: {0}: Wait {1}/{2} seconds".format(state, counter, timeout), timestamp=False)
                if state == 'SUCCESS':
                    self.logInfo("\tState: {0}".format(state), timestamp=False)

            if counter < timeout and state in ["IN_PROGRESS", "down"]:
                time.sleep(1)
                continue

            if counter < timeout and state in ["ERROR", "EXCEPTION"]:
                # ignoreException is for assignPorts.  Don't want to exit test.
                # Verify port connectionStatus for: License Failed and Version Mismatch to report problem immediately.
                if ignoreException:
                    return response
                raise IxNetRestApiException(response.text)

            if counter < timeout and state == 'SUCCESS':
                return response

            if counter == timeout and state != 'SUCCESS':
                if ignoreException:
                    return response
                raise IxNetRestApiException('waitForComplete failed: %s' % response.json())

    def connectToLinuxIxosChassis(self, chassisIp, username, password):
        url = 'https://{0}/platform/api/v1/auth/session'.format(chassisIp)
        response = self.post(url, data={'username': username, 'password': password})
        self.apiKey = response.json()['apiKey']
        self.jsonHeader = {'content-type': 'application/json', 'x-api-key': self.apiKey}

        # userAccountUrl: https://{ip}/platform/api/v1/auth/users/{id}
        self.userSessionId = response.json()['userAccountUrl']

        self.ixosHeader = 'https://{0}/chassis/api/v2/ixos'.format(chassisIp)
        self.diagnosticsHeader = 'https://{0}/chassis/api/v1/diagnostics'.format(chassisIp)
        self.authenticationHeader = 'https://{0}/chassis/api/v1/auth'.format(chassisIp)
        self.sessionUrl = self.ixosHeader

    def connectToLinuxApiServer(self, linuxServerIp, linuxServerIpPort, username='admin', password='admin', verifySslCert=False):
        """
        Description
           Connect to a Linux API server.

        Parameters
           linuxServerIp: (str): The Linux API server IP address.
           username: (str): Login username. Default = admin.
           password: (str): Login password. Default = admin.
           verifySslCert: (str): Defalt: None.  The SSL Certificate for secure access verification.

       Syntax
            POST: /api/v1/auth/session
        """
        self.verifySslCert = verifySslCert

        if self.apiKey is None:
            # 1: Connect to the Linux API server
            url = 'https://{0}:{1}/api/v1/auth/session'.format(linuxServerIp, linuxServerIpPort)
            self.logInfo('connectToLinuxApiServer: %s' % url)
            response = self.post(url, data={'username': username, 'password': password}, ignoreError=True)
            if not str(response.status_code).startswith('2'):
                raise IxNetRestApiException('\nLogin username/password failed\n')
            self.apiKey = response.json()['apiKey']

            url = 'https://{0}:{1}/api/v1/sessions'.format(linuxServerIp, linuxServerIpPort)
            if self.webQuickTest == False:
                data = {'applicationType': 'ixnrest'}
            if self.webQuickTest == True:
                data = {'applicationType': 'ixnetwork'}

            self.jsonHeader = {'content-type': 'application/json', 'x-api-key': self.apiKey}
            self.logInfo('linuxServerCreateSession')
            response = self.post(url, data=data, headers=self.jsonHeader)

            self.sessionIdNumber = response.json()['id']
            response = self.get(url+'/'+str(self.sessionIdNumber))

            # https://192.168.70.108/ixnetworkweb/api/v1/sessions/7
            self.sessionId = response.json()['links'][0]['href']
            # Remove the redirect /ixnetworkweb from the URL. IxNetwork 8.50 will resolve this.
            self.sessionId = self.sessionId.replace('ixnetworkweb/', '')

            # https://10.10.10.1:443
            matchHeader = re.match('(https://[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(:[0-9]+)?)', self.sessionId)
            self.httpHeader = matchHeader.group(1)

            if ':' not in self.httpHeader:
                self.httpHeader + '/' + linuxServerIpPort

            self.sessionUrl = self.sessionId+'/ixnetwork'

            # /api/v1/sessions/4/ixnetwork
            match = re.match('.*(/api.*)', self.sessionId)
            self.apiSessionId = match.group(1) + '/ixnetwork'

            # 3: Start the new session
            response = self.post(self.sessionId+'/operations/start')
            if self.linuxServerWaitForSuccess(response.json()['url'], timeout=60) == 1:
                raise IxNetRestApiException

            if self.webQuickTest == True:
                self.sessionId = 'https://{0}/ixnetworkweb/api/v1/sessions/{1}'.format(linuxServerIp, self.sessionIdNumber)
                self.sessionUrl = 'https://{0}/ixnetworkweb/api/v1/sessions/{1}/ixnetwork'.format(linuxServerIp, self.sessionIdNumber)
                self.httpHeader = self.sessionUrl.split('/api')[0]

        # If an API-Key is provided, then verify the session ID connection.
        if self.apiKey:
            self.get(self.sessionId)

    def linuxServerGetGlobalLicense(self, linuxServerIp):
        """
        Description
           Get the global license server details from the Linux API server.

        Paramters
           linuxServerIp: (str): The IP address of the Linux API server.

        Syntax
            GET: /api/v1/sessions/9999/ixnetworkglobals/license
        """
        staticUrl = 'https://{linuxServerIp}/api/v1/sessions/9999/ixnetworkglobals/license'.format(linuxServerIp=linuxServerIp)
        self.logInfo('linuxServerGetGlobalLicense: %s ' % linuxServerIp)
        response = self.get(staticUrl, silentMode=False)
        licenseServerIp = response.json()['servers'][0]
        licenseServerMode = response.json()['mode']
        licenseServerTier = response.json()['tier']
        self.logInfo('linuxServerGetGlobalLicenses:')
        self.logInfo('\t%s' % licenseServerIp)
        self.logInfo('\t%s' % licenseServerMode)
        self.logInfo('\t%s' % licenseServerTier)
        return licenseServerIp,licenseServerMode,licenseServerTier

    def configLicenseServerDetails(self, licenseServer=None, licenseMode=None, licenseTier=None):
        """
        Description
           Configure license server details: license server IP, license mode and license tier.

        Parameters
           licenseServer: (str): License server IP address(s) in a list.
           licenseMode: (str): subscription | perpetual | mixed
           licenseTier: (str): tier1 | tier2 | tier3 ...

        Syntax
           PATCH: /api/v1/sessions/{id}/ixnetwork/globals/licensing
        """
        # Each new session requires configuring the new session's license details.
        data = {}
        if licenseServer:
            data.update({'licensingServers': licenseServer})
        if licenseMode:
            data.update({'mode': licenseMode})
        if licenseTier:
            data.update({'tier': licenseTier})

        response = self.patch(self.sessionUrl+'/globals/licensing', data=data)
        self.showLicenseDetails()

    def showLicenseDetails(self):
        """
        Description
           Display the new session's license details.

        Syntax
            GET: /api/v1/sessions/{id}/globals/licensing
        """
        response = self.get(self.sessionUrl+'/globals/licensing')
        self.logInfo('\nVerifying sessionId license server: %s' % self.sessionUrl, timestamp=False)
        self.logInfo('\t%s' % response.json()['licensingServers'], timestamp=False)
        self.logInfo('\t%s'%  response.json()['mode'], timestamp=False)
        self.logInfo('\t%s' % response.json()['tier'], timestamp=False)

    def getAllOpenSessionIds(self):
        """
        Description
           Get a list of open session IDs and some session metas.
           
        Syntax
            GETE: /api/v1/sessions

        Return
            A dict
            
        """
        response = self.get(self.sessionUrl)
        sessionIdDict = {}
        from pprint import pprint
        pprint(response.json())
        return
        for session in response.json():
            href = session['links'][0]['href']
            id = session['id']
            username = session['userName']
            state = session['state']
            createOn = session['createdOn']

            sessionIdDict[id] = {'id': id, 'sessionIdUrl': href, 'username': username, 'state': state, 'createdOn': createOn}

        return sessionIdDict

    def linuxServerStopAndDeleteSession(self):
        """
        Description
           Wrapper to stop and delete the session ID on the Linux API server.

        Requirements
           linuxServerStopOperations()
           linuxServerDeleteSession()

        Syntax
           GET = /api/v1/sessions/{id}
        """
        if self.serverOs == 'linux' and self.deleteSessionAfterTest==True:
            self.linuxServerStopOperations()
            self.linuxServerDeleteSession()

    def linuxServerStopOperations(self, sessionId=None):
        """
        Description
           Stop the session ID on the Linux API server.

        Parameter
           sessionId: (str): The session ID to stop.

        Requirement
           self.linuxServerWaitForSuccess()

        Syntax
            POST: /api/v1/sessions/{id}/operations/stop
        """
        if sessionId != None:
            sessionId = sessionId
        else:
            sessionId = self.sessionId

        response = self.post(sessionId+'/operations/stop')
        if self.linuxServerWaitForSuccess(response.json()['url'], timeout=90) == 1:
            raise IxNetRestApiException

    def linuxServerDeleteSession(self, sessionId=None):
        """
        Description
           Delete the session ID on the Linux API server.

        Paramter
          sessionId: (str): The session ID to delete on the Linux API server.

        Syntax
            DELETE: /api/v1/sessions/{id}/operations/stop
        """
        if sessionId != None:
            sessionId = sessionId
        else:
            sessionId = self.sessionId

        response = self.delete(sessionId)

    def linuxServerWaitForSuccess(self, url, timeout=120):
        """
        Description
           Wait for a success completion on the Linux API server.

        Paramters
           url: (str): The URL's ID of the operation to verify.
           timeout: (int): The timeout value.
        """
        data = {'applicationType': 'ixnrest'}
        jsonHeader = {'content-type': 'application/json', 'x-api-key': self.apiKey}
        self.logInfo('linuxServerWaitForSuccess')
        for counter in range(1,timeout+1):
            response = self.get(url, data=data, silentMode=True)
            currentStatus = response.json()['message']
            self.logInfo('\tCurrentStatus: {0}:  {1}/{2} seconds'.format(currentStatus, counter, timeout), timestamp=False)
            if counter < timeout+1 and currentStatus != 'Operation successfully completed':
                time.sleep(1)
            if counter == timeout+1 and currentStatus != 'Operation successfully completed':
                return 1
            if counter < timeout+1 and currentStatus == 'Operation successfully completed':
                return 0

    def newBlankConfig(self):
        """
        Description
           Start a new blank configuration.

        Requirement
            self.waitForComplete()

        Syntax:
           /api/v1/sessions/{1}/ixnetwork/operations/newconfig
        """
        url = self.sessionUrl+'/operations/newconfig'
        response = self.post(url)
        url = self.sessionUrl+'/operations/newconfig/'+response.json()['id']
        self.waitForComplete(response, url)

    def refreshHardware(self, chassisObj):
        """
        Description
           Refresh the chassis

        Parameter
           chassisObj: (str):The chassis object.
                           Ex: /api/v1/sessions/{1}/ixnetwork/availableHardware/chassis/1

        Requirement
           self.waitForComplete()

        Syntax
            /api/v1/sessions/{1}/ixnetwork/availableHardware/chassis/operations/refreshinfo
        """
        response = self.post(self.sessionUrl+'/availableHardware/chassis/operations/refreshinfo', data={'arg1': [chassisObj]})
        self.waitForComplete(response, self.sessionUrl+'/availableHardware/chassis/operations/refreshinfo')

    def query(self, data, silentMode=False):
        """
        Description
           Query for objects using filters.

        Paramater
           silentMode: (bool): True: Don't display any output on stdout.

        Notes
            Assuming this is a BGP configuration, which has two Topologies.
            Below demonstrates how to query the BGP host object by
            drilling down the Topology by its name and the specific the BGP attributes to modify at the
            BGPIpv4Peer node: flap, downtimeInSec, uptimeInSec.
            The from '/' is the entry point to the API tree.
            Notice all the node. This represents the API tree from the / entry point and starting at
            Topology level to the BGP host level.

        Notes
           Use the API Browser tool on the IxNetwork GUI to view the API tree.
            data: {'from': '/',
                    'nodes': [{'node': 'topology',    'properties': ['name'], 'where': [{'property': 'name', 'regex': 'Topo1'}]},
                              {'node': 'deviceGroup', 'properties': [], 'where': []},
                              {'node': 'ethernet',    'properties': [], 'where': []},
                              {'node': 'ipv4',        'properties': [], 'where': []},
                              {'node': 'bgpIpv4Peer', 'properties': ['flap', 'downtimeInSec', 'uptimeInSec'], 'where': []}]
                }

        Requirements
            self.waitForComplete()

        Examples
            response = restObj.query(data=queryData)
            bgpHostAttributes = response.json()['result'][0]['topology'][0]['deviceGroup'][0]['ethernet'][0]['ipv4'][0]['bgpIpv4Peer'][0]

            # GET THE BGP ATTRIBUTES TO MODIFY
            bgpHostFlapMultivalue = bgpHostAttributes['flap']
            bgpHostFlapUpTimeMultivalue = bgpHostAttributes['uptimeInSec']
            bgpHostFlapDownTimeMultivalue = bgpHostAttributes['downtimeInSec']

            restObj.configMultivalue(bgpHostFlapMultivalue, multivalueType='valueList', data={'values': ['true', 'true']})
            restObj.configMultivalue(bgpHostFlapUpTimeMultivalue, multivalueType='singleValue', data={'value': '60'})
            restObj.configMultivalue(bgpHostFlapDownTimeMultivalue, multivalueType='singleValue', data={'value': '30'})
        """
        url = self.sessionUrl+'/operations/query'
        reformattedData = {'selects': [data]}
        response = self.post(url, data=reformattedData, silentMode=silentMode)
        self.waitForComplete(response, url+'/'+response.json()['id'], silentMode=silentMode)
        return response

    def configMultivalue(self, multivalueUrl, multivalueType, data):
        """
        Description
           Configure multivalues.

        Parameters
           multivalueUrl: (str): The multivalue: /api/v1/sessions/{1}/ixnetwork/multivalue/1
           multivalueType: (str): counter|singleValue|valueList
           data: (dict): singleValue: data={'value': '1.1.1.1'})
                             valueList:   data needs to be in a [list]:  data={'values': [list]}
                             counter:     data={'start': value, 'direction': increment|decrement, 'step': value}
        """
        if multivalueType == 'counter':
            # Examples: macAddress = {'start': '00:01:01:00:00:01', 'direction': 'increment', 'step': '00:00:00:00:00:01'}
            #          data=macAddress)
            self.patch(self.httpHeader+multivalueUrl+'/counter', data=data)

        if multivalueType == 'singleValue':
            # data={'value': value}
            self.patch(self.httpHeader+multivalueUrl+'/singleValue', data=data)

        if multivalueType == 'valueList':
            # data={'values': ['item1', 'item2']}
            self.patch(self.httpHeader+multivalueUrl+'/valueList', data=data)

    def getMultivalueValues(self, multivalueObj, silentMode=False):
        """
        Description
           Get the multivalue values.

        Parameters
           multivalueObj: (str): The multivalue object: /api/v1/sessions/{1}/ixnetwork/multivalue/208
           silentMode: (bool): True=Display the GET and status code. False=Don't display.

        Requirements
           self.waitForComplete()
        """
        response = self.get(self.httpHeader+multivalueObj+'?includes=count', silentMode=silentMode)
        count = response.json()['count']
        if silentMode == False:
            self.logInfo('getMultivalueValues: {0} Count={1}'.format(multivalueObj, count))
        data = {'arg1': multivalueObj,
                'arg2': 0,
                'arg3': count
                }
        response = self.post(self.sessionUrl+'/multivalue/operations/getValues', data=data, silentMode=silentMode)
        self.waitForComplete(response, self.sessionUrl+'/operations/multivalue/getValues'+response.json()['id'])
        return response.json()['result']

    def getObjAttributeValue(self, obj, attribute):
        """
        Description
           Based on the object handle, get any property attribute and return the value.

        Parameter
           obj: <str:obj>: An object handle:
                For example: If you want the ethernet MTU, then pass in the ethernet object handle:
                            /api/v1/sessions/{id}/ixnetwork/topology/{id}/deviceGroup/{id}/ethernet/{id}
                            and set attribute='mtu'

        Note:
           Where to get the object's attribute names:
              - Use the API browser and go to your object.
              - All the attributes are listed on the right pane.
        """
        response = self.get(self.httpHeader + obj)

        # value: Could be /api/v1/sessions/{id}/ixnetwork/multivalue/{id} or the actual value
        value = response.json()[attribute]
        if type(value) == str and 'multivalue' in value:
            multivalueObj = value
            value = self.getMultivalueValues(multivalueObj)
            return value
        else:
            return value

    def stdoutRedirect(self):
        """
        Description
           For Robot Framework.  Robot captures the stdout. This stdoutRedirect
           will redirect the output back to stdout so you could see the test progress
           and to troubleshoot.
        """
        for attr in ('stdin', 'stdout', 'stderr'):
            setattr(sys, attr, getattr(sys, '__%s__' %attr))

    @staticmethod
    def prettyprintAllOperations(sessionUrl):
        """
        Description
           A staticmethod to rendering a nice output of an operations options and descriptions.

        Parameter
           sessionUrl: (str): http://{apiServerIp}:{port}/api/v1/sessions/1/ixnetwork

        Syntax:
            /api/v1/sessions/{1}/ixnetwork/operations
        """
        response = self._session.request('GET', sessionUrl+'/operations')
        for item in response.json():
            if 'operation' in item.keys():
                print('\n', item['operation'])
                print('\t%s' % item['description'])
                if 'args' in item.keys():
                    for nestedKey,nestedValue in item['args'][0].items():
                        print('\t\t%s: %s' % (nestedKey, nestedValue))

    @staticmethod
    def printDict(obj, nested_level=0, output=sys.stdout):
        """
        Description
           Print each dict key with indentions for human readability.
        """
        spacing = '   '
        spacing2 = ' '
        if type(obj) == dict:
            print( '%s' % ((nested_level) * spacing), file=output)
            for k, v in obj.items():
                if hasattr(v, '__iter__'):
                    print('%s%s:' % ( (nested_level+1) * spacing, k), file=output, end='')
                    IxNetRestMain.printDict(v, nested_level+1, output)
                else:
                    print('%s%s: %s' % ( (nested_level + 1) * spacing, k, v), file=output)

            print('%s' % (nested_level * spacing), file=output)
        elif type(obj) == list:
            print('%s[' % ((nested_level) * spacing), file=output)
            for v in obj:
                if hasattr(v, '__iter__'):
                    IxNetRestMain.printDict(v, nested_level + 1, file=output)
                else:
                    print('%s%s' % ((nested_level + 1) * spacing, v), file=output)
            print('%s]' % ((nested_level) * spacing), output)
        else:
            print('%s%s' % ((nested_level * spacing2), obj), file=output)

    def placeholder():
        pass


