Core SOAP Python Modules
via Provisioning Portal API

As you observed earlier using the SoapUI application and Python, the Unified CM provisioning and serviceability APIs are SOAP based. To build the web portal, you will leverage the same Python modules you used previously in this lab in addition to some additional modules used to create the web server and REST APIs you will be building.

In this section, you will review the following:

  1. Core Python modules for CUCM SOAP APIs
  2. Portal connectivity and credentials to pod servers
  3. Instantiate core CUCM SOAP API classes

Step 1 - Introduction to Core Python Modules for CUCM SOAP APIs.

We are providing two Python modules, flaskr/cucm/v1/axltoolkit/__init__.py and flaskr/cucm/v1/cucm.py, in order to provide a common core framework allowing the portal server to make use of SOAP APIs. These modules use the Zeep Python library, which you have previously used in exercises. You may open these files as a reference via your VS Code Instance browser tab, using the Explorer. You will utilize several classes & methods from these modules in order to add functionality to your Web Portal. If you don't know what Python modules, classes, or methods are, don't worry. While understanding these concepts would help to understand the underlying code that we have provided for you, it is not necessary to be able to complete this lab.

Not all of classes or methods provided in these pre-built modules will be utilized in the web portal but we are providing them to you for future use.

The following describes in more detail the capabilities provided by these two modules.

  1. flaskr/cucm/v1/axltoolkit: This module provides the following Python classes for various CUCM SOAP APIs utilizing the Zeep Python package as the SOAP Client. The classes provide the appropriate WSDL files to the Zeep package which in turn automatically generates functions that you can call in your code. Additionally, it provides persistent session settings (cookies, authentication) for all SOAP client requests, SOAP request fault detection and debug logging for troubleshooting. The code in these classes is very similar to the code you explored during the example exercises, but just packaged up into Python classes.

  2. flaskr/cucm/v1/cucm.py: This module provides a wrapper/middle layer between the various AxlToolkit classes and the web portal's API methods that you will implement. It extends the basic SOAP Client functionality provided in AxlToolkit for various CUCM API interfaces with automated setup, retry, and fault detection. We have the following major API classes implemented in this module:

    • AXL: This wrapper class further extends the CUCMAXLToolkit class to provide automated setup, retry, and fault detection when making AXL API Calls to CUCM.
    • PAWS: This wrapper class further extends the PawsToolkit class to provide automated setup, fault detection when making PAWS API Calls to CUCM. It requires a PAWS API Service name to be supplied because each Service has its own WSDL file. For example, you will only utilize the VersionService for the web portal.
    • SXML: This wrapper class extends multiple serviceability API classes from the AxlToolkit module to provide automated setup, retry, and fault detection when making SXML API Calls to CUCM. Similar to PAWS class, you need to supply an SXML Service name. Currently we have implemented UcmRisPortToolkit, UcmServiceabilityToolkit, and UcmPerfMonToolkit.

Step 2 - Portal Connectivity & Credentials to Servers

In order for the portal to connect to your pod devices such as Unified CM, Unity Connection, and CMS, you need to supply hostnames, HTTPS port number, username and password for authentication. We have pre-configured a file that will be used to populate environment variables which will be read by various portal APIs that you will be implementing.

In this lab, for simplicity, authentication information (usernames and passwords) is stored in a .env text file that is used to set environment variables. As part of the application initialization, this file is read and all of its data placed into environment variables. When one of these parameters is needed by the code, the appropriate environment variable data is read. While the use of environment variables for sensitive information is common, the actual data should normally not be stored in a text file or even in the version control system at all. To make this code more secure, you would just replace the mechanism by which you load the data into the environment variables, but the rest of the code would remain the same.

  1. In your VS Code instance browser tab, open up .env in your project root folder using the Explorer and ensure the content is accurate for your pod.

# Environment variables

# Participant pod number
POD_NUM='6'

# DEFAULT CUCM ACCESS INFO
CUCM_HOSTNAME=cucm1a.pod6.col.lab
CUCM_PORT=8443
CUCM_USERNAME=admin
CUCM_PASSWORD=C1sco.123

