Skip to content
Snippets Groups Projects
Commit 026bcfa2 authored by Pelle Koster's avatar Pelle Koster
Browse files

validate router host to inventory provider when given

parent ce714724
No related branches found
No related tags found
No related merge requests found
......@@ -4,7 +4,7 @@ import os
import pathlib
from datetime import datetime
from functools import partial
from typing import Callable, Iterable, List
from typing import Callable, Iterable, List, Sequence, Union
import click
......@@ -143,6 +143,22 @@ def get_routers_from_inventory_provider(vendor: Vendor, hosts: Iterable[str]):
return []
def validate_hosts(
router_fqdn: Sequence[str],
vendor: Vendor,
inprov_hosts: Union[str, Sequence[str], None] = None,
):
if inprov_hosts is None:
return
all_fqdns = get_routers_from_inventory_provider(vendor, inprov_hosts)
extra_fqdns = set(router_fqdn) - set(all_fqdns)
if extra_fqdns:
raise ValueError(
f"Routers are not in inventory provider or not {vendor.value}: "
f"{' '.join(extra_fqdns)}"
)
def main(
app_config_params: dict,
juniper_fqdns: List[str],
......@@ -153,27 +169,28 @@ def main(
):
if not juniper_fqdns and not nokia_fqdns:
inprov_hosts = app_config_params.get("inventory", [])
if not inprov_hosts:
raise ValueError("Must supply at least one inventory provider in config")
juniper_fqdns = get_routers_from_inventory_provider(
Vendor.JUNIPER, inprov_hosts
raise click.ClickException(
"At least one --juniper or --nokia router is required"
)
nokia_fqdns = get_routers_from_inventory_provider(Vendor.NOKIA, inprov_hosts)
inprov_hosts = app_config_params.get("inventory", None)
if juniper_fqdns:
juniper_ssh_params = app_config_params.get("juniper")
validate_hosts(juniper_fqdns, vendor=Vendor.JUNIPER, inprov_hosts=inprov_hosts)
juniper_ssh_params = app_config_params.get(Vendor.JUNIPER.value)
if not juniper_ssh_params:
raise ValueError("'juniper' ssh params are required")
raise ValueError(f"'{Vendor.JUNIPER.value}' ssh params are required")
juniper_netconf_provider = get_netconf_provider_(
Vendor.JUNIPER, juniper_ssh_params
)
if nokia_fqdns:
nokia_ssh_params = app_config_params.get("nokia")
validate_hosts(juniper_fqdns, vendor=Vendor.NOKIA, inprov_hosts=inprov_hosts)
nokia_ssh_params = app_config_params.get(Vendor.NOKIA.value)
if not nokia_ssh_params:
raise ValueError("'nokia' ssh params are required")
raise ValueError(f"'{Vendor.JUNIPER.value}' ssh params are required")
nokia_netconf_provider = get_netconf_provider_(Vendor.NOKIA, nokia_ssh_params)
......@@ -227,7 +244,7 @@ def main(
"nokia_fqdns",
multiple=True,
type=click.STRING,
help="nuniper router fqdn(s)",
help="nokia router fqdn(s)",
callback=validate_hostname,
)
@click.option(
......
......@@ -3,16 +3,21 @@ import os
import random
import re
import tempfile
from unittest.mock import patch
import brian_polling_manager.configuration
import jsonschema
import pytest
import responses
@pytest.fixture(autouse=True)
def mocked_setup_logging():
with patch.object(brian_polling_manager.configuration, "_setup_logging") as mock:
yield mock
def _load_test_data(filename):
full_path = os.path.join(
os.path.dirname(__file__),
'data', filename)
full_path = os.path.join(os.path.dirname(__file__), "data", filename)
with open(full_path) as f:
return f.read()
......@@ -21,57 +26,53 @@ def _load_test_data(filename):
def config():
with tempfile.TemporaryDirectory() as state_dir_name:
yield {
'inventory': [
'http://bogus-inventory01.xxx.yyy:12345',
'http://bogus-inventory02.xxx.yyy:12345',
'http://bogus-inventory03.xxx.yyy:12345'
"inventory": [
"http://bogus-inventory01.xxx.yyy:12345",
"http://bogus-inventory02.xxx.yyy:12345",
"http://bogus-inventory03.xxx.yyy:12345",
],
'sensu': {
'api-base': [
'https://bogus-sensu01.xxx.yyy:12345',
'https://bogus-sensu02.xxx.yyy:12345',
'https://bogus-sensu03.xxx.yyy:12345'
"sensu": {
"api-base": [
"https://bogus-sensu01.xxx.yyy:12345",
"https://bogus-sensu02.xxx.yyy:12345",
"https://bogus-sensu03.xxx.yyy:12345",
],
'api-key': 'abc-sensu-key-blah-blah',
'interface-check': {
'script': '/var/lib/sensu/bin/counter2influx.sh',
'measurement': 'counters',
'command': '{script} {measurement}'
' {community} {hostname}'
' {interface} {ifIndex}',
"api-key": "abc-sensu-key-blah-blah",
"interface-check": {
"script": "/var/lib/sensu/bin/counter2influx.sh",
"measurement": "counters",
"command": "{script} {measurement}"
" {community} {hostname}"
" {interface} {ifIndex}",
},
"gws-direct-interface-check": {
"script": "/var/lib/sensu/bin/poll-gws-direct.sh",
"measurement": "gwsd_counters",
"command": "{script} {measurement}"
" {nren} {isp} {hostname} {tag}",
},
'gws-direct-interface-check': {
'script': '/var/lib/sensu/bin/poll-gws-direct.sh',
'measurement': 'gwsd_counters',
'command': '{script} {measurement}'
' {nren} {isp} {hostname} {tag}'
"dscp32-service-check": {
"script": "/var/lib/sensu/bin/poll-gws-indirect.sh",
"measurement": "dscp32_counters",
"command": "{script} {measurement} {service}",
},
'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}",
},
'eumetsat-multicast-check': {
'script': '/home/brian_checks/venv/eumetsat-multicast',
'measurement': 'multicast',
'command': '{script}'
' --inventory http://localhost:18080'
' --measurement {measurement}'
' --hostname {hostname}'
}
},
'statedir': state_dir_name,
'statsd': {
'hostname': 'localhost',
'port': 11119,
'prefix': 'zzzzz'
}
"statedir": state_dir_name,
"statsd": {"hostname": "localhost", "port": 11119, "prefix": "zzzzz"},
}
@pytest.fixture
def config_filename(config):
with tempfile.NamedTemporaryFile(mode='w') as f:
with tempfile.NamedTemporaryFile(mode="w") as f:
f.write(json.dumps(config))
f.flush()
yield f.name
......@@ -81,114 +82,118 @@ def config_filename(config):
def mocked_sensu():
saved_sensu_checks = {
c['metadata']['name']: c
for c in json.loads(_load_test_data('checks.json'))}
c["metadata"]["name"]: c for c in json.loads(_load_test_data("checks.json"))
}
_check_schema = {
'$schema': 'http://json-schema.org/draft-07/schema#',
'type': 'object',
'properties': {
'command': {'type': 'string'},
'interval': {'type': 'integer'},
'subscriptions': {'type': 'array', 'items': {'type': 'string'}},
'proxy_entity_name': {'type': 'string'},
'round_robin': {'type': 'boolean'},
'output_metric_format': {'enum': ['influxdb_line']},
'output_metric_handlers': {
'type': 'array',
'items': {'type': 'string'}
},
'metadata': {
'type': 'object',
'properties': {
'name': {'type': 'string'},
'namespace': {'type': 'string'}
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"command": {"type": "string"},
"interval": {"type": "integer"},
"subscriptions": {"type": "array", "items": {"type": "string"}},
"proxy_entity_name": {"type": "string"},
"round_robin": {"type": "boolean"},
"output_metric_format": {"enum": ["influxdb_line"]},
"output_metric_handlers": {"type": "array", "items": {"type": "string"}},
"metadata": {
"type": "object",
"properties": {
"name": {"type": "string"},
"namespace": {"type": "string"},
},
'required': ['name', 'namespace'],
'additionalProperties': True
"required": ["name", "namespace"],
"additionalProperties": True,
},
},
'required': [
'command', 'interval', 'subscriptions',
'output_metric_format', 'output_metric_handlers',
'round_robin', 'metadata'],
'additionalProperties': True
"required": [
"command",
"interval",
"subscriptions",
"output_metric_format",
"output_metric_handlers",
"round_robin",
"metadata",
],
"additionalProperties": True,
}
# mocked api for returning all checks
responses.add(
method=responses.GET,
url=re.compile(r'.*sensu.+/api/core/v2/namespaces/[^\/]+/checks$'),
json=list(saved_sensu_checks.values())
url=re.compile(r".*sensu.+/api/core/v2/namespaces/[^\/]+/checks$"),
json=list(saved_sensu_checks.values()),
)
def new_check_callback(request):
check = json.loads(request.body)
jsonschema.validate(check, _check_schema)
path_elems = request.path_url.split('/')
path_elems = request.path_url.split("/")
assert len(path_elems) == 7 # sanity
assert path_elems[5] == check['metadata']['namespace']
assert check['metadata']['name'] not in saved_sensu_checks
saved_sensu_checks[check['metadata']['name']] = check
return (201, {}, '')
assert path_elems[5] == check["metadata"]["namespace"]
assert check["metadata"]["name"] not in saved_sensu_checks
saved_sensu_checks[check["metadata"]["name"]] = check
return (201, {}, "")
# mocked api for creating a check
responses.add_callback(
method=responses.POST,
url=re.compile(r'.*sensu.+/api/core/v2/namespaces/[^\/]+/checks$'),
callback=new_check_callback)
url=re.compile(r".*sensu.+/api/core/v2/namespaces/[^\/]+/checks$"),
callback=new_check_callback,
)
def update_check_callback(request):
check = json.loads(request.body)
jsonschema.validate(check, _check_schema)
path_elems = request.path_url.split('/')
path_elems = request.path_url.split("/")
assert len(path_elems) == 8 # sanity
assert path_elems[5] == check['metadata']['namespace']
assert path_elems[-1] == check['metadata']['name']
assert check['metadata']['name'] in saved_sensu_checks, \
'we only intend to call this method for updating existing checks'
saved_sensu_checks[check['metadata']['name']] = check
return 201, {}, ''
assert path_elems[5] == check["metadata"]["namespace"]
assert path_elems[-1] == check["metadata"]["name"]
assert (
check["metadata"]["name"] in saved_sensu_checks
), "we only intend to call this method for updating existing checks"
saved_sensu_checks[check["metadata"]["name"]] = check
return 201, {}, ""
# mocked api for updating a check
responses.add_callback(
method=responses.PUT,
url=re.compile(
r'.*sensu.+/api/core/v2/namespaces/[^\/]+/checks/[^\/]+$'),
callback=update_check_callback)
url=re.compile(r".*sensu.+/api/core/v2/namespaces/[^\/]+/checks/[^\/]+$"),
callback=update_check_callback,
)
def delete_check_callback(request):
path_elems = request.path_url.split('/')
path_elems = request.path_url.split("/")
assert len(path_elems) == 8 # sanity
del saved_sensu_checks[path_elems[-1]]
return 204, {}, ''
return 204, {}, ""
# mocked api for deleting a check
responses.add_callback(
method=responses.DELETE,
url=re.compile(
r'.*sensu.+/api/core/v2/namespaces/[^\/]+/checks/[^\/]+$'),
callback=delete_check_callback)
url=re.compile(r".*sensu.+/api/core/v2/namespaces/[^\/]+/checks/[^\/]+$"),
callback=delete_check_callback,
)
def get_events_callback(request):
return 200, {}, _load_test_data('events.json')
return 200, {}, _load_test_data("events.json")
# mocked api for returning all events
responses.add_callback(
method=responses.GET,
url=re.compile(r'.*sensu.+/api/core/v2/namespaces/[^\/]+/events$'),
callback=get_events_callback)
url=re.compile(r".*sensu.+/api/core/v2/namespaces/[^\/]+/events$"),
callback=get_events_callback,
)
def delete_event_callback(request):
return 204, {}, ''
return 204, {}, ""
# mocked api for deleting an event
responses.add_callback(
method=responses.DELETE,
url=re.compile(
r'.*sensu.+/api/core/v2/namespaces/default/events/.*$'),
callback=delete_event_callback)
url=re.compile(r".*sensu.+/api/core/v2/namespaces/default/events/.*$"),
callback=delete_event_callback,
)
yield saved_sensu_checks
......@@ -198,27 +203,32 @@ def mocked_inventory():
responses.add(
method=responses.GET,
url=re.compile(r'.*inventory.+/poller/interfaces.*'),
body=_load_test_data('interfaces.json'))
url=re.compile(r".*inventory.+/poller/interfaces.*"),
body=_load_test_data("interfaces.json"),
)
responses.add(
method=responses.GET,
url=re.compile(r'.*inventory.+/poller/gws/direct'),
body=_load_test_data('gws-direct.json'))
url=re.compile(r".*inventory.+/poller/gws/direct"),
body=_load_test_data("gws-direct.json"),
)
responses.add(
method=responses.GET,
url=re.compile(r'.*inventory.+/poller/gws/indirect.*'),
body=_load_test_data('gws-indirect.json'))
url=re.compile(r".*inventory.+/poller/gws/indirect.*"),
body=_load_test_data("gws-indirect.json"),
)
responses.add(
method=responses.GET,
url=re.compile(r'.*inventory.+/poller/eumetsat-multicast'),
body=_load_test_data('eumetsat-multicast.json'))
url=re.compile(r".*inventory.+/poller/eumetsat-multicast"),
body=_load_test_data("eumetsat-multicast.json"),
)
bogus_version = {'latch': {'timestamp': 10000 * random.random()}}
bogus_version = {"latch": {"timestamp": 10000 * random.random()}}
# mocked api for returning all checks
responses.add(
method=responses.GET,
url=re.compile(r'.*inventory.+/version.*'),
body=json.dumps(bogus_version))
url=re.compile(r".*inventory.+/version.*"),
body=json.dumps(bogus_version),
)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment