The validator infrastructure is complete. This plan covers migrating configuration sections to use schema validators instead of redundant known dict entries.
validator.py- 733 lines, 18 validators, all 23 ValueTypes mappedLeaf.get_validator()andLeafList.get_validator()- auto-generates validatorsSection.parse()- uses schema validators as fallbackschema_to_json_schema()- JSON Schema export workingtests/unit/test_configuration_validator.py- 611 lines, 13 test classes- CLI command
exabgp schema export- working
11 files with 117 entries where known dict entries can be removed (schema validators handle them):
| File | Entries | Priority | Complexity |
|---|---|---|---|
process/__init__.py |
3 | Phase 1 | Easy |
announce/label.py |
1 | Phase 1 | Easy |
announce/vpn.py |
1 | Phase 1 | Easy |
neighbor/api.py |
9 | Phase 1 | Easy (all BOOLEAN) |
capability.py |
10 | Phase 2 | Medium |
operational/__init__.py |
8 | Phase 2 | Medium |
neighbor/family.py |
5 | Phase 2 | Medium (dynamic init) |
neighbor/nexthop.py |
2 | Phase 2 | Medium (dynamic init) |
announce/ip.py |
18 | Phase 3 | Complex |
l2vpn/vpls.py |
21 | Phase 3 | Complex |
neighbor/__init__.py |
25 | Phase 3 | Complex |
class ParseProcess(Section):
schema = Container(children={
'encoder': Leaf(type=ValueType.ENUMERATION, choices=['text', 'json'], ...),
'respawn': Leaf(type=ValueType.BOOLEAN, ...),
'run': Leaf(type=ValueType.STRING, ...),
})
known = {
'encoder': encoder, # redundant - schema can handle
'respawn': boolean, # redundant - schema can handle
'run': run, # redundant - schema can handle
}
action = {
'encoder': 'set-command',
'respawn': 'set-command',
'run': 'set-command',
}class ParseProcess(Section):
schema = Container(children={
'encoder': Leaf(type=ValueType.ENUMERATION, choices=['text', 'json'], action='set-command', ...),
'respawn': Leaf(type=ValueType.BOOLEAN, action='set-command', ...),
'run': Leaf(type=ValueType.STRING, action='set-command', ...),
})
known = {} # Empty - all handled by schema
action = {} # Empty - derived from schemaCommit 1: process/init.py
- Remove: encoder, respawn, run from
known - Types: ENUMERATION, BOOLEAN, STRING
Commit 2: announce/label.py + announce/vpn.py
- Remove: label, rd from
known - Types: LABEL, RD
Commit 3: neighbor/api.py
- Remove: parsed, packets, consolidate, open, update, notification, keepalive, refresh, operational from
known - Types: All BOOLEAN
Commit 4: capability.py
- Remove: nexthop, asn4, multi-session, operational, route-refresh, aigp, extended-message, software-version, add-path from
known - Keep: graceful-restart (complex validation: max 4095, "disable" keyword)
- Types: BOOLEAN, ENUMERATION
Commit 5: operational/init.py
- Remove: asm, adm, rpcq, rpcp, apcq, apcp, lpcq, lpcp from
known - Types: All STRING with append-name action
Commit 6: neighbor/family.py + neighbor/nexthop.py
- Handle dynamic
knownconstruction in__init__ - Types: ENUMERATION, STRING
Commit 7: announce/ip.py
- Remove: origin, med, as-path, local-preference, atomic-aggregate, aggregator, etc. from
known - Keep entries with complex multi-token parsing
- Types: Various BGP attributes
Commit 8: l2vpn/vpls.py
- Remove: rd, endpoint, base, offset, size, origin, med, etc. from
known - Types: RD, INTEGER, BGP attributes
Commit 9: neighbor/init.py
- Remove: hold-time, rate-limit, listen, connect, passive, description, etc. from
known - Types: INTEGER, PORT, BOOLEAN, STRING, IP_ADDRESS, ASN
| Phase | File | Action |
|---|---|---|
| 1 | src/exabgp/configuration/process/__init__.py |
Remove 3 known entries |
| 1 | src/exabgp/configuration/announce/label.py |
Remove 1 known entry |
| 1 | src/exabgp/configuration/announce/vpn.py |
Remove 1 known entry |
| 1 | src/exabgp/configuration/neighbor/api.py |
Remove 9 known entries |
| 2 | src/exabgp/configuration/capability.py |
Remove 9 known entries |
| 2 | src/exabgp/configuration/operational/__init__.py |
Remove 8 known entries |
| 2 | src/exabgp/configuration/neighbor/family.py |
Refactor dynamic known |
| 2 | src/exabgp/configuration/neighbor/nexthop.py |
Refactor dynamic known |
| 3 | src/exabgp/configuration/announce/ip.py |
Remove 18 known entries |
| 3 | src/exabgp/configuration/l2vpn/vpls.py |
Remove 21 known entries |
| 3 | src/exabgp/configuration/neighbor/__init__.py |
Remove 25 known entries |
./qa/bin/test_everything # Must pass after each commitknown entries to remove:
- 'encoder': encoder
- 'respawn': boolean
- 'run': run
Ensure schema has:
- encoder: Leaf(type=ValueType.ENUMERATION, choices=['text', 'json'], action='set-command')
- respawn: Leaf(type=ValueType.BOOLEAN, action='set-command')
- run: Leaf(type=ValueType.STRING, action='set-command')
known entries to remove:
- 'label': label
Ensure schema has:
- label: Leaf(type=ValueType.LABEL, action='nlri-set')
known entries to remove:
- 'rd': route_distinguisher
Ensure schema has:
- rd: Leaf(type=ValueType.RD, action='nlri-set')
In ParseSend and ParseReceive classes:
known entries to remove:
- 'parsed': boolean
- 'packets': boolean
- 'consolidate': boolean
- 'open': boolean
- 'update': boolean
- 'notification': boolean
- 'keepalive': boolean
- 'refresh': boolean
- 'operational': boolean
All should be Leaf(type=ValueType.BOOLEAN, action='set-command')
known entries to remove:
- 'nexthop': boolean
- 'asn4': boolean
- 'multi-session': boolean
- 'operational': boolean
- 'route-refresh': boolean
- 'aigp': boolean
- 'extended-message': boolean
- 'software-version': boolean
- 'add-path': add_path (ENUMERATION)
KEEP in known (complex validation):
- 'graceful-restart': graceful_restart (special "disable" handling, max 4095)
known entries to remove:
- 'asm': asm
- 'adm': adm
- 'rpcq': rpcq
- 'rpcp': rpcp
- 'apcq': apcq
- 'apcp': apcp
- 'lpcq': lpcq
- 'lpcp': lpcp
All should be Leaf(type=ValueType.STRING, action='append-name')
Dynamic known construction - refactor to use schema:
- 'ipv4': ENUMERATION with SAFI choices
- 'ipv6': ENUMERATION with SAFI choices
- 'l2vpn': ENUMERATION with SAFI choices
- 'bgp-ls': ENUMERATION with SAFI choices
- 'all': BOOLEAN
Dynamic known construction - refactor to use schema:
- 'ipv4': STRING (SAFI with alternate next-hop)
- 'ipv6': STRING (SAFI with alternate next-hop)
known entries to remove:
- 'origin': origin
- 'med': med
- 'local-preference': local_preference
- 'atomic-aggregate': atomic_aggregate
- 'aggregator': aggregator
- 'originator-id': originator_id
- 'cluster-list': cluster_list
- 'community': community
- 'large-community': large_community
- 'extended-community': extended_community
- 'aigp': aigp
- 'name': named
- 'split': split
- 'watchdog': watchdog
- 'withdraw': withdraw
- 'label': label
- 'attribute': attribute
- 'as-path': as_path (may need to keep - complex multi-token)
known entries to remove:
- 'rd': route_distinguisher
- 'endpoint': integer
- 'base': integer
- 'offset': integer
- 'size': integer
- Plus all BGP attribute entries inherited from announce
known entries to remove:
- 'hold-time': ttl
- 'rate-limit': rate_limit
- 'incoming-ttl': incoming_ttl
- 'outgoing-ttl': outgoing_ttl
- 'passive': boolean
- 'listen': port
- 'connect': port
- 'auto-flush': boolean
- 'group-updates': boolean
- 'adj-rib-in': boolean
- 'adj-rib-out': boolean
- 'manual-eor': boolean
- 'md5-base64': boolean
- 'description': description
- 'host-name': host_name
- 'domain-name': domain_name
- 'source-interface': source_interface
- 'router-id': ip
- 'local-as': auto_asn
- 'peer-as': auto_asn
- 'local-address': ip
- 'peer-address': peer_ip
Type mappings:
- INTEGER: hold-time, rate-limit, incoming-ttl, outgoing-ttl
- BOOLEAN: passive, auto-flush, group-updates, adj-rib-in, adj-rib-out, manual-eor, md5-base64
- PORT: listen, connect
- STRING: description, host-name, domain-name, source-interface
- IP_ADDRESS: router-id, local-address
- ASN: local-as, peer-as (with auto)
- IP_RANGE: peer-address
- All migrated entries removed from
knowndict - Schema provides validation (via
_validator_from_schema()) - Action derived from schema (via
_action_from_schema()) - All existing tests pass
- Existing configuration files work unchanged
- Code is cleaner with less duplication
- Keep complex parsers: Entries like
graceful-restart,as-pathwith multi-token parsing stay inknown - Dynamic known: Some sections build
knownin__init__- may need refactoring - Actions: Ensure schema Leaf has correct
actionfield before removing fromactiondict - Test after each commit: Run
./qa/bin/test_everythingto validate
Phase 1:
process/__init__.py: encoder, respawn migrated (keptrun- returns list[str])neighbor/api.py: All 9 boolean entries migrated
Phase 2 (Partial):
capability.py: 8 boolean entries migrated (keptadd-pathandgraceful-restart)schema.py: FixedLeaf.get_validator()to configure BooleanValidator defaults
Phase 3:
neighbor/__init__.py: 16 entries migrated to schema validators:- description, host-name, domain-name, source-interface, md5-password
- passive, listen, connect, group-updates, auto-flush
- adj-rib-out, adj-rib-in, manual-eor
- peer-address, md5-ip, rate-limit
After implementation analysis, several sections in the plan cannot be migrated because:
1. Custom parsing functions bypass Section.parse():
| File | Reason |
|---|---|
announce/ip.py |
Custom ip() function directly accesses known[command] |
l2vpn/vpls.py |
Custom vpls() function in __init__.py directly accesses known[command] |
2. Parsers return complex objects that schema validators cannot produce:
| File | Reason |
|---|---|
operational/__init__.py |
Parsers return Advisory/Query/Response objects, NOT strings |
neighbor/family.py |
Parsers return (AFI, SAFI) tuples with state tracking (_all, _seen) |
neighbor/nexthop.py |
Parsers return (AFI, SAFI, AFI) tuples |
capability.py add-path |
Returns int (0,1,2,3), but ENUMERATION validator returns strings |
process/__init__.py run |
Returns list[str] and validates file existence |
3. Parsers return optional types:
| Entry | Reason |
|---|---|
neighbor/__init__.py hold-time |
Returns HoldTime object, not int |
neighbor/__init__.py router-id |
Returns RouterID object, not IP |
neighbor/__init__.py local-address |
Returns `IP |
neighbor/__init__.py local-as |
Returns `ASN |
neighbor/__init__.py peer-as |
Returns `ASN |
neighbor/__init__.py outgoing-ttl |
Returns `int |
neighbor/__init__.py incoming-ttl |
Returns `int |
neighbor/__init__.py md5-base64 |
Returns `bool |
neighbor/__init__.py inherit |
Returns list[str], not str |
Only migrate entries where:
- Parser returns same type as schema validator (bool for BOOLEAN, str for STRING/ENUMERATION, int for INTEGER)
- No complex object creation (AFI, SAFI, Advisory, etc.)
- No state tracking or side effects
- Section uses
Section.parse()(not custom parsing functions)
Created: 2025-12-02 Updated: 2025-12-02 Status: Phase 1, 2, 3 complete. Migration scope reduced from 117 to ~40 entries due to architectural constraints.