Add CUCM plugin, since it was lying around in another repo.
This commit is contained in:
		
							parent
							
								
									0833ae7a16
								
							
						
					
					
						commit
						de64c02488
					
				@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Parses and checks non-phone devices from CUCM.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					from cmk.base.plugins.agent_based.agent_based_api.v1 import register, Result, Service, State
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Convert JSON entries into dictionaries indexed by name.
 | 
				
			||||||
 | 
					def parse_cucm(string_table):
 | 
				
			||||||
 | 
					    lookup = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for row in string_table:
 | 
				
			||||||
 | 
					        device = json.loads(row[0])
 | 
				
			||||||
 | 
					        name = device["name"]
 | 
				
			||||||
 | 
					        lookup[name] = device
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return lookup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					register.agent_section(
 | 
				
			||||||
 | 
					    name="cucm_chk",
 | 
				
			||||||
 | 
					    parse_function=parse_cucm
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Produce a list of services based on the parsed devices.
 | 
				
			||||||
 | 
					def discover_cucm(section):
 | 
				
			||||||
 | 
					    for name, details in sorted(section.items()):
 | 
				
			||||||
 | 
					        model_name = details["model_name"]
 | 
				
			||||||
 | 
					        ip = details.get("ip")
 | 
				
			||||||
 | 
					        gui_name = "%s %s (%s)" % (model_name, name, ip)
 | 
				
			||||||
 | 
					        yield Service(item=gui_name, parameters={"name": name})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Given a specific device, look it up in the parsed devices, and produce
 | 
				
			||||||
 | 
					# results on that service based upon the devices' status.
 | 
				
			||||||
 | 
					def check_cucm(item, params, section):
 | 
				
			||||||
 | 
					    name = params["name"]
 | 
				
			||||||
 | 
					    device = section.get(name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if device is None:
 | 
				
			||||||
 | 
					        yield Result(state=State.WARN, summary="Not appearing in CUCM API")
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    status = device.get("status")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if status is None:
 | 
				
			||||||
 | 
					        yield Result(state=State.WARN, summary="No status for this in CUCM API")
 | 
				
			||||||
 | 
					    elif status == "Registered":
 | 
				
			||||||
 | 
					        yield Result(state=State.OK, summary="Registered")
 | 
				
			||||||
 | 
					    elif ["Unregistered", "Rejected", "PartiallyRegistered", "Unknown"].count(status) == 1:
 | 
				
			||||||
 | 
					        summary = status
 | 
				
			||||||
 | 
					        msg = device.get("status_reason")
 | 
				
			||||||
 | 
					        if msg:
 | 
				
			||||||
 | 
					            summary += " " + msg
 | 
				
			||||||
 | 
					        yield Result(state=State.WARN, summary=summary)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        yield Result(state=State.WARN, summary="Unknown status: %s" % status)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					register.check_plugin(
 | 
				
			||||||
 | 
					    name="cucm_chk",
 | 
				
			||||||
 | 
					    service_name="CUCM %s",
 | 
				
			||||||
 | 
					    discovery_function=discover_cucm,
 | 
				
			||||||
 | 
					    check_function=check_cucm,
 | 
				
			||||||
 | 
					    check_default_parameters={},
 | 
				
			||||||
 | 
					    check_ruleset_name="cucm_chk",
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Parses and inventories phones.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# XXX for the inventory plugin, if mac/serial/model is None, do not update the inventory
 | 
				
			||||||
 | 
					# XXX for checkmk, add a last_seen. If last_seen is older than six months, remove it.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					from cmk.base.plugins.agent_based.agent_based_api.v1 import register, TableRow
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Convert JSON entries into dictionaries indexed by name.
 | 
				
			||||||
 | 
					def parse_cucm(string_table):
 | 
				
			||||||
 | 
					    lookup = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for row in string_table:
 | 
				
			||||||
 | 
					        phone = json.loads(row[0])
 | 
				
			||||||
 | 
					        name = phone["name"]
 | 
				
			||||||
 | 
					        lookup[name] = phone
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return lookup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Produce a table of all phones parsed earlier.
 | 
				
			||||||
 | 
					def inventory_cucm(section):
 | 
				
			||||||
 | 
					    path = ["phones"]
 | 
				
			||||||
 | 
					    for name, details in sorted(section.items()):
 | 
				
			||||||
 | 
					        details.pop("name")
 | 
				
			||||||
 | 
					        yield TableRow(
 | 
				
			||||||
 | 
					            path=path,
 | 
				
			||||||
 | 
					            key_columns={"name": name},
 | 
				
			||||||
 | 
					            inventory_columns=details
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					register.agent_section(
 | 
				
			||||||
 | 
					    name="cucm_inv",
 | 
				
			||||||
 | 
					    parse_function=parse_cucm
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					register.inventory_plugin(
 | 
				
			||||||
 | 
					    name="cucm_inv",
 | 
				
			||||||
 | 
					    inventory_function=inventory_cucm,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										340
									
								
								check_mk-cucm/local/share/check_mk/agents/special/agent_cucm_chk
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										340
									
								
								check_mk-cucm/local/share/check_mk/agents/special/agent_cucm_chk
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,340 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Contact CUCM to fetch the status of non-phone devices, and return the results
 | 
				
			||||||
 | 
					# of each device as a JSON line.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# This file imports code from agent_cucm_inv, so much of the important logic
 | 
				
			||||||
 | 
					# is found there.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from importlib.util import spec_from_loader, module_from_spec
 | 
				
			||||||
 | 
					from importlib.machinery import SourceFileLoader
 | 
				
			||||||
 | 
					import os, sys, json, urllib.request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Since Python doesn't import files without .py extensions, we need to do a
 | 
				
			||||||
 | 
					# little tapdance to import agent_cucm_inv.
 | 
				
			||||||
 | 
					file = 'agent_cucm_inv'
 | 
				
			||||||
 | 
					path = os.path.dirname(__file__) + '/' + file
 | 
				
			||||||
 | 
					spec = spec_from_loader(file, SourceFileLoader(file, path))
 | 
				
			||||||
 | 
					inv = module_from_spec(spec)
 | 
				
			||||||
 | 
					spec.loader.exec_module(inv)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Call the CUCM RisPort70 API synchronously, using a SOAP query to fetch
 | 
				
			||||||
 | 
					# information about devices matching the requested device type. It returns
 | 
				
			||||||
 | 
					# XML, which we parse.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Be aware that the API will return information about a maximum of 2000 devices,
 | 
				
			||||||
 | 
					# and provides no means of pagination. Having more than 2000 non-phone devices
 | 
				
			||||||
 | 
					# would be quite exceptional, so we don't handle that here, but if you ever
 | 
				
			||||||
 | 
					# need to support more than that look into how agent_cucm_inv uses AXL to
 | 
				
			||||||
 | 
					# do pagination.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# See query_cucm_risport() in agent_cucm_inv for more info.
 | 
				
			||||||
 | 
					def query_cucm(addr, port, user, password, insecure, device):
 | 
				
			||||||
 | 
					    url = 'https://%s:%s/realtimeservice2/services/RISService70/' % (addr, port)
 | 
				
			||||||
 | 
					    headers = [('Content-Type', 'text/plain')]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        return inv.get_url(url, user, password, insecure, headers, f"""
 | 
				
			||||||
 | 
					            <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
 | 
				
			||||||
 | 
					                              xmlns:soap="http://schemas.cisco.com/ast/soap">
 | 
				
			||||||
 | 
					              <soapenv:Header/>
 | 
				
			||||||
 | 
					              <soapenv:Body>
 | 
				
			||||||
 | 
					                <soap:selectCmDevice>
 | 
				
			||||||
 | 
					                  <soap:StateInfo></soap:StateInfo>
 | 
				
			||||||
 | 
					                  <soap:CmSelectionCriteria>
 | 
				
			||||||
 | 
					                    <soap:MaxReturnedDevices>2000</soap:MaxReturnedDevices>
 | 
				
			||||||
 | 
					                    <soap:DeviceClass>{device}</soap:DeviceClass>
 | 
				
			||||||
 | 
					                    <soap:Model>255</soap:Model>
 | 
				
			||||||
 | 
					                    <soap:Status></soap:Status>
 | 
				
			||||||
 | 
					                    <soap:NodeName></soap:NodeName>
 | 
				
			||||||
 | 
					                    <soap:SelectBy>Name</soap:SelectBy>
 | 
				
			||||||
 | 
					                    <soap:SelectItems></soap:SelectItems>
 | 
				
			||||||
 | 
					                    <soap:Protocol>Any</soap:Protocol>
 | 
				
			||||||
 | 
					                    <soap:DownloadStatus>Any</soap:DownloadStatus>
 | 
				
			||||||
 | 
					                  </soap:CmSelectionCriteria>
 | 
				
			||||||
 | 
					                </soap:selectCmDevice>
 | 
				
			||||||
 | 
					              </soapenv:Body>
 | 
				
			||||||
 | 
					            </soapenv:Envelope>
 | 
				
			||||||
 | 
					        """)
 | 
				
			||||||
 | 
					    except urllib.error.HTTPError as e:
 | 
				
			||||||
 | 
					        sys.stderr.write("CUCM error: %s\n" % e)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Statuses listed here: https://developer.cisco.com/docs/sxml/#!risport70-api-reference/ReasonCode
 | 
				
			||||||
 | 
					status_reason_lookup = {
 | 
				
			||||||
 | 
					    "0":  None,
 | 
				
			||||||
 | 
					    "1":  "Unknown",
 | 
				
			||||||
 | 
					    "6":  "ConnectivityError",
 | 
				
			||||||
 | 
					    "8":  "DeviceInitiatedReset",
 | 
				
			||||||
 | 
					    "9":  "CallManagerReset",
 | 
				
			||||||
 | 
					    "10": "DeviceUnregistered",
 | 
				
			||||||
 | 
					    "11": "MalformedRegisterMsg",
 | 
				
			||||||
 | 
					    "12": "SCCPDeviceThrottling",
 | 
				
			||||||
 | 
					    "13": "KeepAliveTimeout",
 | 
				
			||||||
 | 
					    "14": "ConfigurationMismatch",
 | 
				
			||||||
 | 
					    "15": "CallManagerRestart",
 | 
				
			||||||
 | 
					    "16": "DuplicateRegistration",
 | 
				
			||||||
 | 
					    "17": "CallManagerApplyConfig",
 | 
				
			||||||
 | 
					    "18": "DeviceNoResponse",
 | 
				
			||||||
 | 
					    "19": "EMLoginLogout",
 | 
				
			||||||
 | 
					    "20": "EMCCLoginLogout",
 | 
				
			||||||
 | 
					    "25": "RegistrationSequenceError",
 | 
				
			||||||
 | 
					    "26": "InvalidCapabilities",
 | 
				
			||||||
 | 
					    "28": "FallbackInitiated",
 | 
				
			||||||
 | 
					    "29": "DeviceSwitch",
 | 
				
			||||||
 | 
					    "30": "DeviceWipe",
 | 
				
			||||||
 | 
					    "31": "DeviceForcedReset",
 | 
				
			||||||
 | 
					    "33": "LowBattery",
 | 
				
			||||||
 | 
					    "34": "ManualPowerOff",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Model names listed here: https://developer.cisco.com/docs/sxml/#!risport70-api-reference/risport70-api-reference
 | 
				
			||||||
 | 
					model_name_lookup = {
 | 
				
			||||||
 | 
					    "1": "Cisco 30 SP+",
 | 
				
			||||||
 | 
					    "2": "Cisco 12 SP+",
 | 
				
			||||||
 | 
					    "3": "Cisco 12 SP",
 | 
				
			||||||
 | 
					    "4": "Cisco 12 S",
 | 
				
			||||||
 | 
					    "5": "Cisco 30 VIP",
 | 
				
			||||||
 | 
					    "6": "Cisco 7910",
 | 
				
			||||||
 | 
					    "7": "Cisco 7960",
 | 
				
			||||||
 | 
					    "8": "Cisco 7940",
 | 
				
			||||||
 | 
					    "9": "Cisco 7935",
 | 
				
			||||||
 | 
					    "10": "Cisco VGC Phone",
 | 
				
			||||||
 | 
					    "11": "Cisco VGC Virtual Phone",
 | 
				
			||||||
 | 
					    "12": "Cisco ATA 186",
 | 
				
			||||||
 | 
					    "15": "EMCC Base Phone",
 | 
				
			||||||
 | 
					    "20": "SCCP Phone",
 | 
				
			||||||
 | 
					    "30": "Analog Access",
 | 
				
			||||||
 | 
					    "40": "Digital Access",
 | 
				
			||||||
 | 
					    "42": "Digital Access+",
 | 
				
			||||||
 | 
					    "43": "Digital Access WS-X6608",
 | 
				
			||||||
 | 
					    "47": "Analog Access WS-X6624",
 | 
				
			||||||
 | 
					    "48": "VGC Gateway",
 | 
				
			||||||
 | 
					    "50": "Conference Bridge",
 | 
				
			||||||
 | 
					    "51": "Conference Bridge WS-X6608",
 | 
				
			||||||
 | 
					    "52": "Cisco IOS Conference Bridge (HDV2)",
 | 
				
			||||||
 | 
					    "53": "Cisco Conference Bridge (WS-SVC-CMM)",
 | 
				
			||||||
 | 
					    "61": "H.323 Phone",
 | 
				
			||||||
 | 
					    "62": "H.323 Gateway",
 | 
				
			||||||
 | 
					    "70": "Music On Hold",
 | 
				
			||||||
 | 
					    "71": "Device Pilot",
 | 
				
			||||||
 | 
					    "72": "CTI Port",
 | 
				
			||||||
 | 
					    "73": "CTI Route Point",
 | 
				
			||||||
 | 
					    "80": "Voice Mail Port",
 | 
				
			||||||
 | 
					    "83": "Cisco IOS Software Media Termination Point (HDV2)",
 | 
				
			||||||
 | 
					    "84": "Cisco Media Server (WS-SVC-CMM-MS)",
 | 
				
			||||||
 | 
					    "85": "Cisco Video Conference Bridge (IPVC-35xx)",
 | 
				
			||||||
 | 
					    "86": "Cisco IOS Heterogeneous Video Conference Bridge",
 | 
				
			||||||
 | 
					    "87": "Cisco IOS Guaranteed Audio Video Conference Bridge",
 | 
				
			||||||
 | 
					    "88": "Cisco IOS Homogeneous Video Conference Bridge",
 | 
				
			||||||
 | 
					    "90": "Route List",
 | 
				
			||||||
 | 
					    "100": "Load Simulator",
 | 
				
			||||||
 | 
					    "110": "Media Termination Point",
 | 
				
			||||||
 | 
					    "111": "Media Termination Point Hardware",
 | 
				
			||||||
 | 
					    "112": "Cisco IOS Media Termination Point (HDV2)",
 | 
				
			||||||
 | 
					    "113": "Cisco Media Termination Point (WS-SVC-CMM)",
 | 
				
			||||||
 | 
					    "115": "Cisco 7941",
 | 
				
			||||||
 | 
					    "119": "Cisco 7971",
 | 
				
			||||||
 | 
					    "120": "MGCP Station",
 | 
				
			||||||
 | 
					    "121": "MGCP Trunk",
 | 
				
			||||||
 | 
					    "122": "GateKeeper",
 | 
				
			||||||
 | 
					    "124": "7914 14-Button Line Expansion Module",
 | 
				
			||||||
 | 
					    "125": "Trunk",
 | 
				
			||||||
 | 
					    "126": "Tone Announcement Player",
 | 
				
			||||||
 | 
					    "131": "SIP Trunk",
 | 
				
			||||||
 | 
					    "132": "SIP Gateway",
 | 
				
			||||||
 | 
					    "133": "WSM Trunk",
 | 
				
			||||||
 | 
					    "134": "Remote Destination Profile",
 | 
				
			||||||
 | 
					    "227": "7915 12-Button Line Expansion Module",
 | 
				
			||||||
 | 
					    "228": "7915 24-Button Line Expansion Module",
 | 
				
			||||||
 | 
					    "229": "7916 12-Button Line Expansion Module",
 | 
				
			||||||
 | 
					    "230": "7916 24-Button Line Expansion Module",
 | 
				
			||||||
 | 
					    "232": "CKEM 36-Button Line Expansion Module",
 | 
				
			||||||
 | 
					    "253": "SPA8800",
 | 
				
			||||||
 | 
					    "254": "Unknown MGCP Gateway",
 | 
				
			||||||
 | 
					    "255": "Unknown",
 | 
				
			||||||
 | 
					    "302": "Cisco 7985",
 | 
				
			||||||
 | 
					    "307": "Cisco 7911",
 | 
				
			||||||
 | 
					    "308": "Cisco 7961G-GE",
 | 
				
			||||||
 | 
					    "309": "Cisco 7941G-GE",
 | 
				
			||||||
 | 
					    "335": "Motorola CN622",
 | 
				
			||||||
 | 
					    "336": "Third-party SIP Device (Basic)",
 | 
				
			||||||
 | 
					    "348": "Cisco 7931",
 | 
				
			||||||
 | 
					    "358": "Cisco Unified Personal Communicator",
 | 
				
			||||||
 | 
					    "365": "Cisco 7921",
 | 
				
			||||||
 | 
					    "369": "Cisco 7906",
 | 
				
			||||||
 | 
					    "374": "Third-party SIP Device (Advanced)",
 | 
				
			||||||
 | 
					    "375": "Cisco TelePresence",
 | 
				
			||||||
 | 
					    "376": "Nokia S60",
 | 
				
			||||||
 | 
					    "404": "Cisco 7962",
 | 
				
			||||||
 | 
					    "412": "Cisco 3951",
 | 
				
			||||||
 | 
					    "431": "Cisco 7937",
 | 
				
			||||||
 | 
					    "434": "Cisco 7942",
 | 
				
			||||||
 | 
					    "435": "Cisco 7945",
 | 
				
			||||||
 | 
					    "436": "Cisco 7965",
 | 
				
			||||||
 | 
					    "437": "Cisco 7975",
 | 
				
			||||||
 | 
					    "446": "Cisco 3911",
 | 
				
			||||||
 | 
					    "468": "Cisco Unified Mobile Communicator",
 | 
				
			||||||
 | 
					    "478": "Cisco TelePresence 1000",
 | 
				
			||||||
 | 
					    "479": "Cisco TelePresence 3000",
 | 
				
			||||||
 | 
					    "480": "Cisco TelePresence 3200",
 | 
				
			||||||
 | 
					    "481": "Cisco TelePresence 500-37",
 | 
				
			||||||
 | 
					    "484": "Cisco 7925",
 | 
				
			||||||
 | 
					    "486": "Syn-Apps Virtual Phone",
 | 
				
			||||||
 | 
					    "493": "Cisco 9971",
 | 
				
			||||||
 | 
					    "495": "Cisco 6921",
 | 
				
			||||||
 | 
					    "496": "Cisco 6941",
 | 
				
			||||||
 | 
					    "497": "Cisco 6961",
 | 
				
			||||||
 | 
					    "503": "Cisco Unified Client Services Framework",
 | 
				
			||||||
 | 
					    "505": "Cisco TelePresence 1300-65",
 | 
				
			||||||
 | 
					    "520": "Cisco TelePresence 1100",
 | 
				
			||||||
 | 
					    "521": "Transnova S3",
 | 
				
			||||||
 | 
					    "522": "BlackBerry MVS VoWifi",
 | 
				
			||||||
 | 
					    "527": "IPTrade TAD",
 | 
				
			||||||
 | 
					    "537": "Cisco 9951",
 | 
				
			||||||
 | 
					    "540": "Cisco 8961",
 | 
				
			||||||
 | 
					    "547": "Cisco 6901",
 | 
				
			||||||
 | 
					    "548": "Cisco 6911",
 | 
				
			||||||
 | 
					    "550": "Cisco ATA 187",
 | 
				
			||||||
 | 
					    "557": "Cisco TelePresence 200",
 | 
				
			||||||
 | 
					    "558": "Cisco TelePresence 400",
 | 
				
			||||||
 | 
					    "562": "Cisco Dual Mode for iPhone",
 | 
				
			||||||
 | 
					    "564": "Cisco 6945",
 | 
				
			||||||
 | 
					    "575": "Cisco Dual Mode for Android",
 | 
				
			||||||
 | 
					    "577": "Cisco 7926",
 | 
				
			||||||
 | 
					    "580": "Cisco E20",
 | 
				
			||||||
 | 
					    "582": "Generic Single Screen Room System",
 | 
				
			||||||
 | 
					    "583": "Generic Multiple Screen Room System",
 | 
				
			||||||
 | 
					    "584": "Cisco TelePresence EX90",
 | 
				
			||||||
 | 
					    "585": "Cisco 8945",
 | 
				
			||||||
 | 
					    "586": "Cisco 8941",
 | 
				
			||||||
 | 
					    "588": "Generic Desktop Video Endpoint",
 | 
				
			||||||
 | 
					    "590": "Cisco TelePresence 500-32",
 | 
				
			||||||
 | 
					    "591": "Cisco TelePresence 1300-47",
 | 
				
			||||||
 | 
					    "592": "Cisco 3905",
 | 
				
			||||||
 | 
					    "593": "Cisco Cius",
 | 
				
			||||||
 | 
					    "594": "VKEM 36-Button Line Expansion Module",
 | 
				
			||||||
 | 
					    "596": "Cisco TelePresence TX1310-65",
 | 
				
			||||||
 | 
					    "597": "Cisco TelePresence MCU",
 | 
				
			||||||
 | 
					    "598": "Ascom IP-DECT Device",
 | 
				
			||||||
 | 
					    "599": "Cisco TelePresence Exchange System",
 | 
				
			||||||
 | 
					    "604": "Cisco TelePresence EX60",
 | 
				
			||||||
 | 
					    "606": "Cisco TelePresence Codec C90",
 | 
				
			||||||
 | 
					    "607": "Cisco TelePresence Codec C60",
 | 
				
			||||||
 | 
					    "608": "Cisco TelePresence Codec C40",
 | 
				
			||||||
 | 
					    "609": "Cisco TelePresence Quick Set C20",
 | 
				
			||||||
 | 
					    "610": "Cisco TelePresence Profile 42 (C20)",
 | 
				
			||||||
 | 
					    "611": "Cisco TelePresence Profile 42 (C60)",
 | 
				
			||||||
 | 
					    "612": "Cisco TelePresence Profile 52 (C40)",
 | 
				
			||||||
 | 
					    "613": "Cisco TelePresence Profile 52 (C60)",
 | 
				
			||||||
 | 
					    "614": "Cisco TelePresence Profile 52 Dual (C60)",
 | 
				
			||||||
 | 
					    "615": "Cisco TelePresence Profile 65 (C60)",
 | 
				
			||||||
 | 
					    "616": "Cisco TelePresence Profile 65 Dual (C90)",
 | 
				
			||||||
 | 
					    "617": "Cisco TelePresence MX200",
 | 
				
			||||||
 | 
					    "619": "Cisco TelePresence TX9000",
 | 
				
			||||||
 | 
					    "621": "Cisco 7821",
 | 
				
			||||||
 | 
					    "620": "Cisco TelePresence TX9200",
 | 
				
			||||||
 | 
					    "622": "Cisco 7841",
 | 
				
			||||||
 | 
					    "623": "Cisco 7861",
 | 
				
			||||||
 | 
					    "626": "Cisco TelePresence SX20",
 | 
				
			||||||
 | 
					    "627": "Cisco TelePresence MX300",
 | 
				
			||||||
 | 
					    "628": "IMS-integrated Mobile (Basic)",
 | 
				
			||||||
 | 
					    "631": "Third-party AS-SIP Endpoint",
 | 
				
			||||||
 | 
					    "632": "Cisco Cius SP",
 | 
				
			||||||
 | 
					    "633": "Cisco TelePresence Profile 42 (C40)",
 | 
				
			||||||
 | 
					    "634": "Cisco VXC 6215",
 | 
				
			||||||
 | 
					    "635": "CTI Remote Device",
 | 
				
			||||||
 | 
					    "640": "Usage Profile",
 | 
				
			||||||
 | 
					    "642": "Carrier-integrated Mobile",
 | 
				
			||||||
 | 
					    "645": "Universal Device Template",
 | 
				
			||||||
 | 
					    "647": "Cisco DX650",
 | 
				
			||||||
 | 
					    "648": "Cisco Unified Communications for RTX",
 | 
				
			||||||
 | 
					    "652": "Cisco Jabber for Tablet",
 | 
				
			||||||
 | 
					    "659": "Cisco 8831",
 | 
				
			||||||
 | 
					    "682": "Cisco TelePresence SX10",
 | 
				
			||||||
 | 
					    "683": "Cisco 8841",
 | 
				
			||||||
 | 
					    "684": "Cisco 8851",
 | 
				
			||||||
 | 
					    "685": "Cisco 8861",
 | 
				
			||||||
 | 
					    "688": "Cisco TelePresence SX80",
 | 
				
			||||||
 | 
					    "689": "Cisco TelePresence MX200 G2",
 | 
				
			||||||
 | 
					    "690": "Cisco TelePresence MX300 G2",
 | 
				
			||||||
 | 
					    "20000": "Cisco 7905",
 | 
				
			||||||
 | 
					    "30002": "Cisco 7920",
 | 
				
			||||||
 | 
					    "30006": "Cisco 7970",
 | 
				
			||||||
 | 
					    "30007": "Cisco 7912",
 | 
				
			||||||
 | 
					    "30008": "Cisco 7902",
 | 
				
			||||||
 | 
					    "30016": "Cisco IP Communicator",
 | 
				
			||||||
 | 
					    "30018": "Cisco 7961",
 | 
				
			||||||
 | 
					    "30019": "Cisco 7936",
 | 
				
			||||||
 | 
					    "30027": "Analog Phone",
 | 
				
			||||||
 | 
					    "30028": "ISDN BRI Phone",
 | 
				
			||||||
 | 
					    "30032": "SCCP gateway virtual phone",
 | 
				
			||||||
 | 
					    "30035": "IP-STE",
 | 
				
			||||||
 | 
					    "36041": "Cisco TelePresence Conductor",
 | 
				
			||||||
 | 
					    "36042": "Cisco DX80",
 | 
				
			||||||
 | 
					    "36043": "Cisco DX70",
 | 
				
			||||||
 | 
					    "36049": "BEKEM 36-Button Line Expansion Module",
 | 
				
			||||||
 | 
					    "36207": "Cisco TelePresence MX700",
 | 
				
			||||||
 | 
					    "36208": "Cisco TelePresence MX800",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Given CUCM XML, use XPath to extract relevant details for each device
 | 
				
			||||||
 | 
					# searching based on device type. Return a list of devices' information.
 | 
				
			||||||
 | 
					def get_device_details(xml, device):
 | 
				
			||||||
 | 
					    namespace = {"ns1": "http://schemas.cisco.com/ast/soap"}
 | 
				
			||||||
 | 
					    items = xml.findall(f".//ns1:DeviceClass[.='{device}']/..", namespace)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    names_seen = {}
 | 
				
			||||||
 | 
					    trunk_details = []
 | 
				
			||||||
 | 
					    for item in items:
 | 
				
			||||||
 | 
					        ip     = item.find(".//ns1:IP",  namespace).text
 | 
				
			||||||
 | 
					        name   = item.find("ns1:Name",   namespace).text
 | 
				
			||||||
 | 
					        model  = item.find("ns1:Model",  namespace).text
 | 
				
			||||||
 | 
					        status = item.find("ns1:Status", namespace).text
 | 
				
			||||||
 | 
					        reason = item.find("ns1:StatusReason", namespace).text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not names_seen.get(name):
 | 
				
			||||||
 | 
					            trunk_details.append({
 | 
				
			||||||
 | 
					                "ip": ip,
 | 
				
			||||||
 | 
					                "name": name,
 | 
				
			||||||
 | 
					                "status": status,
 | 
				
			||||||
 | 
					                "status_reason": status_reason_lookup.get(reason),
 | 
				
			||||||
 | 
					                "type": device,
 | 
				
			||||||
 | 
					                "model_name": model_name_lookup.get(model) or "Unknown"
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            names_seen[name] = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return trunk_details
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Contact CUCM and query it for device information for the following device
 | 
				
			||||||
 | 
					# types: SIP trunks, hunt lists, H323 and media resources (e.g. IVR). Return
 | 
				
			||||||
 | 
					# a list for devices' information.
 | 
				
			||||||
 | 
					def get_devices(addr, port, user, password, insecure):
 | 
				
			||||||
 | 
					    devices = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for device in ["SIPTrunk", "MediaResources", "H323", "HuntList"]:
 | 
				
			||||||
 | 
					        cucm_xml = query_cucm(addr, port, user, password, insecure, device)
 | 
				
			||||||
 | 
					        details = get_device_details(cucm_xml, device)
 | 
				
			||||||
 | 
					        devices.extend(details)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return devices
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Parse args, contact CUCM, check status of non-hone devices, and then print
 | 
				
			||||||
 | 
					# results
 | 
				
			||||||
 | 
					def main(argv=None):
 | 
				
			||||||
 | 
					    if argv is None:
 | 
				
			||||||
 | 
					        argv = sys.argv[1:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    args = inv.parse_arguments(argv)
 | 
				
			||||||
 | 
					    devices = get_devices(args.hostname, args.port, args.user, args.password,
 | 
				
			||||||
 | 
					                          args.insecure)
 | 
				
			||||||
 | 
					    inv.print_out(devices, "cucm_chk")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    sys.exit(main())
 | 
				
			||||||
							
								
								
									
										434
									
								
								check_mk-cucm/local/share/check_mk/agents/special/agent_cucm_inv
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										434
									
								
								check_mk-cucm/local/share/check_mk/agents/special/agent_cucm_inv
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,434 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Given a CUCM host, query the CUCM to get a complete list of phones, then
 | 
				
			||||||
 | 
					# query all those phones concurrently for additional details, then print
 | 
				
			||||||
 | 
					# the results out in a format CheckMK understands.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Run the command on the console for a complete list of options.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# This script is designed to work with up to 90K phones, and ideally below 10K.
 | 
				
			||||||
 | 
					# If more than 10K phones are queried, this script should be modified to
 | 
				
			||||||
 | 
					# perform connection reuse to improve performance. Beyond 90K support for
 | 
				
			||||||
 | 
					# paginating the AXL API must be added.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					phone_query_timeout = 10    # max time to query a single phone
 | 
				
			||||||
 | 
					phone_queries_timeout = 45  # max time to query all phones
 | 
				
			||||||
 | 
					cucm_page_size = 1000       # CUCM will not return pages larger than 2000.
 | 
				
			||||||
 | 
					                            # Larger page sizes cause notably longer queries,
 | 
				
			||||||
 | 
					                            # so a default of 1000 devices per query is a
 | 
				
			||||||
 | 
					                            # safer number.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import urllib.request, base64, sys, argparse, asyncio, re, json, ssl, html
 | 
				
			||||||
 | 
					from xml.etree import ElementTree
 | 
				
			||||||
 | 
					from textwrap import wrap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Create a TLS context for client connections.
 | 
				
			||||||
 | 
					def create_ssl_ctx():
 | 
				
			||||||
 | 
					    ctx = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT)
 | 
				
			||||||
 | 
					    ctx.check_hostname = False
 | 
				
			||||||
 | 
					    ctx.verify_mode = ssl.CERT_NONE
 | 
				
			||||||
 | 
					    return ctx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Typical GET or POST HTTP with Basic auth (using user and password
 | 
				
			||||||
 | 
					# credientials). Returns data structure parsed from XML.
 | 
				
			||||||
 | 
					def get_url(url, user, password, insecure, headers, data):
 | 
				
			||||||
 | 
					    request = urllib.request.Request(url, data=bytes(data, 'ascii'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for header, value in headers:
 | 
				
			||||||
 | 
					        request.add_header(header, value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if user and password:
 | 
				
			||||||
 | 
					        auth_str = base64.b64encode(bytes('%s:%s' % (user, password), 'ascii'))
 | 
				
			||||||
 | 
					        request.add_header('Authorization', 'Basic %s' % auth_str.decode('utf-8'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ctx = None
 | 
				
			||||||
 | 
					    if insecure:
 | 
				
			||||||
 | 
					        ctx = create_ssl_ctx()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with urllib.request.urlopen(request, context=ctx) as conn:
 | 
				
			||||||
 | 
					        xml_data = conn.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return ElementTree.fromstring(xml_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Call the CUCM AXL API synchronously, using a SOAP query to fetch the names of
 | 
				
			||||||
 | 
					# all phones. It returns XML, which we parse. We call the AXL API because the
 | 
				
			||||||
 | 
					# RisPort70 API (see query_cucm_risport() below) does not support pagination,
 | 
				
			||||||
 | 
					# so we need to get a full list of phone names from AXL first, then do multiple
 | 
				
			||||||
 | 
					# queries on RisPort70 using subsets of the phone name list found from AXL.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# The AXL API has a return limit of 8MB, which is around 90K phones, so we
 | 
				
			||||||
 | 
					# don't bother paginating the AXL API itself; if more than that is needed, add
 | 
				
			||||||
 | 
					# pagination here.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# References:
 | 
				
			||||||
 | 
					# https://developer.cisco.com/docs/axl/
 | 
				
			||||||
 | 
					# https://github.com/reillychase/How-to-return-Cisco-RIS-with-more-than-1000-results/blob/master/main.py
 | 
				
			||||||
 | 
					def query_cucm_axl(addr, port, user, password, insecure):
 | 
				
			||||||
 | 
					    url = 'https://%s:%s/axl/' % (addr, port)
 | 
				
			||||||
 | 
					    headers = [
 | 
				
			||||||
 | 
					        ('Content-Type', 'text/xml'),
 | 
				
			||||||
 | 
					        ('Accept', 'text/xml'),
 | 
				
			||||||
 | 
					        ('SOAPAction', 'CUCM:DB ver=12.5'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        return get_url(url, user, password, insecure, headers, """
 | 
				
			||||||
 | 
					            <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
 | 
				
			||||||
 | 
					                              xmlns:ns="http://www.cisco.com/AXL/API/12.5">
 | 
				
			||||||
 | 
					               <soapenv:Header/>
 | 
				
			||||||
 | 
					               <soapenv:Body>
 | 
				
			||||||
 | 
					                  <ns:listPhone>
 | 
				
			||||||
 | 
					                     <searchCriteria>
 | 
				
			||||||
 | 
					                       <name>%</name>
 | 
				
			||||||
 | 
					                     </searchCriteria>
 | 
				
			||||||
 | 
					                     <returnedTags>
 | 
				
			||||||
 | 
					                        <name/>
 | 
				
			||||||
 | 
					                     </returnedTags>
 | 
				
			||||||
 | 
					                  </ns:listPhone>
 | 
				
			||||||
 | 
					               </soapenv:Body>
 | 
				
			||||||
 | 
					            </soapenv:Envelope>
 | 
				
			||||||
 | 
					        """)
 | 
				
			||||||
 | 
					    except urllib.error.HTTPError as e:
 | 
				
			||||||
 | 
					        sys.stderr.write("AXL error: %s\n" % e)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Call the CUCM RisPort70 API synchronously, using a SOAP query to fetch
 | 
				
			||||||
 | 
					# information about the phones with ids listed in the phone_ids arg. It returns
 | 
				
			||||||
 | 
					# XML, which we parse.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Be aware that the API will return information about a maximum of 2000 devices,
 | 
				
			||||||
 | 
					# and provides no means of pagination. In order to do pagination, we first need
 | 
				
			||||||
 | 
					# to query the AXL API for a list of phone names, then all this function
 | 
				
			||||||
 | 
					# repeatedly with a different subset of 2000 phones from that complete list.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Although this function will allow pages for 2000 devices, it's recommended to
 | 
				
			||||||
 | 
					# use less for each call to avoid timeouts. The default maximum size of
 | 
				
			||||||
 | 
					# phone_ids is 1000, although this can be varied by changing the cucm_page_size
 | 
				
			||||||
 | 
					# at the top of this file.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# References:
 | 
				
			||||||
 | 
					# https://developer.cisco.com/docs/sxml/#!risport70-api-reference
 | 
				
			||||||
 | 
					# https://paultursan.com/2018/12/getting-cucm-real-time-data-via-risport70-with-python-and-zeep-cisco-serviceability-api/
 | 
				
			||||||
 | 
					def query_cucm_risport(addr, port, user, password, insecure, phone_ids):
 | 
				
			||||||
 | 
					    assert len(phone_ids) <= 2000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    url = 'https://%s:%s/realtimeservice2/services/RISService70/' % (addr, port)
 | 
				
			||||||
 | 
					    headers = [('Content-Type', 'text/plain')]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    id_query = ''.join([f'<soap:item><soap:Item>{id}</soap:Item></soap:item>' for id in phone_ids])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        return get_url(url, user, password, insecure, headers, f"""
 | 
				
			||||||
 | 
					            <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
 | 
				
			||||||
 | 
					                              xmlns:soap="http://schemas.cisco.com/ast/soap">
 | 
				
			||||||
 | 
					               <soapenv:Header/>
 | 
				
			||||||
 | 
					               <soapenv:Body>
 | 
				
			||||||
 | 
					                  <soap:selectCmDevice>
 | 
				
			||||||
 | 
					                     <soap:StateInfo></soap:StateInfo>
 | 
				
			||||||
 | 
					                     <soap:CmSelectionCriteria>
 | 
				
			||||||
 | 
					                        <soap:MaxReturnedDevices>2000</soap:MaxReturnedDevices>
 | 
				
			||||||
 | 
					                        <soap:DeviceClass>Any</soap:DeviceClass>
 | 
				
			||||||
 | 
					                        <soap:Model>255</soap:Model>
 | 
				
			||||||
 | 
					                        <soap:Status>Registered</soap:Status>
 | 
				
			||||||
 | 
					                        <soap:NodeName></soap:NodeName>
 | 
				
			||||||
 | 
					                        <soap:SelectBy>Name</soap:SelectBy>
 | 
				
			||||||
 | 
					                        <soap:SelectItems>
 | 
				
			||||||
 | 
					                           {id_query}
 | 
				
			||||||
 | 
					                        </soap:SelectItems>
 | 
				
			||||||
 | 
					                        <soap:Protocol>Any</soap:Protocol>
 | 
				
			||||||
 | 
					                        <soap:DownloadStatus>Any</soap:DownloadStatus>
 | 
				
			||||||
 | 
					                     </soap:CmSelectionCriteria>
 | 
				
			||||||
 | 
					                  </soap:selectCmDevice>
 | 
				
			||||||
 | 
					               </soapenv:Body>
 | 
				
			||||||
 | 
					            </soapenv:Envelope>
 | 
				
			||||||
 | 
					        """)
 | 
				
			||||||
 | 
					    except urllib.error.HTTPError as e:
 | 
				
			||||||
 | 
					        sys.stderr.write("CUCM error: %s\n" % e)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Given AXL XML, use XPath to extract names for all phones.
 | 
				
			||||||
 | 
					def get_phone_ids(xml):
 | 
				
			||||||
 | 
					    # should this be ns2?
 | 
				
			||||||
 | 
					    namespace = {'ns': 'http://www.cisco.com/AXL/API/12.5'}
 | 
				
			||||||
 | 
					    items = xml.findall(".//phone/name", namespace)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    names = []
 | 
				
			||||||
 | 
					    for item in items:
 | 
				
			||||||
 | 
					        names.append(item.text)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return names
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Given CUCM XML, use XPath to extract a bunch of details for each phone.
 | 
				
			||||||
 | 
					def get_phone_details(xml):
 | 
				
			||||||
 | 
					    namespace = {'ns1': 'http://schemas.cisco.com/ast/soap'}
 | 
				
			||||||
 | 
					    items = xml.findall(".//ns1:DeviceClass[.='Phone']/..", namespace)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    names_seen = {}
 | 
				
			||||||
 | 
					    phone_details = []
 | 
				
			||||||
 | 
					    for item in items:
 | 
				
			||||||
 | 
					        ip          = item.find('.//ns1:IP',       namespace).text
 | 
				
			||||||
 | 
					        name        = item.find('ns1:Name',        namespace).text
 | 
				
			||||||
 | 
					        dir_num     = item.find('ns1:DirNumber',   namespace).text
 | 
				
			||||||
 | 
					        description = item.find('ns1:Description', namespace).text
 | 
				
			||||||
 | 
					        user        = item.find('ns1:LoginUserId', namespace).text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # These come with a -Registered on the end of the numbers.
 | 
				
			||||||
 | 
					        # Since all numbers we get from CUCM are registered, there's no
 | 
				
			||||||
 | 
					        # need for the -Registered, and we cut it off here.
 | 
				
			||||||
 | 
					        if dir_num:
 | 
				
			||||||
 | 
					            dir_num = dir_num.split('-')[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not names_seen.get(name):
 | 
				
			||||||
 | 
					            phone_details.append((name, ip, dir_num, user, description))
 | 
				
			||||||
 | 
					            names_seen[name] = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return phone_details
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If a phone (possibly) returns XML, attempt to extract the MAC, serial and
 | 
				
			||||||
 | 
					# model. We're using regex here, instead of full-blown XML parsing, to minimize
 | 
				
			||||||
 | 
					# the time and GC garbage generated.
 | 
				
			||||||
 | 
					def get_phone_details_from_xml(data):
 | 
				
			||||||
 | 
					    # if the HTTP server didn't return a 200...
 | 
				
			||||||
 | 
					    if data.find('200 OK') == -1:
 | 
				
			||||||
 | 
					        return None, None, None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # attempt to extract info from XML
 | 
				
			||||||
 | 
					    mac    = re.search('<MACAddress>(.+)</MACAddress>',     data)
 | 
				
			||||||
 | 
					    serial = re.search('<serialNumber>(.+)</serialNumber>', data)
 | 
				
			||||||
 | 
					    model  = re.search('<modelNumber>(.+)</modelNumber>',   data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mac_str = mac    and normalize_mac(html.unescape(mac[1]))
 | 
				
			||||||
 | 
					    ser_str = serial and html.unescape(serial[1])
 | 
				
			||||||
 | 
					    mod_str = model  and html.unescape(model[1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return mac_str, ser_str, mod_str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If a phone (possibly) returns HTML, attempt to extract the MAC, serial and
 | 
				
			||||||
 | 
					# model. We use regex here for the same reason we use it in
 | 
				
			||||||
 | 
					# get_phone_details_from_xml().
 | 
				
			||||||
 | 
					def get_phone_details_from_html(data):
 | 
				
			||||||
 | 
					    if data.find('200 OK') == -1:
 | 
				
			||||||
 | 
					        return None, None, None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mac = None
 | 
				
			||||||
 | 
					    serial = None
 | 
				
			||||||
 | 
					    model = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # attempt to extract info from HTML
 | 
				
			||||||
 | 
					    matches = re.findall('<b>\s*(.*?)\s*</b>', data, re.M | re.I)
 | 
				
			||||||
 | 
					    for i, txt in enumerate(matches):
 | 
				
			||||||
 | 
					        txt = txt.lower()
 | 
				
			||||||
 | 
					        if   not mac    and txt == 'mac address':
 | 
				
			||||||
 | 
					            mac    = normalize_mac(html.unescape(matches[i + 1]))
 | 
				
			||||||
 | 
					        elif not serial and txt == 'serial number':
 | 
				
			||||||
 | 
					            serial = html.unescape(matches[i + 1])
 | 
				
			||||||
 | 
					        elif not model  and txt == 'model number':
 | 
				
			||||||
 | 
					            model  = html.unescape(matches[i + 1])
 | 
				
			||||||
 | 
					        elif mac and serial and model:
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return mac, serial, model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Different phones return MACs in different formats. We convert them to a single
 | 
				
			||||||
 | 
					# canonical format here.
 | 
				
			||||||
 | 
					def normalize_mac(mac):
 | 
				
			||||||
 | 
					    if mac.find(":") != -1:
 | 
				
			||||||
 | 
					        return mac.lower()
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        return ":".join(wrap(mac, 2)).lower()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Create a new HTTP/HTTPS connection, send a request, and extract any results.
 | 
				
			||||||
 | 
					async def get_async_url(ip, url, insecure=False):
 | 
				
			||||||
 | 
					    ctx = None
 | 
				
			||||||
 | 
					    port = 80
 | 
				
			||||||
 | 
					    if not insecure:
 | 
				
			||||||
 | 
					        ctx = create_ssl_ctx()
 | 
				
			||||||
 | 
					        port = 443
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#   XXX switch from 127.0.0.1:8081 to ip:port
 | 
				
			||||||
 | 
					#    future = asyncio.open_connection('127.0.0.1', 8081, ssl=ctx)
 | 
				
			||||||
 | 
					    future = asyncio.open_connection(ip, port, ssl=ctx)
 | 
				
			||||||
 | 
					    reader, writer = await asyncio.wait_for(future, timeout=phone_query_timeout)
 | 
				
			||||||
 | 
					    query = f'GET {url} HTTP/1.1\r\nHost: {ip}\r\nConnection: close\r\n\r\n'
 | 
				
			||||||
 | 
					    writer.write(query.encode())
 | 
				
			||||||
 | 
					    await writer.drain()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    data = ''
 | 
				
			||||||
 | 
					    while not reader.at_eof():
 | 
				
			||||||
 | 
					        raw = await reader.read(-1)
 | 
				
			||||||
 | 
					        data += raw.decode()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    writer.close()
 | 
				
			||||||
 | 
					    await writer.wait_closed()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Asynchronously contact the HTTP server in a phone. There are several
 | 
				
			||||||
 | 
					# different URLs that might return information, depending on the model of phone.
 | 
				
			||||||
 | 
					# To fetch the MAC and serial details we want requires us to potentially call
 | 
				
			||||||
 | 
					# all endpoints until we get some results. Be aware that some phone HTTP servers
 | 
				
			||||||
 | 
					# return 200 (and empty results) if we call the wrong URL for that model.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# We attempt to contact the phone using HTTPS first, falling back to HTTP if
 | 
				
			||||||
 | 
					# attempts with HTTPS failed.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Originally we tried to take advantage of HTTP connection reuse, but this was
 | 
				
			||||||
 | 
					# causing some problems, and it wasn't worth the effort to handle the edge
 | 
				
			||||||
 | 
					# cases. Now we always make a new connection per request, even when it's
 | 
				
			||||||
 | 
					# multiple URLs on the same IP. If additional performance is ever needed, HTTP
 | 
				
			||||||
 | 
					# connection reuse is worth adding; depending on the latency it can easily
 | 
				
			||||||
 | 
					# 2x+ request rate.
 | 
				
			||||||
 | 
					async def query_phone_info_now(details):
 | 
				
			||||||
 | 
					    name, ip, dir_num, user, description = details
 | 
				
			||||||
 | 
					    mac = None
 | 
				
			||||||
 | 
					    serial = None
 | 
				
			||||||
 | 
					    model = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        data = await get_async_url(ip, '/DeviceInformationX')
 | 
				
			||||||
 | 
					        mac, serial, model = get_phone_details_from_xml(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not mac:
 | 
				
			||||||
 | 
					            data = await get_async_url(ip, '/Device_Information.html')
 | 
				
			||||||
 | 
					            mac, serial, model = get_phone_details_from_html(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not mac:
 | 
				
			||||||
 | 
					            data = await get_async_url(ip, '/CGI/Java/Serviceability?adapter=device.statistics.device')
 | 
				
			||||||
 | 
					            mac, serial, model = get_phone_details_from_html(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not mac:
 | 
				
			||||||
 | 
					            data = await get_async_url(ip, '/')
 | 
				
			||||||
 | 
					            mac, serial, model = get_phone_details_from_html(data)
 | 
				
			||||||
 | 
					    except (ConnectionRefusedError, asyncio.TimeoutError, ssl.SSLError):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            if not mac:
 | 
				
			||||||
 | 
					                data = await get_async_url(ip, '/DeviceInformationX', True)
 | 
				
			||||||
 | 
					                mac, serial, model = get_phone_details_from_xml(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not mac:
 | 
				
			||||||
 | 
					                data = await get_async_url(ip, '/Device_Information.html', True)
 | 
				
			||||||
 | 
					                mac, serial, model = get_phone_details_from_html(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not mac:
 | 
				
			||||||
 | 
					                data = await get_async_url(ip, '/CGI/Java/Serviceability?adapter=device.statistics.device', True)
 | 
				
			||||||
 | 
					                mac, serial, model = get_phone_details_from_html(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not mac:
 | 
				
			||||||
 | 
					                data = await get_async_url(ip, '/', True)
 | 
				
			||||||
 | 
					                mac, serial, model = get_phone_details_from_html(data)
 | 
				
			||||||
 | 
					        except (ConnectionRefusedError, asyncio.TimeoutError):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        "name": name,
 | 
				
			||||||
 | 
					        "ip": ip,
 | 
				
			||||||
 | 
					        "mac": mac,
 | 
				
			||||||
 | 
					        "serial": serial,
 | 
				
			||||||
 | 
					        "dir_num": dir_num,
 | 
				
			||||||
 | 
					        "model": model,
 | 
				
			||||||
 | 
					        "user": user,
 | 
				
			||||||
 | 
					        "description": description
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# This functions job is solely to keep a limit on the concurrent number of
 | 
				
			||||||
 | 
					# connections made to the phones. Without this limit we'd quickly run out of
 | 
				
			||||||
 | 
					# spare sockets when dealing with large numbers of phones.
 | 
				
			||||||
 | 
					async def query_phone_info(details, semaphore):
 | 
				
			||||||
 | 
					    async with semaphore:
 | 
				
			||||||
 | 
					        return await query_phone_info_now(details)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Given information about a list of phones (specifically, their IP addresses),
 | 
				
			||||||
 | 
					# we call the HTTP server on each phone to extract the MAC and serial. We
 | 
				
			||||||
 | 
					# return with a list of dicts containing information about all phones.
 | 
				
			||||||
 | 
					# Contacting thousands of phones serially would take too long, so we keep 200
 | 
				
			||||||
 | 
					# concurrent calls in-flight to the phones to shorten all querying to a few
 | 
				
			||||||
 | 
					# seconds.
 | 
				
			||||||
 | 
					async def query_phones(details):
 | 
				
			||||||
 | 
					    sem = asyncio.Semaphore(200)
 | 
				
			||||||
 | 
					    tasks = map(lambda d: asyncio.create_task(query_phone_info(d, sem)), details)
 | 
				
			||||||
 | 
					    done, pending = await asyncio.wait(tasks, timeout=phone_queries_timeout)
 | 
				
			||||||
 | 
					    # we silently ignore pending for now
 | 
				
			||||||
 | 
					    return map(lambda f: f.result(), done)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Given an array of phone names, do paginated queries to the CUCM for phone
 | 
				
			||||||
 | 
					# information, then asynchronously query all the phones. While the CUCM has
 | 
				
			||||||
 | 
					# most of the information we want about a phone, it critically lacks the serial
 | 
				
			||||||
 | 
					# and MAC of the phone, which is why we need to fetch the details from the
 | 
				
			||||||
 | 
					# phone itself over an HTTP server each phone has.
 | 
				
			||||||
 | 
					def get_phones(addr, port, user, password, insecure):
 | 
				
			||||||
 | 
					    axl_xml = query_cucm_axl(addr, port, user, password, insecure)
 | 
				
			||||||
 | 
					    phone_ids = get_phone_ids(axl_xml)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    phone_details = []
 | 
				
			||||||
 | 
					    page_size = cucm_page_size
 | 
				
			||||||
 | 
					    for i in range(0, len(phone_ids), page_size):
 | 
				
			||||||
 | 
					        ids = phone_ids[i:i + page_size]
 | 
				
			||||||
 | 
					        cucm_xml = query_cucm_risport(addr, port, user, password, insecure, ids)
 | 
				
			||||||
 | 
					        details = get_phone_details(cucm_xml)
 | 
				
			||||||
 | 
					        phone_details.extend(details)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return asyncio.run(query_phones(phone_details))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Print out all our results in a format that CheckMK understands. Most of our
 | 
				
			||||||
 | 
					# output are in JSON rows.
 | 
				
			||||||
 | 
					def print_out(device_info, agent_name):
 | 
				
			||||||
 | 
					    sys.stdout.write(f"<<<{agent_name}:sep(0)>>>\n")
 | 
				
			||||||
 | 
					    device_info = list(device_info)
 | 
				
			||||||
 | 
					    device_info.sort(key=lambda d: d["ip"])
 | 
				
			||||||
 | 
					    for entry in device_info:
 | 
				
			||||||
 | 
					        sys.stdout.write("%s\n" % json.dumps(entry))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Parse the command-line arguments. We have several options, but hostname is
 | 
				
			||||||
 | 
					# always required. Print out help to console if we get no args.
 | 
				
			||||||
 | 
					def parse_arguments(argv):
 | 
				
			||||||
 | 
					    parser = argparse.ArgumentParser()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    parser.add_argument(
 | 
				
			||||||
 | 
					        "-u", "--user", default=None, help="Username for CUCM login"
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    parser.add_argument(
 | 
				
			||||||
 | 
					        "-s", "--password", default=None, help="Password for CUCM login"
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    parser.add_argument(
 | 
				
			||||||
 | 
					        "-p", "--port", default=443, type=int, help="Use alternative port (default: 443)"
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    parser.add_argument(
 | 
				
			||||||
 | 
					        "hostname", metavar="HOSTNAME", help="Hostname of the CUCM to query."
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    parser.add_argument(
 | 
				
			||||||
 | 
					        "-k", "--insecure", default=False, help="Skip certificate verification",
 | 
				
			||||||
 | 
					        action="store_true"
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return parser.parse_args(argv)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Parse args, contact CUCM, query phones, and then print results
 | 
				
			||||||
 | 
					def main(argv=None):
 | 
				
			||||||
 | 
					    if argv is None:
 | 
				
			||||||
 | 
					        argv = sys.argv[1:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    args = parse_arguments(argv)
 | 
				
			||||||
 | 
					    phones = get_phones(args.hostname, args.port, args.user, args.password,
 | 
				
			||||||
 | 
					                        args.insecure)
 | 
				
			||||||
 | 
					    print_out(phones, 'cucm_inv')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    sys.exit(main())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										24
									
								
								check_mk-cucm/local/share/check_mk/checks/agent_cucm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								check_mk-cucm/local/share/check_mk/checks/agent_cucm
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def agent_cucm_arguments(params, hostname, ipaddress):
 | 
				
			||||||
 | 
					    args = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if "user" in params:
 | 
				
			||||||
 | 
					        args += ["-u", params["user"]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if "password" in params:
 | 
				
			||||||
 | 
					        args += ["-s", params["password"]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if "port" in params:
 | 
				
			||||||
 | 
					        args += ["-p", params["port"]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if params.get("insecure"):
 | 
				
			||||||
 | 
					        args.append("-k")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    args.append(params["instance"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return args
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					special_agent_info["cucm_chk"] = agent_cucm_arguments
 | 
				
			||||||
 | 
					special_agent_info["cucm_inv"] = agent_cucm_arguments
 | 
				
			||||||
							
								
								
									
										19
									
								
								check_mk-cucm/local/share/check_mk/web/plugins/views/cucm.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								check_mk-cucm/local/share/check_mk/web/plugins/views/cucm.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					from cmk.gui.plugins.views import inventory_displayhints
 | 
				
			||||||
 | 
					from cmk.gui.i18n import _l
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# inventory list for phones found in CUCM
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inventory_displayhints.update({
 | 
				
			||||||
 | 
					    ".phones:": {
 | 
				
			||||||
 | 
					        "title": _l("Phones"),
 | 
				
			||||||
 | 
					        "keyorder": ["name", "user", "dir_num", "ip", "model", "serial", "mac", "description"],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    ".phones:*.name": {"title": _l("Name")},
 | 
				
			||||||
 | 
					    ".phones:*.user": {"title": _l("User")},
 | 
				
			||||||
 | 
					    ".phones:*.dir_num": {"title": _l("Dir Num")},
 | 
				
			||||||
 | 
					    ".phones:*.ip": {"title": _l("IP Addr")},
 | 
				
			||||||
 | 
					    ".phones:*.model": {"title": _l("Model")},
 | 
				
			||||||
 | 
					    ".phones:*.serial": {"title": _l("Serial")},
 | 
				
			||||||
 | 
					    ".phones:*.mac": {"title": _l("MAC Addr")},
 | 
				
			||||||
 | 
					    ".phones:*.description": {"title": _l("Description")},
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
							
								
								
									
										120
									
								
								check_mk-cucm/local/share/check_mk/web/plugins/wato/cucm.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								check_mk-cucm/local/share/check_mk/web/plugins/wato/cucm.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,120 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# GUI configuration pages to set up inventorying and checks done by the CUCM
 | 
				
			||||||
 | 
					# agent to CUCM. These two pages are for giving the agent the necessary details
 | 
				
			||||||
 | 
					# to connect to CUCM (e.g. IP address, user, login, etc).
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Ideally, we'd have a single page to configure both the inventorying and
 | 
				
			||||||
 | 
					# checks, since both contact the same CUCM instance. Unfortunately, I didn't
 | 
				
			||||||
 | 
					# find a clean way to do it, so we're left with two identical GUI pages that
 | 
				
			||||||
 | 
					# take identical information. At least we manage to share most of the code
 | 
				
			||||||
 | 
					# here by taking a deep copy and modifying the title.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import copy
 | 
				
			||||||
 | 
					from cmk.gui.i18n import _
 | 
				
			||||||
 | 
					from cmk.gui.plugins.wato.utils import (
 | 
				
			||||||
 | 
					    rulespec_registry,
 | 
				
			||||||
 | 
					    HostRulespec,
 | 
				
			||||||
 | 
					    RulespecGroupCheckParametersHardware
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from cmk.gui.plugins.wato.inventory import RulespecGroupInventory
 | 
				
			||||||
 | 
					from cmk.gui.watolib.rulespecs import Rulespec
 | 
				
			||||||
 | 
					from cmk.gui.valuespec import (
 | 
				
			||||||
 | 
					    Dictionary,
 | 
				
			||||||
 | 
					    TextInput,
 | 
				
			||||||
 | 
					    Hostname,
 | 
				
			||||||
 | 
					    NetworkPort,
 | 
				
			||||||
 | 
					    Password,
 | 
				
			||||||
 | 
					    TextAscii,
 | 
				
			||||||
 | 
					    FixedValue
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# GUI config page for inventory.
 | 
				
			||||||
 | 
					def _valuespec_special_agents_cucm_inv():
 | 
				
			||||||
 | 
					    return Dictionary(
 | 
				
			||||||
 | 
					        title=_("CUCM inventory"),
 | 
				
			||||||
 | 
					        help=_(""),
 | 
				
			||||||
 | 
					        optional_keys=["port", "user", "password", "insecure"],
 | 
				
			||||||
 | 
					        elements=[
 | 
				
			||||||
 | 
					            (
 | 
				
			||||||
 | 
					                "instance",
 | 
				
			||||||
 | 
					                Hostname(
 | 
				
			||||||
 | 
					                    title=_("Hostname"),
 | 
				
			||||||
 | 
					                    help=_(
 | 
				
			||||||
 | 
					                        "Host of CUCM host for query"
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    allow_empty=False,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            (
 | 
				
			||||||
 | 
					                "port",
 | 
				
			||||||
 | 
					                NetworkPort(
 | 
				
			||||||
 | 
					                    title=_("Port"),
 | 
				
			||||||
 | 
					                    help=_(
 | 
				
			||||||
 | 
					                        "Port of CUCM host for query"
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    minvalue=1,
 | 
				
			||||||
 | 
					                    default_value=443,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            (
 | 
				
			||||||
 | 
					                "user",
 | 
				
			||||||
 | 
					                TextInput(
 | 
				
			||||||
 | 
					                    title=_("Username"),
 | 
				
			||||||
 | 
					                    help=_(
 | 
				
			||||||
 | 
					                        "Username used when querying CUCM"
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            (
 | 
				
			||||||
 | 
					                "password",
 | 
				
			||||||
 | 
					                Password(
 | 
				
			||||||
 | 
					                    title=_("Password"),
 | 
				
			||||||
 | 
					                    help=_(
 | 
				
			||||||
 | 
					                        "Password used when querying CUCM"
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            (
 | 
				
			||||||
 | 
					                "insecure",
 | 
				
			||||||
 | 
					                FixedValue(
 | 
				
			||||||
 | 
					                    True,
 | 
				
			||||||
 | 
					                    title=_("Insecure"),
 | 
				
			||||||
 | 
					                    totext=_("Disable SSL certificate verification"),
 | 
				
			||||||
 | 
					                    help=_(
 | 
				
			||||||
 | 
					                        "Ignore unverified HTTPS request warnings when contacting CUCM"
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# GUI config page for checks. We do a deep copy of the above function and just
 | 
				
			||||||
 | 
					# change the title. A bit hackish since we're changing a private attribute.
 | 
				
			||||||
 | 
					def _valuespec_special_agents_cucm_chk():
 | 
				
			||||||
 | 
					   inv_spec = _valuespec_special_agents_cucm_inv()
 | 
				
			||||||
 | 
					   chk_spec = copy.deepcopy(inv_spec)
 | 
				
			||||||
 | 
					   chk_spec._title=_("CUCM checks")
 | 
				
			||||||
 | 
					   return chk_spec
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rulespec_registry.register(
 | 
				
			||||||
 | 
					    HostRulespec(
 | 
				
			||||||
 | 
					        factory_default=Rulespec.FACTORY_DEFAULT_UNUSED,
 | 
				
			||||||
 | 
					        name="special_agents:cucm_inv",
 | 
				
			||||||
 | 
					        group=RulespecGroupInventory,
 | 
				
			||||||
 | 
					        valuespec=_valuespec_special_agents_cucm_inv,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rulespec_registry.register(
 | 
				
			||||||
 | 
					    HostRulespec(
 | 
				
			||||||
 | 
					        factory_default=Rulespec.FACTORY_DEFAULT_UNUSED,
 | 
				
			||||||
 | 
					        name="special_agents:cucm_chk",
 | 
				
			||||||
 | 
					        group=RulespecGroupCheckParametersHardware,
 | 
				
			||||||
 | 
					        valuespec=_valuespec_special_agents_cucm_chk,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user