feat: add YANG path constants module

Python module with validated gNMI YANG paths for:
- Interfaces, Loopbacks, VLANs (OpenConfig)
- BGP with neighbor and AFI-SAFI helpers (OpenConfig)
- VXLAN VNI mappings (Arista experimental)
- MLAG and EVPN config (Arista experimental)
- Port-Channel/LAG paths
- Subscription helpers for fabric monitoring

Part of #3
This commit is contained in:
2025-12-26 13:42:31 +00:00
parent 4c7634da59
commit f335d1fc33

345
src/yang/paths.py Normal file
View File

@@ -0,0 +1,345 @@
"""
YANG Path Constants for Arista EOS 4.35.0F
This module provides validated gNMI YANG paths for managing
Arista EVPN-VXLAN fabrics via gNMI.
Usage:
from yang.paths import Interfaces, BGP, VXLAN
# Get interface state path
path = Interfaces.state("Ethernet1")
# Get all BGP neighbors
path = BGP.NEIGHBORS
"""
from dataclasses import dataclass
from typing import Optional
# =============================================================================
# Interfaces (OpenConfig)
# =============================================================================
class Interfaces:
"""OpenConfig interface paths."""
# Base paths
ROOT = "/interfaces/interface"
@staticmethod
def interface(name: str) -> str:
"""Get path for specific interface."""
return f"/interfaces/interface[name={name}]"
@staticmethod
def config(name: str) -> str:
"""Get interface config path."""
return f"/interfaces/interface[name={name}]/config"
@staticmethod
def state(name: str) -> str:
"""Get interface state path."""
return f"/interfaces/interface[name={name}]/state"
@staticmethod
def oper_status(name: str) -> str:
"""Get interface operational status path."""
return f"/interfaces/interface[name={name}]/state/oper-status"
@staticmethod
def admin_status(name: str) -> str:
"""Get interface admin status path."""
return f"/interfaces/interface[name={name}]/state/admin-status"
@staticmethod
def counters(name: str) -> str:
"""Get interface counters path."""
return f"/interfaces/interface[name={name}]/state/counters"
# =============================================================================
# Loopbacks (OpenConfig)
# =============================================================================
class Loopbacks:
"""Loopback interface paths."""
@staticmethod
def interface(index: int) -> str:
"""Get loopback interface path."""
return f"/interfaces/interface[name=Loopback{index}]"
@staticmethod
def config(index: int) -> str:
"""Get loopback config path."""
return f"/interfaces/interface[name=Loopback{index}]/config"
# Common loopbacks
LOOPBACK0 = "/interfaces/interface[name=Loopback0]"
LOOPBACK1 = "/interfaces/interface[name=Loopback1]" # VTEP source
# =============================================================================
# VLANs (OpenConfig)
# =============================================================================
class VLANs:
"""OpenConfig VLAN paths."""
# Base path
ROOT = "/network-instances/network-instance[name=default]/vlans/vlan"
@staticmethod
def vlan(vlan_id: int) -> str:
"""Get specific VLAN path."""
return f"/network-instances/network-instance[name=default]/vlans/vlan[vlan-id={vlan_id}]"
@staticmethod
def config(vlan_id: int) -> str:
"""Get VLAN config path."""
return f"/network-instances/network-instance[name=default]/vlans/vlan[vlan-id={vlan_id}]/config"
@staticmethod
def svi(vlan_id: int) -> str:
"""Get SVI interface path."""
return f"/interfaces/interface[name=Vlan{vlan_id}]"
# =============================================================================
# BGP (OpenConfig)
# =============================================================================
class BGP:
"""OpenConfig BGP paths."""
# Base path for BGP
_BASE = "/network-instances/network-instance[name=default]/protocols/protocol[identifier=BGP][name=BGP]/bgp"
# Global paths
GLOBAL = f"{_BASE}/global"
GLOBAL_CONFIG = f"{_BASE}/global/config"
GLOBAL_STATE = f"{_BASE}/global/state"
# Neighbors
NEIGHBORS = f"{_BASE}/neighbors/neighbor"
@staticmethod
def neighbor(address: str) -> str:
"""Get specific neighbor path."""
return f"{BGP._BASE}/neighbors/neighbor[neighbor-address={address}]"
@staticmethod
def neighbor_config(address: str) -> str:
"""Get neighbor config path."""
return f"{BGP._BASE}/neighbors/neighbor[neighbor-address={address}]/config"
@staticmethod
def neighbor_state(address: str) -> str:
"""Get neighbor state path."""
return f"{BGP._BASE}/neighbors/neighbor[neighbor-address={address}]/state"
@staticmethod
def neighbor_session_state(address: str) -> str:
"""Get neighbor session state path."""
return f"{BGP._BASE}/neighbors/neighbor[neighbor-address={address}]/state/session-state"
@staticmethod
def neighbor_afi_safi(address: str, afi_safi: str) -> str:
"""
Get neighbor AFI-SAFI path.
Args:
address: Neighbor IP address
afi_safi: AFI-SAFI name (e.g., 'IPV4_UNICAST', 'L2VPN_EVPN')
"""
return f"{BGP._BASE}/neighbors/neighbor[neighbor-address={address}]/afi-safis/afi-safi[afi-safi-name={afi_safi}]"
# AFI-SAFI constants
class AfiSafi:
"""BGP AFI-SAFI identifiers."""
IPV4_UNICAST = "IPV4_UNICAST"
IPV6_UNICAST = "IPV6_UNICAST"
L2VPN_EVPN = "L2VPN_EVPN"
# =============================================================================
# VXLAN (Arista Experimental)
# =============================================================================
class VXLAN:
"""
Arista VXLAN paths.
Model: arista-exp-eos-vxlan (augments openconfig-interfaces)
"""
# Base paths
INTERFACE = "/interfaces/interface[name=Vxlan1]"
ROOT = "/interfaces/interface[name=Vxlan1]/arista-vxlan"
CONFIG = "/interfaces/interface[name=Vxlan1]/arista-vxlan/config"
STATE = "/interfaces/interface[name=Vxlan1]/arista-vxlan/state"
# Specific config leaves
SOURCE_INTERFACE = "/interfaces/interface[name=Vxlan1]/arista-vxlan/config/src-ip-intf"
UDP_PORT = "/interfaces/interface[name=Vxlan1]/arista-vxlan/config/udp-port"
# VNI mappings
VLAN_TO_VNIS = "/interfaces/interface[name=Vxlan1]/arista-vxlan/vlan-to-vnis"
VRF_TO_VNIS = "/interfaces/interface[name=Vxlan1]/arista-vxlan/vrf-to-vnis"
@staticmethod
def vlan_to_vni(vlan_id: int) -> str:
"""Get specific VLAN-to-VNI mapping path."""
return f"/interfaces/interface[name=Vxlan1]/arista-vxlan/vlan-to-vnis/vlan-to-vni[vlan={vlan_id}]"
@staticmethod
def vrf_to_vni(vrf_name: str) -> str:
"""Get specific VRF-to-VNI mapping path."""
return f"/interfaces/interface[name=Vxlan1]/arista-vxlan/vrf-to-vnis/vrf-to-vni[vrf={vrf_name}]"
# =============================================================================
# MLAG (Arista Experimental)
# =============================================================================
class MLAG:
"""
Arista MLAG paths.
Model: arista-exp-eos-mlag
WARNING: Only config is exposed via gNMI.
State (peer status, role) requires eAPI.
"""
ROOT = "/arista/eos/mlag"
CONFIG = "/arista/eos/mlag/config"
# Config fields (for reference)
# - domain-id
# - local-intf
# - peer-address
# - peer-link-intf
# - dual-primary-action
# - dual-primary-detection-delay
# - heartbeat-peer-address
# =============================================================================
# EVPN (Arista Experimental)
# =============================================================================
class EVPN:
"""
Arista EVPN paths.
Model: arista-exp-eos-evpn
WARNING: Only config is exposed via gNMI.
Learned routes/MACs require eAPI.
"""
ROOT = "/arista/eos/evpn"
INSTANCES = "/arista/eos/evpn/evpn-instances"
@staticmethod
def instance(name: str) -> str:
"""Get specific EVPN instance path."""
return f"/arista/eos/evpn/evpn-instances/evpn-instance[name={name}]"
@staticmethod
def instance_config(name: str) -> str:
"""Get EVPN instance config path."""
return f"/arista/eos/evpn/evpn-instances/evpn-instance[name={name}]/config"
@staticmethod
def route_target(name: str) -> str:
"""Get EVPN instance route-target path."""
return f"/arista/eos/evpn/evpn-instances/evpn-instance[name={name}]/route-target/config"
# =============================================================================
# Port-Channel / LAG (OpenConfig)
# =============================================================================
class PortChannel:
"""OpenConfig LAG/Port-Channel paths."""
@staticmethod
def interface(channel_id: int) -> str:
"""Get port-channel interface path."""
return f"/interfaces/interface[name=Port-Channel{channel_id}]"
@staticmethod
def config(channel_id: int) -> str:
"""Get port-channel config path."""
return f"/interfaces/interface[name=Port-Channel{channel_id}]/config"
@staticmethod
def aggregation(channel_id: int) -> str:
"""Get LAG aggregation config path."""
return f"/interfaces/interface[name=Port-Channel{channel_id}]/aggregation/config"
@staticmethod
def aggregation_state(channel_id: int) -> str:
"""Get LAG aggregation state path."""
return f"/interfaces/interface[name=Port-Channel{channel_id}]/aggregation/state"
# =============================================================================
# System (OpenConfig)
# =============================================================================
class System:
"""OpenConfig system paths."""
ROOT = "/system"
CONFIG = "/system/config"
STATE = "/system/state"
HOSTNAME = "/system/config/hostname"
# =============================================================================
# Subscription Helpers
# =============================================================================
@dataclass
class SubscriptionPath:
"""Helper for building subscription paths."""
path: str
mode: str = "on-change" # on-change, sample, target-defined
def __str__(self) -> str:
return self.path
# Common subscription paths for fabric monitoring
class FabricSubscriptions:
"""Pre-defined subscription paths for fabric monitoring."""
# Interface state changes
INTERFACE_STATE = SubscriptionPath("/interfaces/interface/state")
# BGP session state changes
BGP_SESSIONS = SubscriptionPath(
"/network-instances/network-instance[name=default]/protocols/protocol[identifier=BGP][name=BGP]/bgp/neighbors/neighbor/state/session-state"
)
# VXLAN VNI changes
VXLAN_VNIS = SubscriptionPath(
"/interfaces/interface[name=Vxlan1]/arista-vxlan/vlan-to-vnis"
)
@classmethod
def all(cls) -> list[SubscriptionPath]:
"""Get all fabric subscription paths."""
return [
cls.INTERFACE_STATE,
cls.BGP_SESSIONS,
cls.VXLAN_VNIS,
]