├── .gitignore ├── .env .example ├── requirements.txt ├── requirements.txt_bak ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── services_soapGetServiceStatus.py ├── services_getProductInformationList.py ├── perfmonPort_collect_counter_data.py ├── services_doControlServices.py ├── risport70_selectCmDevice.py ├── logCollection_GetOneFile.py ├── perfmonPort_collect_session_data.py └── schema ├── LogCollectionPortTypeService.wsdl ├── ControlCenterServices.wsdl ├── PerfmonService.wsdl └── RISService70.wsdl /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/settings.json 2 | __pycache__ 3 | *.pyc 4 | *.txt 5 | .env 6 | venv/ -------------------------------------------------------------------------------- /.env .example: -------------------------------------------------------------------------------- 1 | # Configuration settings used by alls amples 2 | # Be sure to rename this file '.env' 3 | 4 | # Configure your CUCM address and Risport API user credentials here 5 | CUCM_ADDRESS= 6 | USERNAME= 7 | PASSWORD= 8 | 9 | # Print detailed debug output of all HTTP requests/responses 10 | # Options: True/False 11 | DEBUG=False -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | attrs==23.1.0 2 | certifi==2023.11.17 3 | charset-normalizer==3.3.2 4 | idna==3.4 5 | isodate==0.6.1 6 | lxml==4.9.3 7 | platformdirs==4.0.0 8 | python-dotenv==1.0.0 9 | pytz==2023.3.post1 10 | requests==2.31.0 11 | requests-file==1.5.1 12 | requests-toolbelt==1.0.0 13 | six==1.16.0 14 | urllib3==2.1.0 15 | zeep==4.2.1 16 | -------------------------------------------------------------------------------- /requirements.txt_bak: -------------------------------------------------------------------------------- 1 | attrs==23.1.0 2 | certifi==2023.7.22 3 | charset-normalizer==3.2.0 4 | idna==3.4 5 | isodate==0.6.1 6 | lxml==4.9.3 7 | platformdirs==3.10.0 8 | python-dotenv==1.0.0 9 | pytz==2023.3 10 | requests==2.31.0 11 | requests-file==1.5.1 12 | requests-toolbelt==1.0.0 13 | six==1.16.0 14 | urllib3==2.0.4 15 | zeep==4.2.1 16 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Python: Current File", 9 | "type": "python", 10 | "request": "launch", 11 | "program": "${file}", 12 | "console": "integratedTerminal" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018, Cisco Systems, Inc. and/or its affiliates 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # serviceability-python-zeep-samples 2 | 3 | ## Overview 4 | 5 | Sample scripts demonstrating usage of various Cisco CUCM Serviceability APIs using Python and the Zeep SOAP library. 6 | 7 | https://developer.cisco.com/site/sxml/ 8 | 9 | ## Available samples 10 | 11 | * `risport70_selectCmDevice.py` - Demonstrates querying for all device registrations using Risport (``) 12 | 13 | * `perfmonPort_collect_counter_data.py` - Demonstrates retrieving and parsing performance counter data via the Perfmon `` request 14 | 15 | * `services_getProductInformationList.py` - Use Control Center Services to retrieve a list of the installed Products and versions (``) 16 | 17 | * `perfmonPort_collectSession_data.py` - (Mac/Linux only) Uses Perfmonport to start a collection session, add example counters, then periodically retrieve/parse the results (``, ``,``) 18 | 19 | * `logCollection_GetOneFile.py` - Performs a listing of log files available for a specific service (Cisco Audit Logs), retrieves the contents of the latest file, then parses/prints a few lines of the results (``, ``) 20 | 21 | * `services_soapGetServiceStatus.py` - Performs a `` request using the Zeep SOAP library. 22 | 23 | * `services_doControlServices.py` - Performs a `` request using the Zeep SOAP library, 24 | periodically checks the status using `` and parses/prints the results in a simple table output. 25 | 26 | Tested using: 27 | 28 | * Ubuntu 21.04 / Python 3.9.5 29 | * Mac OS 11.4 / Python 3.9.6 30 | 31 | ## Getting started 32 | 33 | * Install Python 3 34 | 35 | (On Windows, choose the option to add to PATH environment variable) 36 | 37 | * Clone this repository: 38 | 39 | ```bash 40 | git clone https://www.github.com/CiscoDevNet/serviceability-python-zeep-samples 41 | cd serviceability-python-zeep-samples 42 | ``` 43 | 44 | * (Optional) Create/activate a Python virtual environment named `venv`: 45 | 46 | ```bash 47 | python3 -m venv venv 48 | source venv/bin/activate 49 | ``` 50 | 51 | * Install needed dependency packages: 52 | 53 | ```bash 54 | pip install -r requirements.txt 55 | ``` 56 | 57 | * Open the project in Visual Studio Code: 58 | 59 | ```bash 60 | code . 61 | ``` 62 | 63 | * Rename the file `.env.example` to `.env` and edit to specify your CUCM address and [Serviceability API user credentials](https://d1nmyq4gcgsfi5.cloudfront.net/site/sxml/help/faq/#sec-1) 64 | 65 | * The Serviceability SOAP API WSDL files for CUCM v12.5 are included in this project. If you'd like to use a different version, replace the files in `schema/` with the versions from your CUCM, which can be retrieved at: 66 | 67 | * CDRonDemand: `https://{cucm}/CDRonDemandService2/services/CDRonDemandService?wsdl` 68 | 69 | * Log Collection: `https://{cucm}:8443/logcollectionservice2/services/LogCollectionPortTypeService?wsdl` 70 | 71 | * PerfMon: `https://{cucm}:8443/perfmonservice2/services/PerfmonService?wsdl` 72 | 73 | * RisPort70: `https://{cucm}:8443/realtimeservice2/services/RISService70?wsdl` 74 | 75 | * Control Center Services: `https://{cucm}:8443/controlcenterservice2/services/ControlCenterServices?wsdl` 76 | 77 | * Control Center Services Extended: `https://{cucm}:8443/controlcenterservice2/services/ControlCenterServicesEx?wsdl` 78 | 79 | ## Hints 80 | 81 | * You can get a 'dump' of an API WSDL to see how Zeep interprets it, for example by running (Mac/Linux): 82 | 83 | ```bash 84 | python -mzeep schema/PerfmonService.wsdl > PerfmonServiceWsdl.txt 85 | ``` 86 | 87 | This can help with identifying the proper object structure to send to Zeep 88 | 89 | * Elements which contain a list, such as: 90 | 91 | ```xml 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | ``` 103 | 104 | are represented a little differently than expected by Zeep. Note that `` becomes an array, not ``: 105 | 106 | ```python 107 | { 108 | 'members': { 109 | 'member': [ 110 | { 111 | 'subElement1': 'value', 112 | 'subElement2': 'value' 113 | }, 114 | { 115 | 'subElement1': 'value', 116 | 'subElement2': 'value' 117 | } 118 | ] 119 | } 120 | } 121 | ``` 122 | -------------------------------------------------------------------------------- /services_soapGetServiceStatus.py: -------------------------------------------------------------------------------- 1 | """Serviceability Control Center Services sample script 2 | 3 | Performs a request using the Zeep SOAP library, and 4 | parses/prints the results in a simple table output. 5 | 6 | Copyright (c) 2021 Cisco and/or its affiliates. 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | """ 23 | 24 | from lxml import etree 25 | import requests 26 | from requests import Session 27 | from requests.auth import HTTPBasicAuth 28 | 29 | from zeep import Client, Settings, Plugin 30 | from zeep.transports import Transport 31 | from zeep.exceptions import Fault 32 | 33 | import os 34 | import sys 35 | 36 | # Edit .env file to specify your Webex site/user details 37 | from dotenv import load_dotenv 38 | 39 | load_dotenv() 40 | 41 | # Set DEBUG=True in .env to enable output of request/response headers and XML 42 | DEBUG = os.getenv("DEBUG") == "True" 43 | 44 | 45 | # The WSDL is a local file in the working directory, see README 46 | WSDL_FILE = "schema/ControlCenterServices.wsdl" 47 | 48 | # This class lets you view the incoming and outgoing HTTP headers and XML 49 | 50 | 51 | # This class lets you view the incoming and outgoing HTTP headers and XML 52 | class MyLoggingPlugin(Plugin): 53 | def egress(self, envelope, http_headers, operation, binding_options): 54 | if not DEBUG: 55 | return 56 | 57 | # Format the request body as pretty printed XML 58 | xml = etree.tostring(envelope, pretty_print=True, encoding="unicode") 59 | 60 | print(f"\nRequest\n-------\nHeaders:\n{http_headers}\n\nBody:\n{xml}") 61 | 62 | def ingress(self, envelope, http_headers, operation): 63 | if not DEBUG: 64 | return 65 | 66 | # Format the response body as pretty printed XML 67 | xml = etree.tostring(envelope, pretty_print=True, encoding="unicode") 68 | 69 | print(f"\nResponse\n-------\nHeaders:\n{http_headers}\n\nBody:\n{xml}") 70 | 71 | 72 | # The first step is to create a SOAP client session 73 | 74 | session = Session() 75 | 76 | # We disable certificate verification by default 77 | session.verify = False 78 | # Suppress the console warning about the resulting insecure requests 79 | requests.packages.urllib3.disable_warnings( 80 | requests.packages.urllib3.exceptions.InsecureRequestWarning 81 | ) 82 | 83 | # To enabled SSL cert checking (recommended for production) 84 | # place the CUCM Tomcat cert .pem file in the root of the project 85 | # and uncomment the two lines below 86 | 87 | # CERT = 'changeme.pem' 88 | # session.verify = CERT 89 | 90 | session.auth = HTTPBasicAuth(os.getenv("CUCM_USERNAME"), os.getenv("PASSWORD")) 91 | 92 | transport = Transport(session=session, timeout=10) 93 | 94 | # strict=False is not always necessary, but it allows zeep to parse imperfect XML 95 | settings = Settings(strict=False, xml_huge_tree=True) 96 | 97 | # If debug output is requested, add the MyLoggingPlugin class 98 | plugin = [MyLoggingPlugin()] if DEBUG else [] 99 | 100 | # Create the Zeep client with the specified settings 101 | client = Client(WSDL_FILE, settings=settings, transport=transport, plugins=plugin) 102 | 103 | # Create the Zeep service binding to the Perfmon SOAP service at the specified CUCM 104 | service = client.create_service( 105 | "{http://schemas.cisco.com/ast/soap}ControlCenterServicesBinding", 106 | f'https://{ os.getenv( "CUCM_ADDRESS" ) }:8443/controlcenterservice2/services/ControlCenterServices', 107 | ) 108 | 109 | # Execute the request 110 | try: 111 | resp = service.soapGetServiceStatus("") 112 | except Fault as err: 113 | print(f"Zeep error: soapGetServiceStatus: { err }") 114 | sys.exit(1) 115 | 116 | print("\nsoapGetServiceStatus response:\n") 117 | print(resp, "\n") 118 | 119 | input("Press Enter to continue...") 120 | 121 | # Create a simple report of the XML response 122 | print("\nService Status") 123 | print(("=" * 57) + "\n") 124 | 125 | # Loop through the top-level of the response object 126 | for item in resp.ServiceInfoList.item: 127 | # Print the name and version, padding/truncating the name to 49 characters 128 | print("{:50.50}".format(item.ServiceName) + item.ServiceStatus) 129 | -------------------------------------------------------------------------------- /services_getProductInformationList.py: -------------------------------------------------------------------------------- 1 | """Serviceability Control Center Services sample script 2 | 3 | Performs a request using the Zeep SOAP library, and 4 | parses/prints the results in a simple table output. 5 | 6 | Dependency Installation: 7 | 8 | $ pip3 install -r requirements.txt 9 | 10 | Copyright (c) 2018 Cisco and/or its affiliates. 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | """ 27 | 28 | from lxml import etree 29 | import requests 30 | from requests import Session 31 | from requests.auth import HTTPBasicAuth 32 | 33 | from zeep import Client, Settings, Plugin 34 | from zeep.transports import Transport 35 | from zeep.exceptions import Fault 36 | 37 | import os 38 | import sys 39 | 40 | # Edit .env file to specify your Webex site/user details 41 | from dotenv import load_dotenv 42 | 43 | load_dotenv() 44 | 45 | # Set DEBUG=True in .env to enable output of request/response headers and XML 46 | DEBUG = os.getenv("DEBUG") == "True" 47 | 48 | 49 | # The WSDL is a local file in the working directory, see README 50 | WSDL_FILE = "schema/ControlCenterServices.wsdl" 51 | 52 | # This class lets you view the incoming and outgoing HTTP headers and XML 53 | 54 | 55 | # This class lets you view the incoming and outgoing HTTP headers and XML 56 | class MyLoggingPlugin(Plugin): 57 | def egress(self, envelope, http_headers, operation, binding_options): 58 | if not DEBUG: 59 | return 60 | 61 | # Format the request body as pretty printed XML 62 | xml = etree.tostring(envelope, pretty_print=True, encoding="unicode") 63 | 64 | print(f"\nRequest\n-------\nHeaders:\n{http_headers}\n\nBody:\n{xml}") 65 | 66 | def ingress(self, envelope, http_headers, operation): 67 | if not DEBUG: 68 | return 69 | 70 | # Format the response body as pretty printed XML 71 | xml = etree.tostring(envelope, pretty_print=True, encoding="unicode") 72 | 73 | print(f"\nResponse\n-------\nHeaders:\n{http_headers}\n\nBody:\n{xml}") 74 | 75 | 76 | # The first step is to create a SOAP client session 77 | 78 | session = Session() 79 | 80 | # We disable certificate verification by default 81 | session.verify = False 82 | # Suppress the console warning about the resulting insecure requests 83 | requests.packages.urllib3.disable_warnings( 84 | requests.packages.urllib3.exceptions.InsecureRequestWarning 85 | ) 86 | 87 | # To enabled SSL cert checking (recommended for production) 88 | # place the CUCM Tomcat cert .pem file in the root of the project 89 | # and uncomment the two lines below 90 | 91 | # CERT = 'changeme.pem' 92 | # session.verify = CERT 93 | 94 | session.auth = HTTPBasicAuth(os.getenv("CUCM_USERNAME"), os.getenv("PASSWORD")) 95 | 96 | transport = Transport(session=session, timeout=10) 97 | 98 | # strict=False is not always necessary, but it allows zeep to parse imperfect XML 99 | settings = Settings(strict=False, xml_huge_tree=True) 100 | 101 | # If debug output is requested, add the MyLoggingPlugin class 102 | plugin = [MyLoggingPlugin()] if DEBUG else [] 103 | 104 | # Create the Zeep client with the specified settings 105 | client = Client(WSDL_FILE, settings=settings, transport=transport, plugins=plugin) 106 | 107 | # Create the Zeep service binding to the Perfmon SOAP service at the specified CUCM 108 | service = client.create_service( 109 | "{http://schemas.cisco.com/ast/soap}ControlCenterServicesBinding", 110 | f'https://{ os.getenv( "CUCM_ADDRESS" ) }:8443/controlcenterservice2/services/ControlCenterServices', 111 | ) 112 | 113 | # Execute the request 114 | try: 115 | resp = service.getProductInformationList("") 116 | except Fault as err: 117 | print(f"Zeep error: getProductInformationList: {err}") 118 | sys.exit(1) 119 | 120 | print("\ngetProductInformationList response:\n") 121 | print(resp, "\n") 122 | 123 | input("Press Enter to continue...") 124 | 125 | # Create a simple report of the XML response 126 | print("\nInstalled Products") 127 | print(("=" * 65) + "\n") 128 | 129 | # Loop through the top-level of the response object 130 | for item in resp.Products.item: 131 | # Extract the Product name/version values 132 | productName = item.ProductName 133 | productVersion = item.ProductVersion 134 | 135 | # Print the name and version, padding/truncating the name to 49 characters 136 | print("{:49.49}".format(productName) + "v" + productVersion) 137 | -------------------------------------------------------------------------------- /perfmonPort_collect_counter_data.py: -------------------------------------------------------------------------------- 1 | """Serviceability Perfmon sample script 2 | 3 | Performs a request for the 'Cisco CallManager' object 4 | using the Zeep SOAP library, and parses/prints the results in a simple table output. 5 | 6 | Dependency Installation: 7 | 8 | $ pip3 install -r requirements.txt 9 | 10 | Copyright (c) 2024 Cisco and/or its affiliates. 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | """ 27 | 28 | from lxml import etree 29 | import requests 30 | from requests import Session 31 | from requests.auth import HTTPBasicAuth 32 | 33 | from zeep import Client, Settings, Plugin 34 | from zeep.transports import Transport 35 | from zeep.exceptions import Fault 36 | 37 | import os 38 | import sys 39 | import re 40 | 41 | # Edit .env file to specify your Webex site/user details 42 | from dotenv import load_dotenv 43 | 44 | load_dotenv() 45 | 46 | # Set DEBUG=True in .env to enable output of request/response headers and XML 47 | DEBUG = os.getenv("DEBUG") == "True" 48 | 49 | # The WSDL is a local file in the working directory, see README 50 | WSDL_FILE = "schema/PerfmonService.wsdl" 51 | 52 | 53 | # This class lets you view the incoming and outgoing HTTP headers and XML 54 | class MyLoggingPlugin(Plugin): 55 | def egress(self, envelope, http_headers, operation, binding_options): 56 | if not DEBUG: 57 | return 58 | 59 | # Format the request body as pretty printed XML 60 | xml = etree.tostring(envelope, pretty_print=True, encoding="unicode") 61 | 62 | print(f"\nRequest\n-------\nHeaders:\n{http_headers}\n\nBody:\n{xml}") 63 | 64 | def ingress(self, envelope, http_headers, operation): 65 | if not DEBUG: 66 | return 67 | 68 | # Format the response body as pretty printed XML 69 | xml = etree.tostring(envelope, pretty_print=True, encoding="unicode") 70 | 71 | print(f"\nResponse\n-------\nHeaders:\n{http_headers}\n\nBody:\n{xml}") 72 | 73 | 74 | # The first step is to create a SOAP client session 75 | session = Session() 76 | 77 | # We disable certificate verification by default 78 | session.verify = False 79 | # Suppress the console warning about the resulting insecure requests 80 | requests.packages.urllib3.disable_warnings( 81 | requests.packages.urllib3.exceptions.InsecureRequestWarning 82 | ) 83 | 84 | # To enabled SSL cert checking (recommended for production) 85 | # place the CUCM Tomcat cert .pem file in the root of the project 86 | # and uncomment the two lines below 87 | 88 | # CERT = 'changeme.pem' 89 | # session.verify = CERT 90 | 91 | session.auth = HTTPBasicAuth(os.getenv("CUCM_USERNAME"), os.getenv("PASSWORD")) 92 | 93 | transport = Transport(session=session, timeout=10) 94 | 95 | # strict=False is not always necessary, but it allows zeep to parse imperfect XML 96 | settings = Settings(strict=False, xml_huge_tree=True) 97 | 98 | # If debug output is requested, add the MyLoggingPlugin class 99 | plugin = [MyLoggingPlugin()] if DEBUG else [] 100 | 101 | # Create the Zeep client with the specified settings 102 | client = Client(WSDL_FILE, settings=settings, transport=transport, plugins=plugin) 103 | 104 | # Create the Zeep service binding to the Perfmon SOAP service at the specified CUCM 105 | service = client.create_service( 106 | "{http://schemas.cisco.com/ast/soap}PerfmonBinding", 107 | f'https://{ os.getenv( "CUCM_ADDRESS" ) }:8443/perfmonservice2/services/PerfmonService', 108 | ) 109 | 110 | # Execute the perfmonCollectCounterData request 111 | try: 112 | resp = service.perfmonCollectCounterData( 113 | Host=os.getenv("CUCM_ADDRESS"), Object="Cisco CallManager" 114 | ) 115 | except Fault as err: 116 | print(f"Zeep error: perfmonCollectCounterData: {err}") 117 | sys.exit(1) 118 | 119 | 120 | print("\nperfmonCollectCounterData response:\n") 121 | print(resp, "\n") 122 | 123 | input("Press Enter to continue...") 124 | 125 | # Create a simple report of the XML response 126 | 127 | # Loop through the top-level of the response object 128 | print("Object Instance Value") 129 | print("=============================================== ================== ====================") 130 | print 131 | for item in resp: 132 | _, _, node, object, counter = item.Name._value_1.split('\\') 133 | instance = "" 134 | search = re.search(r"(.*)\((.*)\)", object) 135 | if search: 136 | object = search.group(1) 137 | instance = search.group(2) 138 | 139 | # Print the name and value, padding/truncating the name to 49 characters 140 | print(f"{object:49}{instance:20}{counter}") 141 | # print("{:49.49}".format(counterName) + " : " + str(item.Value)) 142 | -------------------------------------------------------------------------------- /services_doControlServices.py: -------------------------------------------------------------------------------- 1 | """Serviceability Control Center Services sample script 2 | 3 | Performs a request using the Zeep SOAP library, 4 | periodically checks the status using and parses/prints 5 | the results in a simple table output. 6 | 7 | Copyright (c) 2021 Cisco and/or its affiliates. 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | import time 25 | from lxml import etree 26 | import requests 27 | from requests import Session 28 | from requests.auth import HTTPBasicAuth 29 | 30 | from zeep import Client, Settings, Plugin 31 | from zeep.transports import Transport 32 | from zeep.exceptions import Fault 33 | 34 | import os 35 | import sys 36 | 37 | # Edit .env file to specify your Webex site/user details 38 | from dotenv import load_dotenv 39 | 40 | load_dotenv() 41 | 42 | # Set DEBUG=True in .env to enable output of request/response headers and XML 43 | DEBUG = os.getenv("DEBUG") == "True" 44 | 45 | 46 | # The WSDL is a local file in the working directory, see README 47 | WSDL_FILE = "schema/ControlCenterServices.wsdl" 48 | 49 | # This class lets you view the incoming and outgoing HTTP headers and XML 50 | 51 | 52 | # This class lets you view the incoming and outgoing HTTP headers and XML 53 | class MyLoggingPlugin(Plugin): 54 | def egress(self, envelope, http_headers, operation, binding_options): 55 | if not DEBUG: 56 | return 57 | 58 | # Format the request body as pretty printed XML 59 | xml = etree.tostring(envelope, pretty_print=True, encoding="unicode") 60 | 61 | print(f"\nRequest\n-------\nHeaders:\n{http_headers}\n\nBody:\n{xml}") 62 | 63 | def ingress(self, envelope, http_headers, operation): 64 | if not DEBUG: 65 | return 66 | 67 | # Format the response body as pretty printed XML 68 | xml = etree.tostring(envelope, pretty_print=True, encoding="unicode") 69 | 70 | print(f"\nResponse\n-------\nHeaders:\n{http_headers}\n\nBody:\n{xml}") 71 | 72 | 73 | # The first step is to create a SOAP client session 74 | 75 | session = Session() 76 | 77 | # We disable certificate verification by default 78 | session.verify = False 79 | # Suppress the console warning about the resulting insecure requests 80 | requests.packages.urllib3.disable_warnings( 81 | requests.packages.urllib3.exceptions.InsecureRequestWarning 82 | ) 83 | 84 | # To enabled SSL cert checking (recommended for production) 85 | # place the CUCM Tomcat cert .pem file in the root of the project 86 | # and uncomment the two lines below 87 | 88 | # CERT = 'changeme.pem' 89 | # session.verify = CERT 90 | 91 | session.auth = HTTPBasicAuth(os.getenv("CUCM_USERNAME"), os.getenv("PASSWORD")) 92 | 93 | transport = Transport(session=session, timeout=10) 94 | 95 | # strict=False is not always necessary, but it allows zeep to parse imperfect XML 96 | settings = Settings(strict=False, xml_huge_tree=True) 97 | 98 | # If debug output is requested, add the MyLoggingPlugin class 99 | plugin = [MyLoggingPlugin()] if DEBUG else [] 100 | 101 | # Create the Zeep client with the specified settings 102 | client = Client(WSDL_FILE, settings=settings, transport=transport, plugins=plugin) 103 | 104 | # Create the Zeep service binding to the Perfmon SOAP service at the specified CUCM 105 | service = client.create_service( 106 | "{http://schemas.cisco.com/ast/soap}ControlCenterServicesBinding", 107 | f'https://{ os.getenv( "CUCM_ADDRESS" ) }:8443/controlcenterservice2/services/ControlCenterServices', 108 | ) 109 | 110 | ServiceList = ["Cisco CDR Agent", "Cisco DRF Local"] 111 | ControlServicesDict = {"ControlType": "Restart", "ServiceList": {"item": ServiceList}} 112 | 113 | # Execute the request 114 | try: 115 | resp = service.soapDoControlServices(ControlServicesDict) 116 | except Fault as err: 117 | print(f"Zeep error: soapDoControlServices: { err }") 118 | sys.exit(1) 119 | 120 | print("\nsoapDoControlServices response:\n") 121 | 122 | # Create a simple report of the XML response 123 | print("\nService Status") 124 | print(("=" * 57) + "\n") 125 | 126 | # Loop through the top-level of the response object 127 | for item in resp.ServiceInfoList.item: 128 | # Print the name and version, padding/truncating the name to 49 characters 129 | print("{:50.50}".format(item.ServiceName) + item.ServiceStatus) 130 | 131 | # Check services status until they all are "Started" 132 | while True: 133 | try: 134 | resp = service.soapGetServiceStatus(ServiceList) 135 | except Fault as err: 136 | print(f"Zeep error: soapGetServiceStatus: { err }") 137 | sys.exit(1) 138 | 139 | for item in resp.ServiceInfoList.item: 140 | print("{:50.50}".format(item.ServiceName) + item.ServiceStatus) 141 | if item.ServiceStatus == "Started": 142 | ServiceList.remove(item.ServiceName) 143 | if len(ServiceList) > 0: 144 | time.sleep(5) 145 | else: 146 | break 147 | -------------------------------------------------------------------------------- /risport70_selectCmDevice.py: -------------------------------------------------------------------------------- 1 | """Risport70 sample script 2 | 3 | Dependency Installation: 4 | 5 | $ pip install -r requirements.txt 6 | 7 | Copyright (c) 2018 Cisco and/or its affiliates. 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | 25 | from lxml import etree 26 | import requests 27 | from requests import Session 28 | from requests.auth import HTTPBasicAuth 29 | 30 | from zeep import Client, Settings, Plugin 31 | from zeep.transports import Transport 32 | from zeep.exceptions import Fault 33 | import time 34 | 35 | import os 36 | import sys 37 | 38 | # Edit .env file to specify your Webex site/user details 39 | from dotenv import load_dotenv 40 | 41 | load_dotenv() 42 | 43 | # Set DEBUG=True in .env to enable output of request/response headers and XML 44 | DEBUG = os.getenv("DEBUG") == "True" 45 | 46 | # The WSDL is a local file in the root directory, see README 47 | WSDL_FILE = "schema/RISService70.wsdl" 48 | 49 | # This class lets you view the incoming and outgoing http headers and XML 50 | 51 | 52 | # This class lets you view the incoming and outgoing HTTP headers and XML 53 | class MyLoggingPlugin(Plugin): 54 | def egress(self, envelope, http_headers, operation, binding_options): 55 | if not DEBUG: 56 | return 57 | 58 | # Format the request body as pretty printed XML 59 | xml = etree.tostring(envelope, pretty_print=True, encoding="unicode") 60 | 61 | print(f"\nRequest\n-------\nHeaders:\n{http_headers}\n\nBody:\n{xml}") 62 | 63 | def ingress(self, envelope, http_headers, operation): 64 | if not DEBUG: 65 | return 66 | 67 | # Format the response body as pretty printed XML 68 | xml = etree.tostring(envelope, pretty_print=True, encoding="unicode") 69 | 70 | print(f"\nResponse\n-------\nHeaders:\n{http_headers}\n\nBody:\n{xml}") 71 | 72 | 73 | # The first step is to create a SOAP client session 74 | session = Session() 75 | 76 | # We disable certificate verification by default 77 | session.verify = False 78 | # Suppress the console warning about the resulting insecure requests 79 | requests.packages.urllib3.disable_warnings( 80 | requests.packages.urllib3.exceptions.InsecureRequestWarning 81 | ) 82 | 83 | # To enabled SSL cert checking (recommended for production) 84 | # place the CUCM Tomcat cert .pem file in the root of the project 85 | # and uncomment the two lines below 86 | 87 | # CERT = 'changeme.pem' 88 | # session.verify = CERT 89 | 90 | session.auth = HTTPBasicAuth(os.getenv("CUCM_USERNAME"), os.getenv("PASSWORD")) 91 | 92 | transport = Transport(session=session, timeout=10) 93 | 94 | # strict=False is not always necessary, but it allows zeep to parse imperfect XML 95 | settings = Settings(strict=False, xml_huge_tree=True) 96 | 97 | plugin = [MyLoggingPlugin()] if DEBUG else [] 98 | 99 | client = Client(WSDL_FILE, settings=settings, transport=transport, plugins=plugin) 100 | 101 | # Create the Zeep service binding to the Perfmon SOAP service at the specified CUCM 102 | service = client.create_service( 103 | "{http://schemas.cisco.com/ast/soap}RisBinding", 104 | f'https://{ os.getenv( "CUCM_ADDRESS" ) }:8443/realtimeservice2/services/RISService70', 105 | ) 106 | 107 | # Build and execute the request object 108 | 109 | stateInfo = "" 110 | 111 | criteria = { 112 | "MaxReturnedDevices": "1000", 113 | "DeviceClass": "Phone", 114 | "Model": "255", 115 | "Status": "Any", 116 | "NodeName": "", 117 | "SelectBy": "Name", 118 | "Protocol": "Any", 119 | "DownloadStatus": "Any", 120 | "SelectItems": {"item": []}, 121 | } 122 | 123 | # One or more specific devices can be retrieved by replacing * with 124 | # the device name in multiple items 125 | criteria["SelectItems"]["item"].append({"Item": "*"}) 126 | 127 | # Execute the request 128 | try: 129 | resp = service.selectCmDevice(stateInfo, criteria) 130 | except Fault as err: 131 | print(f"Zeep error: selectCmDevice: { err }") 132 | sys.exit(1) 133 | 134 | print("\nselectCmDevice response:\n") 135 | print(resp, "\n") 136 | 137 | for node in resp["SelectCmDeviceResult"]["CmNodes"]["item"]: 138 | if node["ReturnCode"] != "Ok": 139 | continue 140 | 141 | print("Node: ", node["Name"]) 142 | print() 143 | 144 | print( 145 | "{name:16}{ip:16}{dirn:11}{status:13}{desc:16}{ts:17}".format( 146 | name="Name", 147 | ip="IP Address", 148 | dirn="DN", 149 | status="Status", 150 | desc="Description", 151 | ts="Time", 152 | ) 153 | ) 154 | 155 | print( 156 | "{name:16}{ip:16}{dirn:11}{status:13}{desc:16}{ts:17}".format( 157 | name="-" * 15, 158 | ip="-" * 15, 159 | dirn="-" * 10, 160 | status="-" * 12, 161 | desc="-" * 15, 162 | ts="-" * 16, 163 | ) 164 | ) 165 | 166 | for device in node["CmDevices"]["item"]: 167 | ipaddresses = device["IPAddress"] 168 | ipaddress = ipaddresses["item"][0]["IP"] if ipaddresses else "" 169 | description = device["Description"] if device["Description"] != None else "" 170 | devicename = device["Name"] 171 | timestamp = time.strftime("%Y/%m/%d %H:%M", time.localtime(device["TimeStamp"])) 172 | 173 | if device["LinesStatus"] == None: 174 | print( 175 | "{name:16}{ip:16}{dirn:11}{status:13}{desc:16}{ts:17}".format( 176 | name=devicename, 177 | ip=ipaddress, 178 | dirn=" ", 179 | status=" ", 180 | desc=description, 181 | ts=timestamp, 182 | ) 183 | ) 184 | else: 185 | for x in range(0, len(device["LinesStatus"]["item"])): 186 | if x == 0: 187 | print( 188 | "{name:16}{ip:16}{dirn:11}{status:13}{desc:16}{ts:17}".format( 189 | name=devicename, 190 | ip=ipaddress, 191 | dirn=device["LinesStatus"]["item"][x]["DirectoryNumber"], 192 | status=device["LinesStatus"]["item"][x]["Status"], 193 | desc=description, 194 | ts=timestamp, 195 | ) 196 | ) 197 | else: 198 | print( 199 | "{pad:32}{dirn:11}{status:13}".format( 200 | pad=" ", 201 | dirn=device["LinesStatus"]["item"][x]["DirectoryNumber"], 202 | status=device["LinesStatus"]["item"][x]["Status"], 203 | ) 204 | ) 205 | -------------------------------------------------------------------------------- /logCollection_GetOneFile.py: -------------------------------------------------------------------------------- 1 | """Serviceability Log Collection Service sample script 2 | 3 | Performs a to get a listing of log files available for a 4 | specific service (Cisco Audit Logs), retrieves the contents of the latest file 5 | using , then parses/prints a few lines of the results. 6 | 7 | Dependency Installation: 8 | 9 | $ pip3 install -r requirements.txt 10 | 11 | Copyright (c) 2024 Cisco and/or its affiliates. 12 | Permission is hereby granted, free of charge, to any person obtaining a copy 13 | of this software and associated documentation files (the "Software"), to deal 14 | in the Software without restriction, including without limitation the rights 15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | copies of the Software, and to permit persons to whom the Software is 17 | furnished to do so, subject to the following conditions: 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | """ 28 | 29 | import copy 30 | from lxml import etree 31 | import requests 32 | from requests import Session 33 | from requests.auth import HTTPBasicAuth 34 | 35 | from zeep import Client, Settings, Plugin 36 | from zeep.transports import Transport 37 | from zeep.exceptions import Fault 38 | 39 | import os 40 | import sys 41 | 42 | # Edit .env file to specify your Webex site/user details 43 | from dotenv import load_dotenv 44 | 45 | load_dotenv() 46 | 47 | # Set DEBUG=True in .env to enable output of request/response headers and XML 48 | DEBUG = os.getenv("DEBUG") == "True" 49 | 50 | # The WSDL is a local file in the working directory, see README 51 | WSDL_FILE = "schema/LogCollectionPortTypeService.wsdl" 52 | 53 | # This class lets you view the incoming and outgoing HTTP headers and XML 54 | 55 | 56 | # This class lets you view the incoming and outgoing HTTP headers and XML 57 | class MyLoggingPlugin(Plugin): 58 | def egress(self, envelope, http_headers, operation, binding_options): 59 | if not DEBUG: 60 | return 61 | 62 | # Format the request body as pretty printed XML 63 | xml = etree.tostring(envelope, pretty_print=True, encoding="unicode") 64 | 65 | print(f"\nRequest\n-------\nHeaders:\n{http_headers}\n\nBody:\n{xml}") 66 | 67 | def ingress(self, envelope, http_headers, operation): 68 | if not DEBUG: 69 | return 70 | 71 | # Modify the plugin to selectively remove large binary file content 72 | # from GetOneFile response 73 | if envelope.find("./{*}Body/{*}GetOneFileReturn") is not None: 74 | display_xml = copy.deepcopy(envelope) 75 | display_xml.find( 76 | "./{*}Body/{*}GetOneFileReturn" 77 | ).text = "REMOVED_FOR_BREVITY" 78 | # Format the response body as pretty printed XML 79 | xml_string = etree.tostring( 80 | display_xml, pretty_print=True, encoding="unicode" 81 | ) 82 | else: 83 | # Format the response body as pretty printed XML 84 | xml_string = etree.tostring(envelope, pretty_print=True, encoding="unicode") 85 | 86 | print(f"\nResponse\n-------\nHeaders:\n{http_headers}\n\nBody:\n{xml_string}") 87 | 88 | 89 | # The first step is to create a SOAP client session 90 | session = Session() 91 | 92 | # We disable certificate verification by default 93 | session.verify = False 94 | # Suppress the console warning about the resulting insecure requests 95 | requests.packages.urllib3.disable_warnings( 96 | requests.packages.urllib3.exceptions.InsecureRequestWarning 97 | ) 98 | 99 | # To enabled SSL cert checking (recommended for production) 100 | # place the CUCM Tomcat cert .pem file in the root of the project 101 | # and uncomment the two lines below 102 | 103 | # CERT = 'changeme.pem' 104 | # session.verify = CERT 105 | 106 | session.auth = HTTPBasicAuth(os.getenv("CUCM_USERNAME"), os.getenv("PASSWORD")) 107 | print(os.getenv("CUCM_USERNAME"), os.getenv("PASSWORD")) 108 | transport = Transport(session=session, timeout=10) 109 | 110 | # strict=False is not always necessary, but it allows zeep to parse imperfect XML 111 | settings = Settings(strict=False, xml_huge_tree=True) 112 | 113 | # If debug output is requested, add the MyLoggingPlugin class 114 | plugin = [MyLoggingPlugin()] if DEBUG else [] 115 | 116 | # Create the Zeep client with the specified settings 117 | client = Client(WSDL_FILE, settings=settings, transport=transport, plugins=plugin) 118 | 119 | # Create the Zeep service binding to the Log Collection SOAP service at the specified CUCM 120 | service = client.create_service( 121 | "{http://schemas.cisco.com/ast/soap}LogCollectionPortSoapBinding", 122 | f'https://{ os.getenv( "CUCM_ADDRESS" ) }:8443/logcollectionservice2/services/LogCollectionPortTypeService', 123 | ) 124 | 125 | # Get a listing of all available log files for the 'Cisco Audit Logs' service 126 | # Note: All the below fields must appear, but onsome some require data, depending on 127 | # the desired functionality 128 | try: 129 | resp = service.selectLogFiles( 130 | FileSelectionCriteria={ 131 | "ServiceLogs": ["Cisco Audit Logs"], 132 | "SystemLogs": [], 133 | "SearchStr": "", 134 | "Frequency": "OnDemand", 135 | "JobType": "DownloadtoClient", 136 | "ToDate": "", 137 | "FromDate": "", 138 | "TimeZone": "", 139 | "RelText": "None", 140 | "RelTime": 0, 141 | "Port": "", 142 | "IPAddress": "", 143 | "UserName": "", 144 | "Password": "", 145 | "ZipInfo": False, 146 | "RemoteFolder": "", 147 | } 148 | ) 149 | except Fault as err: 150 | print(f"Zeep error: selectLogFiles: { err }") 151 | sys.exit(1) 152 | 153 | print("\nselectLogFiles response:\n") 154 | print(resp, "\n") 155 | 156 | input("Press Enter to continue...") 157 | 158 | # Create a simple report of the XML response 159 | print("\nAvailable log files:\n") 160 | 161 | # Print the name/size/date, padding/truncating the name to 20 characters 162 | print(f'Filename{ 31 * " " } Size{ 6 * " " } Date{ 24 * " " }') 163 | print(f'{ 39 * "-" } { 10 * "-" } { 28 * "-" }') 164 | 165 | if len(resp.Node.ServiceList.ServiceLogs[0].SetOfFiles.File) == 0: 166 | print("\nNo matching files found...") 167 | sys.exit(0) 168 | 169 | lastFileName = None 170 | lastFilePath = None 171 | 172 | # Loop through and print the returned files list 173 | for logFile in resp.Node.ServiceList.ServiceLogs[0].SetOfFiles.File: 174 | print( 175 | f'{ logFile.name.ljust( 39, " ") } {logFile.filesize.ljust( 10, " " ) } {logFile.modifiedDate.ljust( 28, " ") }' 176 | ) 177 | 178 | lastFileName = logFile.name 179 | lastFilePath = logFile.absolutepath 180 | 181 | input("\nPress Enter to continue...") 182 | 183 | # Retrieve the latest (or, at least the last) log file via DIME 184 | try: 185 | resp = service.GetOneFile(lastFilePath) 186 | except Fault as err: 187 | print(f"Zeep error: GetOneFile: { err }") 188 | sys.exit(1) 189 | 190 | print("\nGetOneFile: success\n") 191 | 192 | # The output will be bytes - convert to UTF-8 string 193 | fileOutput = resp.decode("utf-8") 194 | # Null out the resp variable in case it was taking up a large amount of memory 195 | resp = None 196 | 197 | print(f"File contents (first 10 lines...) of [ { lastFileName } ]:\n") 198 | 199 | # Split contents into an array of separate strings for printing out 200 | for line in fileOutput.splitlines()[:10]: 201 | print(line) 202 | -------------------------------------------------------------------------------- /perfmonPort_collect_session_data.py: -------------------------------------------------------------------------------- 1 | """Serviceability Perfmon sample script 2 | 3 | Creates a perfmon counter session with , adds some example 4 | counters with , periodically refreshes/parses/prints counter 5 | data with , then waits for a keypress before cleaning 6 | up the session with 7 | 8 | Note: this sample uses the 'curses' module, which is not supported on Windows 9 | 10 | Copyright (c) 2024 Cisco and/or its affiliates. 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | """ 27 | 28 | from lxml import etree 29 | import requests 30 | from requests import Session 31 | from requests.auth import HTTPBasicAuth 32 | 33 | from zeep import Client, Settings, Plugin 34 | from zeep.transports import Transport 35 | from zeep.exceptions import Fault 36 | 37 | import curses 38 | 39 | import time 40 | from time import sleep 41 | 42 | import os 43 | import sys 44 | 45 | # Edit .env file to specify your Webex site/user details 46 | from dotenv import load_dotenv 47 | 48 | load_dotenv() 49 | 50 | # The WSDL is a local file in the working directory, see README 51 | WSDL_FILE = "schema/PerfmonService.wsdl" 52 | 53 | # Set DEBUG=True in .env to enable output of request/response headers and XML 54 | DEBUG = os.getenv("DEBUG") == "True" 55 | 56 | 57 | # This class lets you view the incoming and outgoing HTTP headers and XML 58 | class MyLoggingPlugin(Plugin): 59 | def egress(self, envelope, http_headers, operation, binding_options): 60 | if not DEBUG: 61 | return 62 | 63 | # Format the request body as pretty printed XML 64 | xml = etree.tostring(envelope, pretty_print=True, encoding="unicode") 65 | 66 | print(f"\nRequest\n-------\nHeaders:\n{http_headers}\n\nBody:\n{xml}") 67 | 68 | def ingress(self, envelope, http_headers, operation): 69 | if not DEBUG: 70 | return 71 | 72 | # Format the response body as pretty printed XML 73 | xml = etree.tostring(envelope, pretty_print=True, encoding="unicode") 74 | 75 | print(f"\nResponse\n-------\nHeaders:\n{http_headers}\n\nBody:\n{xml}") 76 | 77 | 78 | # The first step is to create a SOAP client session 79 | session = Session() 80 | 81 | # We disable certificate verification by default 82 | session.verify = False 83 | # Suppress the console warning about the resulting insecure requests 84 | requests.packages.urllib3.disable_warnings( 85 | requests.packages.urllib3.exceptions.InsecureRequestWarning 86 | ) 87 | 88 | # To enabled SSL cert checking (recommended for production) 89 | # place the CUCM Tomcat cert .pem file in the root of the project 90 | # and uncomment the two lines below 91 | 92 | # CERT = 'changeme.pem' 93 | # session.verify = CERT 94 | 95 | session.auth = HTTPBasicAuth(os.getenv("CUCM_USERNAME"), os.getenv("PASSWORD")) 96 | 97 | transport = Transport(session=session, timeout=10) 98 | 99 | # strict=False is not always necessary, but it allows zeep to parse imperfect XML 100 | settings = Settings(strict=False, xml_huge_tree=True) 101 | 102 | # Create the Zeep client with the specified settings 103 | client = Client( 104 | WSDL_FILE, settings=settings, transport=transport, plugins=[MyLoggingPlugin()] 105 | ) 106 | 107 | # Create the Zeep service binding to the Perfmon SOAP service at the specified CUCM 108 | service = client.create_service( 109 | "{http://schemas.cisco.com/ast/soap}PerfmonBinding", 110 | f'https://{ os.getenv( "CUCM_ADDRESS" ) }:8443/perfmonservice2/services/PerfmonService', 111 | ) 112 | 113 | # Open a new Perfmon counter session 114 | try: 115 | resp = service.perfmonOpenSession() 116 | except Fault as err: 117 | print(f"Zeep error: perfmonOpenSession: { err }") 118 | sys.exit(1) 119 | 120 | # Save the returned session handle 121 | sessionHandle = resp 122 | 123 | print("\nperfmonOpenSession response:\n") 124 | print(resp, "\n") 125 | 126 | input("Press Enter to continue...\n") 127 | 128 | # Add a set of performance counters to the session 129 | 130 | # Create an array of counter names we want to monitor 131 | # See the Perfmon API Reference for info on how to build the counter path name. 132 | # Here we want to monitor the current CUCM node, for the 'Processor' object, of the '0' (first) 133 | # instance, watching the '% CPU Time'/'IOWait Percentage'/'Idle Percentage' counters 134 | counters = { 135 | "Counter": [ 136 | {"Name": f'\\\\{ os.getenv( "CUCM_ADDRESS" ) }\\Processor(0)\\% CPU Time'}, 137 | { 138 | "Name": f'\\\\{ os.getenv( "CUCM_ADDRESS" ) }\\Processor(0)\\IOwait Percentage' 139 | }, 140 | {"Name": f'\\\\{ os.getenv( "CUCM_ADDRESS" ) }\\Processor(0)\\Idle Percentage'}, 141 | ] 142 | } 143 | 144 | try: 145 | resp = service.perfmonAddCounter( 146 | SessionHandle=sessionHandle, ArrayOfCounter=counters 147 | ) 148 | except Fault as err: 149 | print(f"Zeep error: perfmonAddCounter: { err }") 150 | sys.exit(1) 151 | 152 | print("\nperfmonAddCounter response: SUCCESS\n") 153 | 154 | input("Press Enter to continue...\n") 155 | 156 | 157 | # Define a function to start monitoring session counters 158 | def monitorCounters(stdscr): 159 | # Disable printing debug output to keep the display sane 160 | global DEBUG 161 | DEBUG_SAVE = DEBUG 162 | DEBUG = False 163 | 164 | # Using the provided curses window object, turn on non-blocking mode for key input 165 | stdscr.nodelay(True) 166 | 167 | # Repeat indefinitely (until a break or return) 168 | while True: 169 | # Hide the cursor and clear the window 170 | curses.curs_set(0) 171 | stdscr.clear() 172 | 173 | # Create a simple report of the XML response, and 'print' to the curses window 174 | stdscr.addstr('\nperfmonCollectSessionData output for "Processor(0)"\n') 175 | stdscr.addstr("=======================================================\n\n") 176 | 177 | # Retrieve the latest counter data for the session 178 | # No Try/Except wrapper is used, so that exceptions trigger the curses.wrapper 179 | # behaviour, and are re-raised from the wrapped function (where they 180 | # are then handled) 181 | resp = service.perfmonCollectSessionData(SessionHandle=sessionHandle) 182 | 183 | # Loop through the response and parse/print the counter names and values 184 | for item in resp: 185 | # Extract the final value in the counter path, which sould be the counter name 186 | counterPath = item.Name._value_1 187 | last = counterPath.rfind("\\") + 1 188 | counterName = counterPath[last:] 189 | 190 | # Print the name and value, padding/truncating the name to 49 characters 191 | stdscr.addstr( 192 | "{:49.49}".format(counterName) + " : " + str(item.Value) + "\n" 193 | ) 194 | 195 | # Print the current time for Last Updated 196 | stdscr.addstr(f'Last updated: { time.strftime( "%X" ) }\n\n') 197 | 198 | stdscr.addstr("\n(Press any key to exit)") 199 | 200 | # Flush the text output to the window 201 | stdscr.refresh() 202 | 203 | # Check keyboard input for a key press, repeat up to 50 times, waiting a tenth of a 204 | # second between re-checks 205 | for x in range(1, 50): 206 | # If an input character is waiting... 207 | if stdscr.getch() != curses.ERR: 208 | # Restore the previous debug setting 209 | DEBUG = DEBUG_SAVE 210 | # Return from the monitorCounters() function 211 | return 212 | else: 213 | # Otherwise, sleep for tenth of a second 214 | sleep(0.1) 215 | 216 | 217 | # Start the continuous monitoring function. 218 | # As the function uses curses to handle display/input, curses.wrapper is 219 | # used to automatically return the console window to a sane state in case 220 | # of any unhandled exceptions 221 | try: 222 | curses.wrapper(monitorCounters) 223 | except Fault as err: 224 | print(f"Zeep error: perfmonCollectSessionData: { err }") 225 | sys.exit(1) 226 | 227 | # Cleanup the session objected we created 228 | try: 229 | resp = service.perfmonCloseSession(SessionHandle=sessionHandle) 230 | except Fault as err: 231 | print(f"Zeep error: perfmonCloseSession: { err }") 232 | sys.exit(1) 233 | 234 | print("\nperfmonCloseSession response: SUCCESS\n") 235 | -------------------------------------------------------------------------------- /schema/LogCollectionPortTypeService.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 9 | LogCollectionPortTypeService 10 | 11 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 204 | 205 | 206 | 207 | 209 | 210 | 211 | 212 | 214 | 215 | 216 | 217 | 219 | 220 | 221 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 265 | 266 | 267 | -------------------------------------------------------------------------------- /schema/ControlCenterServices.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | -------------------------------------------------------------------------------- /schema/PerfmonService.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | -------------------------------------------------------------------------------- /schema/RISService70.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | --------------------------------------------------------------------------------