forked from mirrors/thatmattlove-hyperglass
improve supported NOS validation, should actually fix #41
This commit is contained in:
parent
3a6cc82862
commit
be1c6831a4
5 changed files with 76 additions and 281 deletions
|
|
@ -22,9 +22,11 @@ from hyperglass.constants import (
|
|||
CREDIT,
|
||||
DEFAULT_HELP,
|
||||
DEFAULT_TERMS,
|
||||
TRANSPORT_REST,
|
||||
DEFAULT_DETAILS,
|
||||
SUPPORTED_QUERY_TYPES,
|
||||
PARSED_RESPONSE_FIELDS,
|
||||
SUPPORTED_STRUCTURED_OUTPUT,
|
||||
__version__,
|
||||
)
|
||||
from hyperglass.exceptions import ConfigError, ConfigInvalid, ConfigMissing
|
||||
|
|
@ -93,7 +95,7 @@ def _config_required(config_path: Path) -> dict:
|
|||
"Unvalidated data from file '{f}': {c}", f=str(config_path), c=config
|
||||
)
|
||||
except (yaml.YAMLError, yaml.MarkedYAMLError) as yaml_error:
|
||||
raise ConfigError(error_msg=str(yaml_error))
|
||||
raise ConfigError(str(yaml_error))
|
||||
return config
|
||||
|
||||
|
||||
|
|
@ -114,6 +116,28 @@ def _config_optional(config_path: Path) -> dict:
|
|||
return config
|
||||
|
||||
|
||||
def _validate_nos_commands(all_nos, commands):
|
||||
nos_with_commands = commands.dict().keys()
|
||||
|
||||
for nos in all_nos:
|
||||
valid = False
|
||||
if nos in SUPPORTED_STRUCTURED_OUTPUT:
|
||||
valid = True
|
||||
elif nos in TRANSPORT_REST:
|
||||
valid = True
|
||||
elif nos in nos_with_commands:
|
||||
valid = True
|
||||
|
||||
if not valid:
|
||||
raise ConfigError(
|
||||
'"{nos}" is used on a device, '
|
||||
+ 'but no command profile for "{nos}" is defined.',
|
||||
nos=nos,
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
user_config = _config_optional(CONFIG_MAIN)
|
||||
|
||||
# Read raw debug value from config to enable debugging quickly.
|
||||
|
|
@ -136,6 +160,8 @@ except ValidationError as validation_errors:
|
|||
error_msg=error["msg"],
|
||||
)
|
||||
|
||||
_validate_nos_commands(devices.all_nos, commands)
|
||||
|
||||
set_cache_env(db=params.cache.database, host=params.cache.host, port=params.cache.port)
|
||||
|
||||
# Re-evaluate debug state after config is validated
|
||||
|
|
|
|||
|
|
@ -8,17 +8,12 @@ from pathlib import Path
|
|||
|
||||
# Third Party
|
||||
from pydantic import StrictInt, StrictStr, StrictBool, validator
|
||||
from netmiko.ssh_dispatcher import CLASS_MAPPER_BASE as NETMIKO_SUPPORTED
|
||||
|
||||
# Project
|
||||
from hyperglass.log import log
|
||||
from hyperglass.util import clean_name
|
||||
from hyperglass.util import clean_name, validate_nos
|
||||
from hyperglass.models import HyperglassModel, HyperglassModelExtra
|
||||
from hyperglass.constants import (
|
||||
SCRAPE_HELPERS,
|
||||
TRANSPORT_REST,
|
||||
SUPPORTED_STRUCTURED_OUTPUT,
|
||||
)
|
||||
from hyperglass.constants import SCRAPE_HELPERS, SUPPORTED_STRUCTURED_OUTPUT
|
||||
from hyperglass.exceptions import ConfigError, UnsupportedDevice
|
||||
from hyperglass.configuration.models.ssl import Ssl
|
||||
from hyperglass.configuration.models.vrfs import Vrf, Info
|
||||
|
|
@ -100,8 +95,10 @@ class Router(HyperglassModel):
|
|||
if value in SCRAPE_HELPERS.keys():
|
||||
value = SCRAPE_HELPERS[value]
|
||||
|
||||
if value not in (*TRANSPORT_REST, *NETMIKO_SUPPORTED.keys()):
|
||||
raise UnsupportedDevice('NOS "{n}" is not supported.', n=value)
|
||||
supported, _ = validate_nos(value)
|
||||
|
||||
if not supported:
|
||||
raise UnsupportedDevice('"{nos}" is not supported.', nos=value)
|
||||
|
||||
return value
|
||||
|
||||
|
|
@ -250,6 +247,7 @@ class Routers(HyperglassModelExtra):
|
|||
networks = set()
|
||||
display_vrfs = set()
|
||||
vrf_objects = set()
|
||||
all_nos = set()
|
||||
router_objects = []
|
||||
routers = Routers()
|
||||
routers.hostnames = []
|
||||
|
|
@ -260,64 +258,53 @@ class Routers(HyperglassModelExtra):
|
|||
# Validate each router config against Router() model/schema
|
||||
router = Router(**definition)
|
||||
|
||||
"""
|
||||
Set a class attribute for each router so each router's
|
||||
attributes can be accessed with `devices.router_hostname`
|
||||
"""
|
||||
# Set a class attribute for each router so each router's
|
||||
# attributes can be accessed with `devices.router_hostname`
|
||||
setattr(routers, router.name, router)
|
||||
|
||||
"""
|
||||
Add router-level attributes (assumed to be unique) to
|
||||
class lists, e.g. so all hostnames can be accessed as a
|
||||
list with `devices.hostnames`, same for all router
|
||||
classes, for when iteration over all routers is required.
|
||||
"""
|
||||
# Add router-level attributes (assumed to be unique) to
|
||||
# class lists, e.g. so all hostnames can be accessed as a
|
||||
# list with `devices.hostnames`, same for all router
|
||||
# classes, for when iteration over all routers is required.
|
||||
routers.hostnames.append(router.name)
|
||||
router_objects.append(router)
|
||||
all_nos.add(router.nos)
|
||||
|
||||
for vrf in router.vrfs:
|
||||
"""
|
||||
For each configured router VRF, add its name and
|
||||
display_name to a class set (for automatic de-duping).
|
||||
"""
|
||||
|
||||
# For each configured router VRF, add its name and
|
||||
# display_name to a class set (for automatic de-duping).
|
||||
vrfs.add(vrf.name)
|
||||
display_vrfs.add(vrf.display_name)
|
||||
|
||||
"""
|
||||
Also add the names to a router-level list so each
|
||||
router's VRFs and display VRFs can be easily accessed.
|
||||
"""
|
||||
# Also add the names to a router-level list so each
|
||||
# router's VRFs and display VRFs can be easily accessed.
|
||||
router.display_vrfs.append(vrf.display_name)
|
||||
router.vrf_names.append(vrf.name)
|
||||
|
||||
"""
|
||||
Add a 'default_vrf' attribute to the devices class
|
||||
which contains the configured default VRF display name.
|
||||
"""
|
||||
# Add a 'default_vrf' attribute to the devices class
|
||||
# which contains the configured default VRF display name.
|
||||
if vrf.name == "default" and not hasattr(cls, "default_vrf"):
|
||||
routers.default_vrf = {
|
||||
"name": vrf.name,
|
||||
"display_name": vrf.display_name,
|
||||
}
|
||||
|
||||
"""
|
||||
Add the native VRF objects to a set (for automatic
|
||||
de-duping), but exlcude device-specific fields.
|
||||
"""
|
||||
# Add the native VRF objects to a set (for automatic
|
||||
# de-duping), but exlcude device-specific fields.
|
||||
_copy_params = {
|
||||
"deep": True,
|
||||
"exclude": {"ipv4": {"source_address"}, "ipv6": {"source_address"}},
|
||||
}
|
||||
vrf_objects.add(vrf.copy(**_copy_params))
|
||||
|
||||
"""
|
||||
Convert the de-duplicated sets to a standard list, add lists
|
||||
as class attributes.
|
||||
"""
|
||||
# Convert the de-duplicated sets to a standard list, add lists
|
||||
# as class attributes.
|
||||
routers.vrfs = list(vrfs)
|
||||
routers.display_vrfs = list(display_vrfs)
|
||||
routers.vrf_objects = list(vrf_objects)
|
||||
routers.networks = list(networks)
|
||||
routers.all_nos = list(all_nos)
|
||||
|
||||
# Sort router list by router name attribute
|
||||
routers.routers = sorted(router_objects, key=lambda x: x.display_name)
|
||||
|
|
|
|||
|
|
@ -185,242 +185,7 @@ FUNC_COLOR_MAP = {
|
|||
|
||||
TRANSPORT_REST = ("frr", "bird")
|
||||
|
||||
TRANSPORT_SCRAPE = (
|
||||
"a10",
|
||||
"accedian",
|
||||
"alcatel_aos",
|
||||
"alcatel_sros",
|
||||
"apresia_aeos",
|
||||
"arista_eos",
|
||||
"aruba_os",
|
||||
"avaya_ers",
|
||||
"avaya_vsp",
|
||||
"brocade_fastiron",
|
||||
"brocade_netiron",
|
||||
"brocade_nos",
|
||||
"brocade_vdx",
|
||||
"brocade_vyos",
|
||||
"checkpoint_gaia",
|
||||
"calix_b6",
|
||||
"ciena_saos",
|
||||
"cisco_asa",
|
||||
"cisco_ios",
|
||||
"cisco_ios_telnet",
|
||||
"cisco_nxos",
|
||||
"cisco_s300",
|
||||
"cisco_tp",
|
||||
"cisco_wlc",
|
||||
"cisco_xe",
|
||||
"cisco_xr",
|
||||
"coriant",
|
||||
"dell_dnos9",
|
||||
"dell_force10",
|
||||
"dell_os6",
|
||||
"dell_os9",
|
||||
"dell_os10",
|
||||
"dell_powerconnect",
|
||||
"dell_isilon",
|
||||
"eltex",
|
||||
"enterasys",
|
||||
"extreme",
|
||||
"extreme_ers",
|
||||
"extreme_exos",
|
||||
"extreme_netiron",
|
||||
"extreme_nos",
|
||||
"extreme_slx",
|
||||
"extreme_vdx",
|
||||
"extreme_vsp",
|
||||
"extreme_wing",
|
||||
"f5_ltm",
|
||||
"f5_tmsh",
|
||||
"f5_linux",
|
||||
"fortinet",
|
||||
"generic_termserver",
|
||||
"hp_comware",
|
||||
"hp_procurve",
|
||||
"huawei",
|
||||
"huawei_vrpv8",
|
||||
"ipinfusion_ocnos",
|
||||
"juniper",
|
||||
"juniper_junos",
|
||||
"linux",
|
||||
"mellanox",
|
||||
"mrv_optiswitch",
|
||||
"netapp_cdot",
|
||||
"netscaler",
|
||||
"ovs_linux",
|
||||
"paloalto_panos",
|
||||
"pluribus",
|
||||
"quanta_mesh",
|
||||
"rad_etx",
|
||||
"ruckus_fastiron",
|
||||
"ubiquiti_edge",
|
||||
"ubiquiti_edgeswitch",
|
||||
"vyatta_vyos",
|
||||
"vyos",
|
||||
"oneaccess_oneos",
|
||||
)
|
||||
|
||||
SCRAPE_HELPERS = {
|
||||
"junos": "juniper",
|
||||
"ios": "cisco_ios",
|
||||
}
|
||||
|
||||
|
||||
class Supported:
|
||||
"""Define items supported by hyperglass.
|
||||
|
||||
query_types: Supported query types used to validate Flask input.
|
||||
|
||||
rest: Supported REST API platforms
|
||||
|
||||
scrape: Supported "scrape" platforms which will be accessed via
|
||||
Netmiko. List updated 07/2019.
|
||||
"""
|
||||
|
||||
query_parameters = ("query_location", "query_type", "query_target", "query_vrf")
|
||||
|
||||
query_types = ("bgp_route", "bgp_community", "bgp_aspath", "ping", "traceroute")
|
||||
|
||||
rest = ("frr", "bird")
|
||||
|
||||
scrape = (
|
||||
"a10",
|
||||
"accedian",
|
||||
"alcatel_aos",
|
||||
"alcatel_sros",
|
||||
"apresia_aeos",
|
||||
"arista_eos",
|
||||
"aruba_os",
|
||||
"avaya_ers",
|
||||
"avaya_vsp",
|
||||
"brocade_fastiron",
|
||||
"brocade_netiron",
|
||||
"brocade_nos",
|
||||
"brocade_vdx",
|
||||
"brocade_vyos",
|
||||
"checkpoint_gaia",
|
||||
"calix_b6",
|
||||
"ciena_saos",
|
||||
"cisco_asa",
|
||||
"cisco_ios",
|
||||
"cisco_ios_telnet",
|
||||
"cisco_nxos",
|
||||
"cisco_s300",
|
||||
"cisco_tp",
|
||||
"cisco_wlc",
|
||||
"cisco_xe",
|
||||
"cisco_xr",
|
||||
"coriant",
|
||||
"dell_dnos9",
|
||||
"dell_force10",
|
||||
"dell_os6",
|
||||
"dell_os9",
|
||||
"dell_os10",
|
||||
"dell_powerconnect",
|
||||
"dell_isilon",
|
||||
"eltex",
|
||||
"enterasys",
|
||||
"extreme",
|
||||
"extreme_ers",
|
||||
"extreme_exos",
|
||||
"extreme_netiron",
|
||||
"extreme_nos",
|
||||
"extreme_slx",
|
||||
"extreme_vdx",
|
||||
"extreme_vsp",
|
||||
"extreme_wing",
|
||||
"f5_ltm",
|
||||
"f5_tmsh",
|
||||
"f5_linux",
|
||||
"fortinet",
|
||||
"generic_termserver",
|
||||
"hp_comware",
|
||||
"hp_procurve",
|
||||
"huawei",
|
||||
"huawei_vrpv8",
|
||||
"ipinfusion_ocnos",
|
||||
"juniper",
|
||||
"juniper_junos",
|
||||
"linux",
|
||||
"mellanox",
|
||||
"mrv_optiswitch",
|
||||
"netapp_cdot",
|
||||
"netscaler",
|
||||
"ovs_linux",
|
||||
"paloalto_panos",
|
||||
"pluribus",
|
||||
"quanta_mesh",
|
||||
"rad_etx",
|
||||
"ruckus_fastiron",
|
||||
"ubiquiti_edge",
|
||||
"ubiquiti_edgeswitch",
|
||||
"vyatta_vyos",
|
||||
"vyos",
|
||||
"oneaccess_oneos",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def is_supported(nos):
|
||||
"""Verify if NOS is supported.
|
||||
|
||||
Arguments:
|
||||
nos {str} -- NOS short name
|
||||
|
||||
Returns:
|
||||
{bool} -- True if supported
|
||||
"""
|
||||
return bool(nos in Supported.rest + Supported.scrape)
|
||||
|
||||
@staticmethod
|
||||
def is_scrape(nos):
|
||||
"""Verify if NOS transport is scrape.
|
||||
|
||||
Arguments:
|
||||
nos {str} -- NOS short name
|
||||
|
||||
Returns:
|
||||
{bool} -- True if scrape
|
||||
"""
|
||||
return bool(nos in Supported.scrape)
|
||||
|
||||
@staticmethod
|
||||
def is_rest(nos):
|
||||
"""Verify if NOS transport is REST.
|
||||
|
||||
Arguments:
|
||||
nos {str} -- NOS short name
|
||||
|
||||
Returns:
|
||||
{bool} -- True if REST
|
||||
"""
|
||||
return bool(nos in Supported.rest)
|
||||
|
||||
@staticmethod
|
||||
def is_supported_query(query_type):
|
||||
"""Verify if query type is supported.
|
||||
|
||||
Arguments:
|
||||
query_type {str} -- query type
|
||||
|
||||
Returns:
|
||||
{bool} -- True if supported
|
||||
"""
|
||||
return bool(query_type in Supported.query_types)
|
||||
|
||||
@staticmethod
|
||||
def map_transport(nos):
|
||||
"""Map NOS to transport name.
|
||||
|
||||
Arguments:
|
||||
nos {str} -- NOS short name
|
||||
|
||||
Returns:
|
||||
{str} -- Transport name
|
||||
"""
|
||||
transport = None
|
||||
if nos in Supported.scrape:
|
||||
transport = "scrape"
|
||||
elif nos in Supported.rest:
|
||||
transport = "rest"
|
||||
return transport
|
||||
|
|
|
|||
|
|
@ -23,13 +23,13 @@ from netmiko import (
|
|||
|
||||
# Project
|
||||
from hyperglass.log import log
|
||||
from hyperglass.util import parse_exception
|
||||
from hyperglass.util import validate_nos, parse_exception
|
||||
from hyperglass.compat import _sshtunnel as sshtunnel
|
||||
from hyperglass.encode import jwt_decode, jwt_encode
|
||||
from hyperglass.constants import Supported
|
||||
from hyperglass.exceptions import (
|
||||
AuthError,
|
||||
RestError,
|
||||
ConfigError,
|
||||
ScrapeError,
|
||||
DeviceTimeout,
|
||||
ResponseEmpty,
|
||||
|
|
@ -413,20 +413,22 @@ class Execute:
|
|||
log.debug(f"Received query for {self.query_data}")
|
||||
log.debug(f"Matched device config: {device}")
|
||||
|
||||
supported, transport = validate_nos(device.nos)
|
||||
|
||||
connect = None
|
||||
output = params.messages.general
|
||||
|
||||
transport = Supported.map_transport(device.nos)
|
||||
connect = Connect(device, self.query_data, transport)
|
||||
|
||||
if Supported.is_rest(device.nos):
|
||||
if supported and transport == "rest":
|
||||
output = await connect.rest()
|
||||
|
||||
elif Supported.is_scrape(device.nos):
|
||||
elif supported and transport == "scrape":
|
||||
if device.proxy:
|
||||
output = await connect.scrape_proxied()
|
||||
else:
|
||||
output = await connect.scrape_direct()
|
||||
else:
|
||||
raise ConfigError('"{nos}" is not supported.', nos=device.nos)
|
||||
|
||||
if output == "" or output == "\n":
|
||||
raise ResponseEmpty(
|
||||
|
|
|
|||
|
|
@ -763,3 +763,18 @@ def make_repr(_class):
|
|||
yield f"{attr}={str(attr_val)}"
|
||||
|
||||
return f'{_class.__name__}({", ".join(_process_attrs(dir(_class)))})'
|
||||
|
||||
|
||||
def validate_nos(nos):
|
||||
"""Validate device NOS is supported."""
|
||||
from hyperglass.constants import TRANSPORT_REST
|
||||
from netmiko.ssh_dispatcher import CLASS_MAPPER_BASE
|
||||
|
||||
result = (False, None)
|
||||
|
||||
if nos in TRANSPORT_REST:
|
||||
result = (True, "rest")
|
||||
elif nos in CLASS_MAPPER_BASE.keys():
|
||||
result = (True, "scrape")
|
||||
|
||||
return result
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue