Feature added: Unifi. #79

This commit is contained in:
Nicholas St. Germain 2019-01-04 15:30:27 -06:00
parent 9774a02131
commit 5ba5e6eda1
9 changed files with 137 additions and 38 deletions

View file

@ -11,6 +11,7 @@ from argparse import ArgumentParser, RawTextHelpFormatter
from logging import getLogger, StreamHandler, Formatter, DEBUG from logging import getLogger, StreamHandler, Formatter, DEBUG
from varken.ombi import OmbiAPI from varken.ombi import OmbiAPI
from varken.unifi import UniFiAPI
from varken.cisco import CiscoAPI from varken.cisco import CiscoAPI
from varken import VERSION, BRANCH from varken import VERSION, BRANCH
from varken.sonarr import SonarrAPI from varken.sonarr import SonarrAPI
@ -135,8 +136,13 @@ if __name__ == "__main__":
ASA = CiscoAPI(firewall, DBMANAGER) ASA = CiscoAPI(firewall, DBMANAGER)
schedule.every(firewall.get_bandwidth_run_seconds).seconds.do(threaded, ASA.get_bandwidth) schedule.every(firewall.get_bandwidth_run_seconds).seconds.do(threaded, ASA.get_bandwidth)
if CONFIG.unifi_enabled:
for server in CONFIG.unifi_servers:
UNIFI = UniFiAPI(server, DBMANAGER)
schedule.every(server.get_usg_stats_run_seconds).seconds.do(threaded, UNIFI.get_usg_stats)
# Run all on startup # Run all on startup
SERVICES_ENABLED = [CONFIG.ombi_enabled, CONFIG.radarr_enabled, CONFIG.tautulli_enabled, SERVICES_ENABLED = [CONFIG.ombi_enabled, CONFIG.radarr_enabled, CONFIG.tautulli_enabled, CONFIG.unifi_enabled,
CONFIG.sonarr_enabled, CONFIG.ciscoasa_enabled, CONFIG.sickchill_enabled] CONFIG.sonarr_enabled, CONFIG.ciscoasa_enabled, CONFIG.sickchill_enabled]
if not [enabled for enabled in SERVICES_ENABLED if enabled]: if not [enabled for enabled in SERVICES_ENABLED if enabled]:
vl.logger.error("All services disabled. Exiting") vl.logger.error("All services disabled. Exiting")

View file

@ -5,6 +5,7 @@ tautulli_server_ids = 1
ombi_server_ids = 1 ombi_server_ids = 1
ciscoasa_server_ids = false ciscoasa_server_ids = false
sickchill_server_ids = false sickchill_server_ids = false
unifi_server_ids = false
[influxdb] [influxdb]
url = influxdb.domain.tld url = influxdb.domain.tld
@ -95,3 +96,13 @@ outside_interface = WAN
ssl = false ssl = false
verify_ssl = false verify_ssl = false
get_bandwidth_run_seconds = 300 get_bandwidth_run_seconds = 300
[unifi-1]
url = unifi.domain.tld:8443
username = ubnt
password = ubnt
site = default
usg_name = MyRouter
ssl = false
verify_ssl = false
get_usg_stats_run_seconds = 300

View file

@ -1,2 +1,2 @@
VERSION = 1.7 VERSION = "1.6.1"
BRANCH = 'pre-nightly' BRANCH = 'pre-nightly'

View file

@ -7,7 +7,6 @@ from varken.helpers import connection_handler
class CiscoAPI(object): class CiscoAPI(object):
def __init__(self, firewall, dbmanager): def __init__(self, firewall, dbmanager):
self.now = datetime.now(timezone.utc).astimezone().isoformat()
self.dbmanager = dbmanager self.dbmanager = dbmanager
self.firewall = firewall self.firewall = firewall
# Create session to reduce server web thread load, and globally define pageSize for all requests # Create session to reduce server web thread load, and globally define pageSize for all requests
@ -32,7 +31,7 @@ class CiscoAPI(object):
self.session.headers = {'X-Auth-Token': post} self.session.headers = {'X-Auth-Token': post}
def get_bandwidth(self): def get_bandwidth(self):
self.now = datetime.now(timezone.utc).astimezone().isoformat() now = datetime.now(timezone.utc).astimezone().isoformat()
endpoint = '/api/monitoring/device/interfaces/' + self.firewall.outside_interface endpoint = '/api/monitoring/device/interfaces/' + self.firewall.outside_interface
if not self.session.headers: if not self.session.headers:
@ -50,7 +49,7 @@ class CiscoAPI(object):
"tags": { "tags": {
"interface": self.firewall.outside_interface "interface": self.firewall.outside_interface
}, },
"time": self.now, "time": now,
"fields": { "fields": {
"upload_bitrate": get['outputBitRate'], "upload_bitrate": get['outputBitRate'],
"download_bitrate": get['inputBitRate'] "download_bitrate": get['inputBitRate']

View file

@ -90,7 +90,8 @@ def rfc1918_ip_check(ip):
return rfc1918_ip return rfc1918_ip
def connection_handler(session, request, verify): def connection_handler(session, request, verify, as_is_reply=False):
air = as_is_reply
s = session s = session
r = request r = request
v = verify v = verify
@ -114,6 +115,8 @@ def connection_handler(session, request, verify):
if get.headers['X-Auth-Token']: if get.headers['X-Auth-Token']:
return get.headers['X-Auth-Token'] return get.headers['X-Auth-Token']
if air:
return get
except InvalidSchema: except InvalidSchema:
logger.error("You added http(s):// in the config file. Don't do that.") logger.error("You added http(s):// in the config file. Don't do that.")
@ -123,6 +126,7 @@ def connection_handler(session, request, verify):
except ConnectionError as e: except ConnectionError as e:
logger.error('Cannot resolve the url/ip/port. Check connectivity. Error: %s', e) logger.error('Cannot resolve the url/ip/port. Check connectivity. Error: %s', e)
return return_json return return_json

View file

@ -4,9 +4,9 @@ from os.path import join, exists
from re import match, compile, IGNORECASE from re import match, compile, IGNORECASE
from configparser import ConfigParser, NoOptionError, NoSectionError from configparser import ConfigParser, NoOptionError, NoSectionError
from varken.helpers import clean_sid_check, rfc1918_ip_check
from varken.structures import SickChillServer
from varken.varkenlogger import BlacklistFilter from varken.varkenlogger import BlacklistFilter
from varken.structures import SickChillServer, UniFiServer
from varken.helpers import clean_sid_check, rfc1918_ip_check
from varken.structures import SonarrServer, RadarrServer, OmbiServer, TautulliServer, InfluxServer, CiscoASAFirewall from varken.structures import SonarrServer, RadarrServer, OmbiServer, TautulliServer, InfluxServer, CiscoASAFirewall
@ -15,7 +15,7 @@ class INIParser(object):
self.config = None self.config = None
self.data_folder = data_folder self.data_folder = data_folder
self.filtered_strings = None self.filtered_strings = None
self.services = ['sonarr', 'radarr', 'ombi', 'tautulli', 'sickchill', 'ciscoasa'] self.services = ['sonarr', 'radarr', 'ombi', 'tautulli', 'sickchill', 'ciscoasa', 'unifi']
self.logger = getLogger() self.logger = getLogger()
self.influx_server = InfluxServer() self.influx_server = InfluxServer()
@ -167,29 +167,26 @@ class INIParser(object):
url = self.url_check(self.config.get(section, 'url'), section=section) url = self.url_check(self.config.get(section, 'url'), section=section)
apikey = None apikey = None
if service != 'ciscoasa': if service not in ['ciscoasa', 'unifi']:
apikey = self.config.get(section, 'apikey') apikey = self.config.get(section, 'apikey')
scheme = 'https://' if self.config.getboolean(section, 'ssl') else 'http://' scheme = 'https://' if self.config.getboolean(section, 'ssl') else 'http://'
verify_ssl = self.config.getboolean(section, 'verify_ssl') verify_ssl = self.config.getboolean(section, 'verify_ssl')
if scheme != 'https://': if scheme != 'https://':
verify_ssl = False verify_ssl = False
if service == 'sonarr': if service in ['sonarr', 'radarr']:
queue = self.config.getboolean(section, 'queue') queue = self.config.getboolean(section, 'queue')
queue_run_seconds = self.config.getint(section, 'queue_run_seconds')
if service == 'sonarr':
missing_days = self.config.getint(section, 'missing_days') missing_days = self.config.getint(section, 'missing_days')
future_days = self.config.getint(section, 'future_days') future_days = self.config.getint(section, 'future_days')
missing_days_run_seconds = self.config.getint(section, 'missing_days_run_seconds') missing_days_run_seconds = self.config.getint(section, 'missing_days_run_seconds')
future_days_run_seconds = self.config.getint(section, 'future_days_run_seconds') future_days_run_seconds = self.config.getint(section, 'future_days_run_seconds')
queue_run_seconds = self.config.getint(section, 'queue_run_seconds')
server = SonarrServer(id=server_id, url=scheme + url, api_key=apikey, verify_ssl=verify_ssl, server = SonarrServer(id=server_id, url=scheme + url, api_key=apikey, verify_ssl=verify_ssl,
missing_days=missing_days, future_days=future_days, missing_days=missing_days, future_days=future_days,
missing_days_run_seconds=missing_days_run_seconds, missing_days_run_seconds=missing_days_run_seconds,
@ -197,12 +194,7 @@ class INIParser(object):
queue=queue, queue_run_seconds=queue_run_seconds) queue=queue, queue_run_seconds=queue_run_seconds)
if service == 'radarr': if service == 'radarr':
queue = self.config.getboolean(section, 'queue')
queue_run_seconds = self.config.getint(section, 'queue_run_seconds')
get_missing = self.config.getboolean(section, 'get_missing') get_missing = self.config.getboolean(section, 'get_missing')
get_missing_run_seconds = self.config.getint(section, 'get_missing_run_seconds') get_missing_run_seconds = self.config.getint(section, 'get_missing_run_seconds')
server = RadarrServer(id=server_id, url=scheme + url, api_key=apikey, verify_ssl=verify_ssl, server = RadarrServer(id=server_id, url=scheme + url, api_key=apikey, verify_ssl=verify_ssl,
@ -212,18 +204,17 @@ class INIParser(object):
if service == 'tautulli': if service == 'tautulli':
fallback_ip = self.config.get(section, 'fallback_ip') fallback_ip = self.config.get(section, 'fallback_ip')
get_stats = self.config.getboolean(section, 'get_stats')
get_activity = self.config.getboolean(section, 'get_activity') get_activity = self.config.getboolean(section, 'get_activity')
get_activity_run_seconds = self.config.getint(section, 'get_activity_run_seconds') get_activity_run_seconds = self.config.getint(section, 'get_activity_run_seconds')
get_stats = self.config.getboolean(section, 'get_stats')
get_stats_run_seconds = self.config.getint(section, 'get_stats_run_seconds') get_stats_run_seconds = self.config.getint(section, 'get_stats_run_seconds')
invalid_wan_ip = rfc1918_ip_check(fallback_ip) invalid_wan_ip = rfc1918_ip_check(fallback_ip)
if invalid_wan_ip: if invalid_wan_ip:
self.logger.error('Invalid failback_ip [%s] set for %s-%s!', fallback_ip, service, server_id) self.logger.error('Invalid fallback_ip [%s] set for %s-%s!', fallback_ip, service,
server_id)
exit(1) exit(1)
server = TautulliServer(id=server_id, url=scheme + url, api_key=apikey, server = TautulliServer(id=server_id, url=scheme + url, api_key=apikey,
@ -233,17 +224,13 @@ class INIParser(object):
get_stats_run_seconds=get_stats_run_seconds) get_stats_run_seconds=get_stats_run_seconds)
if service == 'ombi': if service == 'ombi':
issue_status_counts = self.config.getboolean(section, 'get_issue_status_counts')
request_type_counts = self.config.getboolean(section, 'get_request_type_counts') request_type_counts = self.config.getboolean(section, 'get_request_type_counts')
request_type_run_seconds = self.config.getint(section, 'request_type_run_seconds')
request_total_counts = self.config.getboolean(section, 'get_request_total_counts') request_total_counts = self.config.getboolean(section, 'get_request_total_counts')
request_total_run_seconds = self.config.getint(section, 'request_total_run_seconds')
issue_status_counts = self.config.getboolean(section, 'get_issue_status_counts')
issue_status_run_seconds = self.config.getint(section, 'issue_status_run_seconds') issue_status_run_seconds = self.config.getint(section, 'issue_status_run_seconds')
request_type_run_seconds = self.config.getint(section, 'request_type_run_seconds')
request_total_run_seconds = self.config.getint(section, 'request_total_run_seconds')
server = OmbiServer(id=server_id, url=scheme + url, api_key=apikey, verify_ssl=verify_ssl, server = OmbiServer(id=server_id, url=scheme + url, api_key=apikey, verify_ssl=verify_ssl,
request_type_counts=request_type_counts, request_type_counts=request_type_counts,
@ -255,20 +242,18 @@ class INIParser(object):
if service == 'sickchill': if service == 'sickchill':
get_missing = self.config.getboolean(section, 'get_missing') get_missing = self.config.getboolean(section, 'get_missing')
get_missing_run_seconds = self.config.getint(section, 'get_missing_run_seconds') get_missing_run_seconds = self.config.getint(section, 'get_missing_run_seconds')
server = SickChillServer(id=server_id, url=scheme + url, api_key=apikey, server = SickChillServer(id=server_id, url=scheme + url, api_key=apikey,
verify_ssl=verify_ssl, get_missing=get_missing, verify_ssl=verify_ssl, get_missing=get_missing,
get_missing_run_seconds=get_missing_run_seconds) get_missing_run_seconds=get_missing_run_seconds)
if service == 'ciscoasa': if service in ['ciscoasa', 'unifi']:
username = self.config.get(section, 'username') username = self.config.get(section, 'username')
password = self.config.get(section, 'password') password = self.config.get(section, 'password')
if service == 'ciscoasa':
outside_interface = self.config.get(section, 'outside_interface') outside_interface = self.config.get(section, 'outside_interface')
get_bandwidth_run_seconds = self.config.getint(section, 'get_bandwidth_run_seconds') get_bandwidth_run_seconds = self.config.getint(section, 'get_bandwidth_run_seconds')
server = CiscoASAFirewall(id=server_id, url=scheme + url, verify_ssl=verify_ssl, server = CiscoASAFirewall(id=server_id, url=scheme + url, verify_ssl=verify_ssl,
@ -276,8 +261,16 @@ class INIParser(object):
outside_interface=outside_interface, outside_interface=outside_interface,
get_bandwidth_run_seconds=get_bandwidth_run_seconds) get_bandwidth_run_seconds=get_bandwidth_run_seconds)
getattr(self, f'{service}_servers').append(server) if service == 'unifi':
site = self.config.get(section, 'site').lower()
usg_name = self.config.get(section, 'usg_name')
get_usg_stats_run_seconds = self.config.getint(section, 'get_usg_stats_run_seconds')
server = UniFiServer(id=server_id, url=scheme + url, verify_ssl=verify_ssl, site=site,
username=username, password=password, usg_name=usg_name,
get_usg_stats_run_seconds=get_usg_stats_run_seconds)
getattr(self, f'{service}_servers').append(server)
except NoOptionError as e: except NoOptionError as e:
self.logger.error('Missing key in %s. Error: %s', section, e) self.logger.error('Missing key in %s. Error: %s', section, e)
self.rectify_ini() self.rectify_ini()

View file

@ -86,6 +86,17 @@ class CiscoASAFirewall(NamedTuple):
verify_ssl: bool = False verify_ssl: bool = False
class UniFiServer(NamedTuple):
get_usg_stats_run_seconds: int = 30
id: int = None
password: str = 'ubnt'
site: str = None
url: str = 'unifi.domain.tld:8443'
username: str = 'ubnt'
usg_name: str = None
verify_ssl: bool = False
# Shared # Shared
class Queue(NamedTuple): class Queue(NamedTuple):
downloadId: str = None downloadId: str = None

View file

@ -52,7 +52,7 @@ class TautulliAPI(object):
geodata = self.geoiphandler.lookup(session.ip_address_public) geodata = self.geoiphandler.lookup(session.ip_address_public)
except (ValueError, AddressNotFoundError): except (ValueError, AddressNotFoundError):
if self.server.fallback_ip: if self.server.fallback_ip:
# Try the failback ip in the config file # Try the fallback ip in the config file
try: try:
geodata = self.geoiphandler.lookup(self.server.fallback_ip) geodata = self.geoiphandler.lookup(self.server.fallback_ip)
except AddressNotFoundError as e: except AddressNotFoundError as e:

75
varken/unifi.py Normal file
View file

@ -0,0 +1,75 @@
from time import time
from logging import getLogger
from requests import Session, Request
from datetime import datetime, timezone
from varken.helpers import connection_handler
class UniFiAPI(object):
def __init__(self, server, dbmanager):
self.dbmanager = dbmanager
self.server = server
# Create session to reduce server web thread load, and globally define pageSize for all requests
self.session = Session()
self.logger = getLogger()
self.get_cookie()
def __repr__(self):
return f"<unifi-{self.server.id}>"
def get_cookie(self):
endpoint = '/api/login'
pre_cookies = {'username': self.server.username, 'password': self.server.password, 'remember': True}
req = self.session.prepare_request(Request('POST', self.server.url + endpoint, json=pre_cookies))
post = connection_handler(self.session, req, self.server.verify_ssl, as_is_reply=True)
if not post.cookies.get('unifises'):
return
cookies = {'unifises': post.cookies.get('unifises')}
self.session.cookies.update(cookies)
def get_usg_stats(self):
now = datetime.now(timezone.utc).astimezone().isoformat()
endpoint = f'/api/s/{self.server.site}/stat/device'
req = self.session.prepare_request(Request('GET', self.server.url + endpoint))
get = connection_handler(self.session, req, self.server.verify_ssl)
if not get:
return
devices = {device['name']: device for device in get['data']}
if devices.get(self.server.usg_name):
device = devices[self.server.usg_name]
else:
self.logger.error("Could not find a USG named %s from your UniFi Controller", self.server.usg_name)
return
influx_payload = [
{
"measurement": "UniFi",
"tags": {
"model": device['model'],
"name": device['name']
},
"time": now,
"fields": {
"bytes_current": device['wan1']['bytes-r'],
"rx_bytes_total": device['wan1']['rx_bytes'],
"rx_bytes_current": device['wan1']['rx_bytes-r'],
"tx_bytes_total": device['wan1']['tx_bytes'],
"tx_bytes_current": device['wan1']['tx_bytes-r'],
"speedtest_latency": device['speedtest-status']['latency'],
"speedtest_download": device['speedtest-status']['xput_download'],
"speedtest_upload": device['speedtest-status']['xput_upload'],
"cpu_loadavg_1": device['sys_stats']['loadavg_1'],
"cpu_loadavg_5": device['sys_stats']['loadavg_5'],
"cpu_loadavg_15": device['sys_stats']['loadavg_15'],
"cpu_util": device['system-stats']['cpu'],
"mem_util": device['system-stats']['mem'],
}
}
]
self.dbmanager.write_points(influx_payload)