-
Pelle Koster authoredPelle Koster authored
configuration.py 9.40 KiB
import json
import logging
import logging.config
import os
from typing import Union
import jsonschema
from brian_polling_manager import inventory
logger = logging.getLogger(__name__)
_DEFAULT_LOGGING_FILENAME = os.path.join(
os.path.dirname(__file__),
'logging_default_config.json')
_DEFAULT_CONFIG = {
'inventory': [
'http://test-inventory-provider01.geant.org:8080',
'http://test-inventory-provider02.geant.org:8080'
],
'sensu': {
'api-base': [
'https://test-poller-sensu-agent01.geant.org:8080',
'https://test-poller-sensu-agent02.geant.org:8080',
'https://test-poller-sensu-agent03.geant.org:8080'
],
'api-key': '696a815c-607e-4090-81de-58988c83033e',
'interface-check': {
'script': '/home/brian_checks/venv/get-interface-stats',
'config': '/var/lib/sensu/conf/get-interface-stats.config.json',
'command': '{script} --config {config} --juniper {hostname}',
},
'gws-direct-interface-check': {
'script': '/var/lib/sensu/bin/poll-gws-direct.sh',
'measurement': 'gwsd_counters',
'command': '{script} --inventory http://localhost:18080'
' {measurement} {nren} {isp} {hostname} {tag}'
},
'dscp32-service-check': {
'script': '/var/lib/sensu/bin/poll-gws-indirect.sh',
'measurement': 'dscp32_counters',
'command': '{script} {measurement} {service}'
},
'eumetsat-multicast-check': {
'script': '/home/brian_checks/venv/eumetsat-multicast',
'measurement': 'multicast',
'command': '{script} --inventory http://localhost:18080'
' --measurement {measurement} --hostname {hostname}'
}
},
'statedir': '/tmp/',
'logging': _DEFAULT_LOGGING_FILENAME,
'statsd': {
'hostname': 'localhost',
'port': 8125,
'prefix': 'brian_polling'
}
}
CONFIG_SCHEMA = {
'$schema': 'http://json-schema.org/draft-07/schema#',
'definitions': {
'influx-check': {
'type': 'object',
'properties': {
'script': {'type': 'string'},
'measurement': {'type': 'string'},
'command': {'type': 'string'},
},
'required': ['script', 'measurement', 'command'],
'additionalProperties': False
},
'router-check': {
'type': 'object',
'properties': {
'script': {'type': 'string'},
'config': {'type': 'string'},
'command': {'type': 'string'},
},
'required': ['script', 'config', 'command'],
'additionalProperties': False
},
'sensu': {
'type': 'object',
'properties': {
'api-base': {
'type': 'array',
'items': {'type': 'string'},
'minItems': 1
},
'api-key': {'type': 'string'},
'interface-check':
{'$ref': '#/definitions/router-check'},
'gws-direct-interface-check':
{'$ref': '#/definitions/influx-check'},
'dscp32-service-check':
{'$ref': '#/definitions/influx-check'},
'eumetsat-multicast-check':
{'$ref': '#/definitions/influx-check'},
},
'required': [
'api-base', 'api-key',
'interface-check',
'gws-direct-interface-check',
'dscp32-service-check',
'eumetsat-multicast-check'],
'additionalProperties': False
},
'statsd': {
'type': 'object',
'properties': {
'hostname': {'type': 'string'},
'port': {'type': 'integer'},
'prefix': {'type': 'string'}
},
'required': ['hostname', 'port', 'prefix'],
'additionalProperties': False
}
},
'type': 'object',
'properties': {
'inventory': {
'type': 'array',
'items': {'type': 'string'},
'minItems': 1
},
'sensu': {'$ref': '#/definitions/sensu'},
'statedir': {'type': 'string'},
'logging': {'type': 'string'},
'statsd': {'$ref': '#/definitions/statsd'}
},
'required': ['inventory', 'sensu', 'statedir'],
'additionalProperties': False
}
class State(object):
GWS_DIRECT = 'gws-direct.json'
GWS_INDIRECT = 'gws-indirect.json'
INTERFACES = 'interfaces.json'
STATE = 'state.json'
EUMET_MC = 'eumetsat-multicast.json'
STATE_SCHEMA = {
'$schema': 'http://json-schema.org/draft-07/schema#',
'type': 'object',
'properties': {
'last': {'type': 'number'}
},
'required': ['last'],
'additionalProperties': False
}
def __init__(self, state_dir: str):
assert os.path.isdir(state_dir)
self.cache_filenames = {
'state': os.path.join(state_dir, State.STATE),
'interfaces': os.path.join(state_dir, State.INTERFACES),
'gws-direct': os.path.join(state_dir, State.GWS_DIRECT),
'gws-indirect': os.path.join(state_dir, State.GWS_INDIRECT),
'eumetsat-multicast': os.path.join(state_dir, State.EUMET_MC)
}
@staticmethod
def _load_json(filename, schema):
try:
with open(filename) as f:
state = json.loads(f.read())
jsonschema.validate(state, schema)
return state
except (json.JSONDecodeError, jsonschema.ValidationError, OSError):
logger.exception(
f'unable to open state file {filename}')
return None
@staticmethod
def _save_json(filename, new_data, schema):
try:
jsonschema.validate(new_data, schema)
except jsonschema.ValidationError:
logger.exception('invalid interface state data')
return
with open(filename, 'w') as f:
f.write(json.dumps(new_data))
@property
def last(self) -> int:
state = State._load_json(
self.cache_filenames['state'], State.STATE_SCHEMA)
return state['last'] if state else -1
@last.setter
def last(self, new_last: Union[float, None]):
if not new_last or new_last < 0:
os.unlink(self.cache_filenames['state'])
else:
State._save_json(
self.cache_filenames['state'],
{'last': new_last},
State.STATE_SCHEMA)
@property
def interfaces(self) -> list:
return State._load_json(
self.cache_filenames['interfaces'],
inventory.INVENTORY_INTERFACES_SCHEMA)
@interfaces.setter
def interfaces(self, new_interfaces):
State._save_json(
self.cache_filenames['interfaces'],
new_interfaces,
inventory.INVENTORY_INTERFACES_SCHEMA)
@property
def gws_direct(self) -> list:
return State._load_json(
self.cache_filenames['gws-direct'],
inventory.GWS_DIRECT_SCHEMA)
@gws_direct.setter
def gws_direct(self, new_interfaces):
State._save_json(
self.cache_filenames['gws-direct'],
new_interfaces,
inventory.GWS_DIRECT_SCHEMA)
@property
def gws_indirect(self) -> list:
return State._load_json(
self.cache_filenames['gws-indirect'],
inventory.GWS_INDIRECT_SCHEMA)
@gws_indirect.setter
def gws_indirect(self, new_services):
State._save_json(
self.cache_filenames['gws-indirect'],
new_services,
inventory.GWS_INDIRECT_SCHEMA)
@property
def eumetsat_multicast(self) -> list:
return State._load_json(
self.cache_filenames['eumetsat-multicast'],
inventory.MULTICAST_SUBSCRIPTION_LIST_SCHEMA)
@eumetsat_multicast.setter
def eumetsat_multicast(self, new_subscriptions):
State._save_json(
self.cache_filenames['eumetsat-multicast'],
new_subscriptions,
inventory.MULTICAST_SUBSCRIPTION_LIST_SCHEMA)
def _setup_logging(filename=None):
"""
set up logging using the configured filename
:raises: json.JSONDecodeError, OSError, AttributeError,
ValueError, TypeError, ImportError
"""
if not filename:
filename = _DEFAULT_LOGGING_FILENAME
with open(filename) as f:
# TODO: this mac workaround should be removed ...
d = json.loads(f.read())
import platform
if platform.system() == 'Darwin':
d['handlers']['syslog_handler']['address'] = '/var/run/syslog'
logging.config.dictConfig(d)
# logging.config.dictConfig(json.loads(f.read()))
def load_config(file=None):
"""
loads, validates and returns configuration parameters
:param value: filename (file-like object, opened for reading)
:return: a dict containing configuration parameters
:raises: json.JSONDecodeError, jsonschema.ValidationError,
OSError, AttributeError, ValueError, TypeError, ImportError
"""
if not file:
config = _DEFAULT_CONFIG
else:
config = json.loads(file.read())
jsonschema.validate(config, CONFIG_SCHEMA)
_setup_logging(config.get('logging', None))
return config