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:
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.
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.
# 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)
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.
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.
Any time you save a file that is used by your Flask app, you must restart it!!
These are the APIs being exposed by the backend of your provisioning portal.
@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.