Skip to content
Snippets Groups Projects
test_juniper_data.py 5.68 KiB
import json
import os
import re

import jsonschema
import pytest

from inventory_provider import juniper

TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data")

ROUTER_HOSTNAMES = [
    'mx2.ath.gr.geant.net',
    'mx1.tal.ee.geant.net',
    'mx2.tal.ee.geant.net',
    'mx2.rig.lv.geant.net',
    'mx1.kau.lt.geant.net',
    'mx2.kau.lt.geant.net',
    'mx2.zag.hr.geant.net',
    'mx2.lju.si.geant.net',
    'mx1.bud.hu.geant.net',
    'mx1.pra.cz.geant.net',
    'mx2.bra.sk.geant.net',
    'mx1.lon.uk.geant.net',
    'mx1.vie.at.geant.net',
    'mx2.bru.be.geant.net',
    'mx1.poz.pl.geant.net',
    'mx1.ams.nl.geant.net',
    'mx1.fra.de.geant.net',
    'mx1.par.fr.geant.net',
    'mx1.gen.ch.geant.net',
    'mx1.mil2.it.geant.net',
    'mx1.lis.pt.geant.net',
    'mx2.lis.pt.geant.net',
    'mx1.mad.es.geant.net',
    'mx1.sof.bg.geant.net',
    'mx1.buc.ro.geant.net',
    'mx1.ham.de.geant.net',
    'mx1.dub.ie.geant.net',
    'mx1.dub2.ie.geant.net',
    'mx1.mar.fr.geant.net',
    'mx1.lon2.uk.geant.net',
    'mx1.ath2.gr.geant.net',
]

CACHE_SCHEMA = {
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "patternProperties": {
        "^.*\\.geant\\.net$": {
            "type": "object",
            "properties": {
                "bgp": {"type": "object"},
                "vrr": {"type": "object"},
                "interfaces": {"type": "object"},
                "snmp-interfaces": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "v4Address": {"type": "string"},
                            "v4Mask": {"type": "string"},
                            "v4InterfaceName": {"type": "string"},
                            "v6Address": {"type": "string"},
                            "v6Mask": {"type": "string"},
                            "v6InterfaceName": {"type": "string"},
                        },
                        "additionalProperties": False
                    }
                }
            },
            "required": ["bgp", "vrr", "interfaces", "snmp-interfaces"],
            "additionalProperties": False
        }
    },
    "additionalProperties": False
}


@pytest.fixture
def cached_test_data():
    with open("router-info.json") as f:
        cache = json.loads(f.read())
    jsonschema.validate(cache, CACHE_SCHEMA)
    return cache


def _parsed_old_style_output_data(s):
    for l in s.splitlines():
        if not l:
            continue

        m = re.match(
            (r'^set\s+routing-instances\s+(\S+)+\s+'
             r'protocols\s+(\S+)\s+'
             r'group\s+(\S+)\s+'
             r'neighbor\s+([a-f\d\.:]+)\s+'
             r'description\s+"?([^"]+)"?\s*$'), l)
        assert m

        yield {
            "routing-instances": m.group(1),
            "protocols": m.group(2),
            "group": m.group(3),
            "neighbor": m.group(4),
            "description": m.group(5)
        }


@pytest.fixture(params=ROUTER_HOSTNAMES)
def router_output(request):
    output = {}
    for key in ["bgpv4", "bgpv6", "bgp", "interfaces", "vrr"]:
        filename = os.path.join(
            TEST_DATA_DIR,
            "%s-%s.output" % (request.param, key))
        with open(filename) as f:
            output[key] = f.read()
    return output


def test_ipv4_neighbors(mocker, router_output):

    old_v4_data = dict([
        (x["neighbor"], x)
        for x in
        _parsed_old_style_output_data(router_output["bgpv4"])])

    def _mocked_ssh_exec_commands(hostname, params, commands):
        assert len(commands) == 2  # the expected number
        return [None, router_output["bgp"]]

    mocker.patch(
        'inventory_provider.juniper.ssh_exec_commands',
        _mocked_ssh_exec_commands)

    neighbors = juniper.fetch_bgp_config(
        None,
        None,
        group_expression=r'^GEANT-IX[\s-].*$')

    assert len(neighbors) == len(old_v4_data)
    for n in neighbors:
        address = n["name"]["data"]
        description = n["description"][0]["data"]
        assert old_v4_data[address]["description"] == description


def test_ipv6_neighbors(mocker, router_output):

    old_v6_data = dict([
        (x["neighbor"], x)
        for x in
        _parsed_old_style_output_data(router_output["bgpv6"])])

    def _mocked_ssh_exec_commands(hostname, params, commands):
        assert len(commands) == 2  # the expected number
        return [None, router_output["bgp"]]

    mocker.patch(
        'inventory_provider.juniper.ssh_exec_commands',
        _mocked_ssh_exec_commands)

    neighbors = juniper.fetch_bgp_config(
        None,
        None,
        group_expression=r'^GEANT-IXv6[\s-].*$')

    assert len(neighbors) == len(old_v6_data)
    for n in neighbors:
        address = n["name"]["data"]
        description = n["description"][0]["data"]
        assert old_v6_data[address]["description"] == description


COMMAND_HANDLERS = {
    'bgp': juniper.fetch_bgp_config,
    'vrr': juniper.fetch_vrr_config,
    'interfaces': juniper.fetch_interfaces
}


def test_juniper_shell_output_parsing(mocker, router_output):
    """
    just call the correct parser for each type of shell output
    (not a proper test ... just verifies there's no crash)

    TODO: add jsonschema validation
    :param router_output:
    :return:
    """

    output = None

    def _mocked_ssh_exec_commands(hostname, params, commands):
        assert len(commands) == 2  # the expected number
        return [None, output]

    mocker.patch(
        'inventory_provider.juniper.ssh_exec_commands',
        _mocked_ssh_exec_commands)

    for key in ["bgp", "vrr", "interfaces"]:
        output = router_output[key]
        COMMAND_HANDLERS[key](None, None)