# DEFAULT CUC ACCESS INFO
CUC_HOSTNAME=cuc1a.pod6.col.lab
CUC_PORT=8443
CUC_USERNAME=admin
CUC_PASSWORD=C1sco.123

# DEFAULT CMS ACCESS INFO
CMS_HOSTNAME=cms1a.pod6.col.lab
CMS_PORT=8443
CMS_USERNAME=admin
CMS_PASSWORD=C1sco.123

# Service App settings (client_id, client_secret, and refresh_token)

Step 3 - Instantiate Core CUCM SOAP API Classes

In order to utilize the core CUCM SOAP API Python classes you need to instantiate them (AXL, PAWS, SXML) by providing the details of your CUCM server stored in environment variables which came from the .env file you just looked at. You will need to make use of the CUCM_HOSTNAME, CUCM_PORT, CUCM_USERNAME, and CUCM_PASSWORD variables.

When communicating with CUCM and other VOS-based UC applications (CUC, IM&P, etc) over web services based APIs (AXL, PAWS, SXML, REST), you must maintain a web connectivity session where the application can reuse the already established TLS connection as well as cache and re-use the JSESSIONID / JSESSIONIDSSO cookie(s) returned after a successful authentication. If you do not cache these authentication cookie(s) and do not send one with subsequent requests, each API request your provisioning portal makes will open a new session with the product. This may lead to large numbers of active sessions, which, in turn, can lead to overall system performance degradation. Although the Cisco Tomcat (the web server used on CUCM and other VOS-based products) web application sessions have a default timeout of 30 minutes, if the JSESSIONID & JSESSIONIDSSO cookies are not cached by the client and the request rate is high, Cisco Tomcat services will start to spike the CPU and deplete available memory, potentially leading to more serious issues on the server.

You can monitor Active Sessions on a per web application basis by monitoring the following CUCM performance counter:

        \\cucm-host\Cisco Tomcat Web Application(axl)\SessionsActive

Performance degradation is also dependent on the request and response activity, but as a general rule active session count over a 100 is of concern. The Using JSESSIONIDSSO to improve performance page on developer.cisco.com has more information on this topic.

In Python you can easily achieve this persistent authentication cookie caching by utilizing the session object available within the Python Requests library https://requests.readthedocs.io/en/latest/user/advanced/#session-objects. The Python requests library is already utilized by the AxlToolkit module in the same way you did in the earlier exercises.

