diff --git a/.travis.yml b/.travis.yml index 53ee695edd..b5e661c825 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,6 +34,7 @@ env: - DD_AGENT_BRANCH=master matrix: - TRAVIS_FLAVOR=default + - TRAVIS_FLAVOR=asterisk FLAVOR_VERSION=latest # END OF TRAVIS MATRIX before_install: diff --git a/asterisk/README.md b/asterisk/README.md new file mode 100644 index 0000000000..6c9c029834 --- /dev/null +++ b/asterisk/README.md @@ -0,0 +1,32 @@ +# Asterisk Integration + +## Overview + +Get metrics from asterisk service in real time to: + +* Visualize and monitor asterisk states +* Be notified about asterisk failovers and events. + +## Installation + +Install the `dd-check-asterisk` package manually or with your favorite configuration manager + +## Configuration + +Edit the `asterisk.yaml` file to point to your server and port, set the masters to monitor + +## Validation + +When you run `datadog-agent info` you should see something like the following: + + Checks + ====== + + asterisk + ----------- + - instance #0 [OK] + - Collected 39 metrics, 0 events & 7 service checks + +## Compatibility + +The asterisk check is compatible with all major platforms diff --git a/asterisk/check.py b/asterisk/check.py new file mode 100644 index 0000000000..76a8d501d2 --- /dev/null +++ b/asterisk/check.py @@ -0,0 +1,175 @@ +# stdlib +import re + +# 3rd party +import asterisk.manager + +# project +from checks import AgentCheck + +EVENT_TYPE = SOURCE_TYPE_NAME = 'asterisk' + +# +# requires pyst2 for Asterisk Manager Interface +# https://github.com/rdegges/pyst2 +# +# requires re for regular expression matching on asterisk output +# +# +class AsteriskCheck(AgentCheck): + + def __init__(self, name, init_config, agentConfig, instances=None): + AgentCheck.__init__(self, name, init_config, agentConfig, instances) + + def check(self, instance): + if 'host' not in instance: + instance['host'] = 'localhost' + if 'manager_user' not in instance: + self.log.error('manager_user not defined, skipping') + return + if 'manager_secret' not in instance: + self.log.error('manager_secret not defined, skipping') + return + + +###### Connect + mgr = asterisk.manager.Manager() + try: + if 'port' in instance: + mgr.connect(instance['host'],instance['port']) + else: + mgr.connect(instance['host']) + mgr.login(instance['manager_user'],instance['manager_secret']) + except asterisk.manager.ManagerSocketException as e: + self.log.error('Error connecting to Asterisk Manager Interface') + mgr.close() + return + except asterisk.manager.ManagerAuthException as e: + self.log.error('Error Logging in to Asterisk Manager Interface') + mgr.close() + return + +##### Call Volume + call_volume = mgr.command('core show calls') + + current_call_vol = call_volume.data.split('\n') + + current_call_vol = current_call_vol[0].replace('active call','') + current_call_vol = current_call_vol.replace('s','') + current_call_vol = current_call_vol.replace(' ','') + + self.gauge('asterisk.callvolume',current_call_vol) + +##### SIP Peers + sip_result = mgr.command('sip show peers') + + sip_results = sip_result.data.split('\n') + + siptotals = sip_results[len(sip_results)-3] + + siptotal = re.findall(r'([0-9]+) sip peer',siptotals)[0] + + monitored_peers = re.findall(r'Monitored: ([0-9]+) online, ([0-9]+) offline',siptotals)[0] + unmonitored_peers = re.findall(r'Unmonitored: ([0-9]+) online, ([0-9]+) offline',siptotals)[0] + + self.gauge('asterisk.sip.peers',siptotal) + self.gauge('asterisk.sip.monitored.online',monitored_peers[0]) + self.gauge('asterisk.sip.monitored.offline',monitored_peers[1]) + self.gauge('asterisk.sip.unmonitored.online',unmonitored_peers[0]) + self.gauge('asterisk.sip.unmonitored.offline',unmonitored_peers[1]) + +##### SIP Trunks (You have to add '-trunk' string into your SIP trunk name to detect it as a Trunk) + sip_total_trunks = 0 + sip_online_trunks = 0 + sip_offline_trunks = 0 + + + for chan in sip_results: + if chan != None: + chan_data = chan.split() + + if len(chan_data) > 1: + if "-trunk" in chan_data[0]: + sip_total_trunks += 1 + if len(chan_data) > 2 and "OK" in chan_data[5]: + sip_online_trunks += 1 + if len(chan_data) > 2 and chan_data[5] == "UNREACHABLE": + sip_offline_trunks += 1 + + self.gauge('asterisk.sip.trunks.total',sip_total_trunks) + self.gauge('asterisk.sip.trunks.online',sip_online_trunks) + self.gauge('asterisk.sip.trunks.offline',sip_offline_trunks) + +##### PRI In Use + + pri = mgr.command('pri show channels') + + pri_channels = pri.data.split('\n') + + pri_channels[0] = None + pri_channels[1] = None + + openchannels = 0 + for chan in pri_channels: + if chan != None: + chan_data = chan.split() + if len(chan_data) > 2 and chan_data[3] == "No": + openchannels += 1 + + self.gauge('asterisk.pri.channelsinuse',openchannels) + +##### IAX2 Peers + + iax_result = mgr.command('iax2 show peers') + + iax_results = iax_result.data.split('\n') + + iax_total_line = iax_results[len(iax_results)-3] + + iax_peers_total = re.findall(r'([0-9]+) iax2 peers',iax_total_line)[0] + iax_peers_online = re.findall(r'\[([0-9]+) online',iax_total_line)[0] + iax_peers_offline = re.findall(r'([0-9]+) offline',iax_total_line)[0] + iax_peers_unmonitored = re.findall(r'([0-9]+) unmonitored',iax_total_line)[0] + + self.gauge('asterisk.iax2.peers',iax_peers_total) + self.gauge('asterisk.iax2.online',iax_peers_online) + self.gauge('asterisk.iax2.offline',iax_peers_offline) + self.gauge('asterisk.iax2.unmonitored',iax_peers_unmonitored) + +##### DAHDI Channels + + dahdi_result = mgr.command('dahdi show status') + + dahdi_results = dahdi_result.data.split('\n') + + dahdi_total_trunks = len(dahdi_results)-3 + + dahdi_results[0] = None + + dahdi_online_trunks = 0 + dahdi_offline_trunks = 0 + + for chan in dahdi_results: + if chan != None: + chan_data = chan.split() + + if len(chan_data) > 1: + if "Wildcard" in chan_data[0]: + if len(chan_data) > 2 and chan_data[2] == "OK": + dahdi_online_trunks += 1 + if len(chan_data) > 2 and chan_data[2] == "RED": + dahdi_offline_trunks += 1 + + if "wanpipe" in chan_data[0]: + if len(chan_data) > 2 and chan_data[3] == "OK": + dahdi_online_trunks += 1 + if len(chan_data) > 2 and chan_data[3] == "RED": + dahdi_offline_trunks += 1 + + self.gauge('asterisk.dahdi.total',dahdi_total_trunks) + self.gauge('asterisk.dahdi.online',dahdi_online_trunks) + self.gauge('asterisk.dahdi.offline',dahdi_offline_trunks) + +##### Close connection + + mgr.close() diff --git a/asterisk/ci/asterisk.rake b/asterisk/ci/asterisk.rake new file mode 100644 index 0000000000..6a6f24f3a7 --- /dev/null +++ b/asterisk/ci/asterisk.rake @@ -0,0 +1,64 @@ +require 'ci/common' + +def asterisk_version + ENV['FLAVOR_VERSION'] || 'latest' +end + +def asterisk_rootdir + "#{ENV['INTEGRATIONS_DIR']}/asterisk_#{asterisk_version}" +end + +namespace :ci do + namespace :asterisk do |flavor| + task before_install: ['ci:common:before_install'] + + task install: ['ci:common:install'] do + use_venv = in_venv + install_requirements('asterisk/requirements.txt', + "--cache-dir #{ENV['PIP_CACHE']}", + "#{ENV['VOLATILE_DIR']}/ci.log", use_venv) + # sample docker usage + # sh %(docker create -p XXX:YYY --name asterisk source/asterisk:asterisk_version) + # sh %(docker start asterisk) + end + + task before_script: ['ci:common:before_script'] + + task script: ['ci:common:script'] do + this_provides = [ + 'asterisk' + ] + Rake::Task['ci:common:run_tests'].invoke(this_provides) + end + + task before_cache: ['ci:common:before_cache'] + + task cleanup: ['ci:common:cleanup'] + # sample cleanup task + # task cleanup: ['ci:common:cleanup'] do + # sh %(docker stop asterisk) + # sh %(docker rm asterisk) + # end + + task :execute do + exception = nil + begin + %w(before_install install before_script).each do |u| + Rake::Task["#{flavor.scope.path}:#{u}"].invoke + end + Rake::Task["#{flavor.scope.path}:script"].invoke + Rake::Task["#{flavor.scope.path}:before_cache"].invoke + rescue => e + exception = e + puts "Failed task: #{e.class} #{e.message}".red + end + if ENV['SKIP_CLEANUP'] + puts 'Skipping cleanup, disposable environments are great'.yellow + else + puts 'Cleaning up' + Rake::Task["#{flavor.scope.path}:cleanup"].invoke + end + raise exception if exception + end + end +end diff --git a/asterisk/conf.yaml.example b/asterisk/conf.yaml.example new file mode 100644 index 0000000000..5dedc20bc5 --- /dev/null +++ b/asterisk/conf.yaml.example @@ -0,0 +1,7 @@ +instances: + - host: localhost #defaults to localhost + port: 5038 #defaults to 5038 + manager_user: user #required + manager_secret: secret #required + + #this user needs to have the command write privilege diff --git a/asterisk/manifest.json b/asterisk/manifest.json new file mode 100644 index 0000000000..35090457af --- /dev/null +++ b/asterisk/manifest.json @@ -0,0 +1,11 @@ +{ + "maintainer": "help@datadoghq.com", + "manifest_version": "0.1.0", + "max_agent_version": "6.0.0", + "min_agent_version": "5.6.3", + "name": "asterisk", + "short_description": "asterisk description.", + "support": "contrib", + "supported_os": ["linux","mac_os","windows"], + "version": "0.1.0" +} diff --git a/asterisk/metadata.csv b/asterisk/metadata.csv new file mode 100644 index 0000000000..ae0af07419 --- /dev/null +++ b/asterisk/metadata.csv @@ -0,0 +1 @@ +metric_name,metric_type,interval,unit_name,per_unit_name,description,orientation,integration,short_name diff --git a/asterisk/requirements.txt b/asterisk/requirements.txt new file mode 100644 index 0000000000..f89ecf55da --- /dev/null +++ b/asterisk/requirements.txt @@ -0,0 +1 @@ +# integration pip requirements diff --git a/asterisk/test_asterisk.py b/asterisk/test_asterisk.py new file mode 100644 index 0000000000..d2604f5a9f --- /dev/null +++ b/asterisk/test_asterisk.py @@ -0,0 +1,38 @@ +# (C) Datadog, Inc. 2010-2016 +# All rights reserved +# Licensed under Simplified BSD License (see LICENSE) + +# stdlib +from nose.plugins.attrib import attr + +# 3p + +# project +from tests.checks.common import AgentCheckTest + + +instance = { + 'host': 'localhost', + 'port': 26379, + 'password': 'datadog-is-devops-best-friend' +} + + +# NOTE: Feel free to declare multiple test classes if needed + +@attr(requires='asterisk') +class TestAsterisk(AgentCheckTest): + """Basic Test for asterisk integration.""" + CHECK_NAME = 'asterisk' + + def test_check(self): + """ + Testing Asterisk check. + """ + self.load_check({}, {}) + + # run your actual tests... + + self.assertTrue(True) + # Raises when COVERAGE=true and coverage < 100% + self.coverage_report() diff --git a/circle.yml b/circle.yml index 8373636490..e474339453 100644 --- a/circle.yml +++ b/circle.yml @@ -32,6 +32,7 @@ test: override: - bundle exec rake prep_travis_ci - rake ci:run[default] + - rake ci:run[asterisk] - bundle exec rake requirements post: - if [[ $(docker ps -a -q) ]]; then docker stop $(docker ps -a -q); fi