In order to maintain this persistent session object, you must instantiate the CUCM SOAP API Python classes globally in the flaskr/api/v1/cucm.py file. Follow these steps to create new variables and create new instances of the classes discussed earlier.

  1. In your VS Code tab, using the Explorer function, open up flaskr/api/v1/cucm.py
  2. After the Comment Line "# Core CUCM SOAP API Python Class Instances" copy & paste the following highlighted lines.

    
    
    api = Namespace('cucm', description='Cisco Unified Communications Manager APIs')
    
    # Read environment variables
    cucm_host = getenv('CUCM_HOSTNAME')
    cucm_user = getenv('CUCM_USERNAME')
    cucm_pass = getenv('CUCM_PASSWORD')
    
    # Core CUCM SOAP API Python Class Instances
    myAXL = AXL(cucm_host, cucm_user, cucm_pass)
    myPAWSVersionService = PAWS(cucm_host, cucm_user, cucm_pass, 'VersionService')
    mySXMLRisPort70Service = SXML(cucm_host, cucm_user, cucm_pass, 'realtimeservice2')
    mySXMLControlCenterServicesService = SXML(cucm_host, cucm_user, cucm_pass, 'controlcenterservice2')
    mySXMLPerfMonService = SXML(cucm_host, cucm_user, cucm_pass, 'perfmonservice2')
    ###########################################
            

    This code creates five variables, one for each of the SOAP API classes. You will later be able to call these to issue commands through these APIs. Notice that the username, password, and hostname information passed into these classes comes from the variables declared immediately before the lines you pasted, retrieved from the environment variables using the Python getenv function. The strings passed into getenv directly map to the names defined in the .env file.

  3. Save this file by going to application menu → File → Save or by simply pressing Ctrl+S
  4. Restart your Flask app by clicking the green restart button from the controller: which you should always see along the top of the window while Flask is running.
    If Flask is not started at all, click the Debug button on the left side followed by the green start/play button next to Start LTRCOL-2574 Portal
  5. If the terminal window at bottom right shows: * Running on http://10.0.106.40.40:5000/ (Press CTRL+C to quit), then your Flask app is running.

    Any time you save a file that is used by your Flask app, you must restart it!!

  6. Access your portal's RESTPlus/Swagger UI page at http://dev1.pod6.col.lab:5000/api/v1/

    These are the APIs being exposed by the backend of your provisioning portal.

  7. Expand the cucm section by clicking anywhere on the cucm "Cisco Unified Communications Manager APIs" line

  8. Click the GET /cucm/version operation.

  9. On the right, click Try it out.
  10. In the expanded section, click Execute.
  11. Examine the response. You should note a Server response code of 200 and a response body with your CUCM's Active Software Version.

  12. To understand what just happened, let's explore the code that was executed. In the cucm.py file, you will see the class cucm_get_version_api:

    
    @api.route("/version")
    class cucm_get_version_api(Resource):
        def get(self):
            """
            Returns CUCM Active Software version
    
            This API method utilizes getActiveVersion PAWS requests along with the supplied parameters
            
    https://developer.cisco.com/site/paws/documents/api-reference/ """ try: version_info = myPAWSVersionService.get_version() except Exception as e: result = {'success': False, 'message': str(e)} return jsonify(result) apiresult = {'success': True, 'message': "CUCM Active Version Retrieved Successfully", 'version': version_info['version']} return jsonify(apiresult)

    The first line, @api.route("/version"), automatically ensures that a function in this class is called when your web server receives a request on the "/version" URI depending on the verb that was used in the HTTP request. In this case you sent a GET, so the get(self) method is the one that gets called. The meat of what this method is doing is calling myPAWSVersionService.get_version() and parsing the output.

    So what does myPAWSVersionService.get_version() do?

    myPAWSVersionService is an instance of the PAWS class that you had instantiated at the beginning of this page. The PAWS class has a method called get_version that uses the PAWS API to pull the version information from CUCM. To explore what is happening in the PAWS class, you can see the following function in the flaskr/cucm/v1/cucm.py file:

    
    @Decorators.paws_result_check
    @Decorators.paws_setup
    def get_version(self, Version='Active'):
        if (Version == 'Active'):
            return self.pawsclient.get_active_version()
        elif (Version == 'Inactive'):
            return self.pawsclient.get_inactive_version()
        else:
            raise Exception("get_version only accepts 'Active' or 'Inactive'")
        

    By default, the function returns the Active version and calls self.pawsclient.get_active_version() to obtain the version. So now what does get_active_version() do? This function comes from the AxlToolkit that has the following method:

    
        def get_active_version(self):
            active_version = self.service.getActiveVersion()
            return active_version
    

    The get_active_version() method calls self.service.getActiveVersion(). The getActiveVersion() method is one that was automatically generated by Zeep by reading the PAWS API WSDL file. It is this function that takes care of creating a SOAP request and sending it to the server, parsing the response, and returning back the version information from the server.

    Back to the original class cucm_get_version_api:

    
        try:
            version_info = myPAWSVersionService.get_version()
        except Exception as e:
            result = {'success': False, 'message': str(e)}
            return jsonify(result)
        apiresult = {'success': True, 'message': "CUCM Active Version Retrieved Successfully", 'version': version_info['version']}
        return jsonify(apiresult)
        

    The last operation to explain here is return jsonify(apiresult) line. You may have noticed why we are using jsonify instead of just returning the apiresult which is a dictionary. The reason is that the jsonify function is useful in Flask apps because it automatically sets the correct response headers and content type for JSON responses, and allows you to easily return JSON-formatted data.

    For the rest of this lab, you will be focused on these final steps where you implement the actual work to make the API calls and parse the results. In this case, getting the version was relatively straightforward, but as you will see, more complex interactions will require more code.

Congratulations! You have just executed your first API call via your portal which called the CUCM PAWS API Version Service (getActiveVersion) request. The code to invoke this API was already provided, but you will be implementing several others on your own. Let's dive a little deeper and implement API functions that will provision your CUCM via the AXL API.