testprotocols 0.6.0__tar.gz → 0.7.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {testprotocols-0.6.0/src/testprotocols.egg-info → testprotocols-0.7.1}/PKG-INFO +1 -1
- {testprotocols-0.6.0 → testprotocols-0.7.1}/pyproject.toml +1 -1
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/__init__.py +1 -1
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/first_hop_security.py +1 -3
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/iperf_client.py +0 -5
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/models/dhcp.py +1 -1
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/models/sdwan_appliance.py +11 -11
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/models/switch.py +3 -3
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/models/switch_routing.py +7 -7
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/sdwan_policy_manager.py +3 -1
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/site_to_site_vpn.py +32 -3
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/switch_acl.py +1 -3
- {testprotocols-0.6.0 → testprotocols-0.7.1/src/testprotocols.egg-info}/PKG-INFO +1 -1
- testprotocols-0.7.1/tests/test_appliance_vlans.py +52 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/tests/test_cpe_templates.py +2 -1
- {testprotocols-0.6.0 → testprotocols-0.7.1}/tests/test_device_types.py +81 -46
- {testprotocols-0.6.0 → testprotocols-0.7.1}/tests/test_dhcp_templates.py +3 -2
- {testprotocols-0.6.0 → testprotocols-0.7.1}/tests/test_firewall_templates.py +5 -4
- {testprotocols-0.6.0 → testprotocols-0.7.1}/tests/test_infra_templates.py +2 -1
- {testprotocols-0.6.0 → testprotocols-0.7.1}/tests/test_ip_interface.py +2 -1
- {testprotocols-0.6.0 → testprotocols-0.7.1}/tests/test_ip_routing.py +2 -1
- {testprotocols-0.6.0 → testprotocols-0.7.1}/tests/test_network_tool_templates.py +2 -1
- {testprotocols-0.6.0 → testprotocols-0.7.1}/tests/test_radius_templates.py +2 -1
- {testprotocols-0.6.0 → testprotocols-0.7.1}/tests/test_sdwan_appliance_models.py +16 -13
- {testprotocols-0.6.0 → testprotocols-0.7.1}/tests/test_sdwan_appliance_templates.py +4 -1
- {testprotocols-0.6.0 → testprotocols-0.7.1}/tests/test_switch_models.py +19 -18
- {testprotocols-0.6.0 → testprotocols-0.7.1}/tests/test_switch_routing_models.py +12 -7
- testprotocols-0.7.1/tests/test_switch_templates.py +95 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/tests/test_traffic_templates.py +2 -1
- {testprotocols-0.6.0 → testprotocols-0.7.1}/tests/test_voice_wifi_templates.py +2 -1
- {testprotocols-0.6.0 → testprotocols-0.7.1}/tests/test_wan_edge_templates.py +4 -3
- {testprotocols-0.6.0 → testprotocols-0.7.1}/tests/test_wifi_templates.py +6 -5
- testprotocols-0.6.0/tests/test_appliance_vlans.py +0 -33
- testprotocols-0.6.0/tests/test_switch_templates.py +0 -69
- {testprotocols-0.6.0 → testprotocols-0.7.1}/LICENSE +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/NOTICE +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/README.md +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/setup.cfg +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/aftr_gateway.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/appliance_nat.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/appliance_uplinks.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/appliance_vlans.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/arp_client.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/bgp.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/conntrack.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/content_filtering.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/device_lifecycle.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/device_management.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/devices/__init__.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/devices/base.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/devices/client.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/devices/cpe.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/devices/infra.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/devices/sdwan.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/devices/switch.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/devices/traffic.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/devices/voice.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/devices/wan.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/dhcp_client.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/dhcp_server.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/discovery.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/dns_client.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/file_transfer.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/firewall.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/firewall_zones.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/gateway_redundancy.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/http_client.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/http_server.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/hw_console.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/interface_dhcp.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/ip_interface.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/ip_routing.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/iperf_generator.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/iperf_server.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/l3_firewall.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/l7_firewall.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/link_aggregation.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/mac_table.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/models/__init__.py +1 -1
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/models/firewall.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/models/impairment.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/models/l2_common.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/models/multicast.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/models/networking.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/models/packets.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/models/qoe.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/models/radius.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/models/tr069.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/models/traffic.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/models/wan_edge.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/models/wifi.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/multicast_client.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/nat.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/netem_controller.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/network_endpoint.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/network_probe.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/nmap_scanner.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/ntp_client.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/ntp_config.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/ospf.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/packet_filter.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/pcap_capture.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/pdu_controller.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/port_poe.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/port_security.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/port_status.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/py.typed +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/qoe_browser.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/radius_client.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/radius_server.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/reachability_responder.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/routed_interfaces.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/router.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/routing_read.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/sip_phone.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/sip_server.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/snmp_client.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/spanning_tree.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/static_routes.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/storm_control.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/streaming_server.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/switch_ports.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/switch_qos.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/switch_vlans.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/syslog_config.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/tftp_server.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/threat_prevention.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/tr069_client.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/tr069_server.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/traffic_shaping.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/upnp_client.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/vlan_client.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/wan_link_admin.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/wifi_bss.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/wifi_client.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/wifi_mesh.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/wifi_onboarding.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/wifi_radio.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/wifi_rf.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/wifi_stations.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols/wifi_transitions.py +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols.egg-info/SOURCES.txt +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols.egg-info/dependency_links.txt +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/src/testprotocols.egg-info/top_level.txt +0 -0
- {testprotocols-0.6.0 → testprotocols-0.7.1}/tests/test_package_imports.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: testprotocols
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Common Test Resource Layer — capability and device protocols for telco testing.
|
|
5
5
|
Author-email: Alottabits <rjvisser@alottabits.com>
|
|
6
6
|
Maintainer-email: Alottabits <rjvisser@alottabits.com>
|
|
@@ -18,9 +18,7 @@ from testprotocols.models.switch import BindingSource, FhsBinding, FhsScope, Fhs
|
|
|
18
18
|
class FirstHopSecurity(Protocol):
|
|
19
19
|
"""Abstract contract for DHCP snooping + Dynamic ARP Inspection."""
|
|
20
20
|
|
|
21
|
-
def set_dhcp_snooping(
|
|
22
|
-
self, scope: FhsScope, enabled: bool, vlan: int | None = None
|
|
23
|
-
) -> None:
|
|
21
|
+
def set_dhcp_snooping(self, scope: FhsScope, enabled: bool, vlan: int | None = None) -> None:
|
|
24
22
|
"""Enable/disable DHCP snooping globally or per-VLAN. A switch-wide-only
|
|
25
23
|
product maps ``PER_VLAN`` to ``GLOBAL`` or raises unsupported-capability."""
|
|
26
24
|
...
|
|
@@ -19,7 +19,6 @@ class IperfClient(Protocol):
|
|
|
19
19
|
traffic_port: int,
|
|
20
20
|
bandwidth: int | None = None,
|
|
21
21
|
bind_to_ip: str | None = None,
|
|
22
|
-
direction: str | None = None,
|
|
23
22
|
ip_version: int | None = None,
|
|
24
23
|
udp_protocol: bool = False,
|
|
25
24
|
time: int = 10,
|
|
@@ -51,10 +50,6 @@ class IperfClient(Protocol):
|
|
|
51
50
|
``tcp_rmem``/``tcp_wmem``), so the host must be provisioned
|
|
52
51
|
accordingly.
|
|
53
52
|
|
|
54
|
-
``direction`` is DEPRECATED: a raw CLI fragment appended verbatim
|
|
55
|
-
after the typed flags. Kept as an escape hatch only; use the typed
|
|
56
|
-
parameters instead.
|
|
57
|
-
|
|
58
53
|
Returns a tuple of (pid, log_file_path).
|
|
59
54
|
"""
|
|
60
55
|
...
|
|
@@ -41,7 +41,7 @@ class DhcpLeaseObservation:
|
|
|
41
41
|
server: str = ""
|
|
42
42
|
"""The DHCP server identifier, option 54 (dotted quad; empty if absent)."""
|
|
43
43
|
|
|
44
|
-
options: Mapping[int, str] = field(default_factory=dict)
|
|
44
|
+
options: Mapping[int, str] = field(default_factory=dict[int, str])
|
|
45
45
|
"""Raw received value per option code, as reported by the client."""
|
|
46
46
|
|
|
47
47
|
|
|
@@ -227,7 +227,7 @@ class OneToOneNatRule:
|
|
|
227
227
|
public_ip: str
|
|
228
228
|
lan_ip: str
|
|
229
229
|
uplink: str = "any"
|
|
230
|
-
allowed_inbound: list[NatInboundAllow] = field(default_factory=list)
|
|
230
|
+
allowed_inbound: list[NatInboundAllow] = field(default_factory=list[NatInboundAllow])
|
|
231
231
|
|
|
232
232
|
|
|
233
233
|
@dataclass
|
|
@@ -236,7 +236,7 @@ class OneToManyNatRule:
|
|
|
236
236
|
|
|
237
237
|
public_ip: str
|
|
238
238
|
uplink: str = "any"
|
|
239
|
-
port_forwards: list[PortForwardRule] = field(default_factory=list)
|
|
239
|
+
port_forwards: list[PortForwardRule] = field(default_factory=list[PortForwardRule])
|
|
240
240
|
|
|
241
241
|
|
|
242
242
|
# --- WAN uplinks ---
|
|
@@ -287,7 +287,7 @@ class SyslogServer:
|
|
|
287
287
|
|
|
288
288
|
host: str
|
|
289
289
|
port: int = 514
|
|
290
|
-
roles: list[SyslogRole] = field(default_factory=list)
|
|
290
|
+
roles: list[SyslogRole] = field(default_factory=list[SyslogRole])
|
|
291
291
|
|
|
292
292
|
|
|
293
293
|
# --- Threat prevention (IDS / IPS + malware) ---
|
|
@@ -423,10 +423,10 @@ class VlanConfig:
|
|
|
423
423
|
appliance_ip: str
|
|
424
424
|
dhcp_mode: DhcpMode = DhcpMode.SERVER
|
|
425
425
|
dhcp_lease_seconds: int = 86400
|
|
426
|
-
dns_servers: list[str] = field(default_factory=list)
|
|
427
|
-
dhcp_options: list[DhcpOption] = field(default_factory=list)
|
|
428
|
-
reservations: list[DhcpReservation] = field(default_factory=list)
|
|
429
|
-
reserved_ranges: list[tuple[str, str]] = field(default_factory=list)
|
|
426
|
+
dns_servers: list[str] = field(default_factory=list[str])
|
|
427
|
+
dhcp_options: list[DhcpOption] = field(default_factory=list[DhcpOption])
|
|
428
|
+
reservations: list[DhcpReservation] = field(default_factory=list[DhcpReservation])
|
|
429
|
+
reserved_ranges: list[tuple[str, str]] = field(default_factory=list[tuple[str, str]])
|
|
430
430
|
|
|
431
431
|
|
|
432
432
|
@dataclass
|
|
@@ -489,8 +489,8 @@ class SiteToSiteVpnConfig:
|
|
|
489
489
|
"""
|
|
490
490
|
|
|
491
491
|
role: VpnRole
|
|
492
|
-
hubs: list[VpnHub] = field(default_factory=list)
|
|
493
|
-
subnets: list[VpnSubnet] = field(default_factory=list)
|
|
492
|
+
hubs: list[VpnHub] = field(default_factory=list[VpnHub])
|
|
493
|
+
subnets: list[VpnSubnet] = field(default_factory=list[VpnSubnet])
|
|
494
494
|
|
|
495
495
|
|
|
496
496
|
@dataclass
|
|
@@ -619,8 +619,8 @@ class BgpConfig:
|
|
|
619
619
|
|
|
620
620
|
enabled: bool
|
|
621
621
|
as_number: int
|
|
622
|
-
neighbors: list[BgpNeighbor] = field(default_factory=list)
|
|
623
|
-
advertised_networks: list[str] = field(default_factory=list)
|
|
622
|
+
neighbors: list[BgpNeighbor] = field(default_factory=list[BgpNeighbor])
|
|
623
|
+
advertised_networks: list[str] = field(default_factory=list[str])
|
|
624
624
|
|
|
625
625
|
|
|
626
626
|
@dataclass
|
|
@@ -145,7 +145,7 @@ class SwitchPort:
|
|
|
145
145
|
mode: PortMode
|
|
146
146
|
enabled: bool = True
|
|
147
147
|
native_vlan: int | None = None
|
|
148
|
-
allowed_vlans: list[int] = field(default_factory=list)
|
|
148
|
+
allowed_vlans: list[int] = field(default_factory=list[int])
|
|
149
149
|
description: str = ""
|
|
150
150
|
voice_vlan: int | None = None
|
|
151
151
|
isolated: bool = False
|
|
@@ -195,7 +195,7 @@ class AccessPolicy:
|
|
|
195
195
|
|
|
196
196
|
port: str
|
|
197
197
|
policy_type: AccessPolicyType
|
|
198
|
-
allowed_macs: list[str] = field(default_factory=list)
|
|
198
|
+
allowed_macs: list[str] = field(default_factory=list[str])
|
|
199
199
|
max_macs: int | None = None
|
|
200
200
|
sticky: bool = False
|
|
201
201
|
|
|
@@ -209,7 +209,7 @@ class StormControlConfig:
|
|
|
209
209
|
"""
|
|
210
210
|
|
|
211
211
|
port: str
|
|
212
|
-
thresholds: dict[StormControlType, float] = field(default_factory=dict)
|
|
212
|
+
thresholds: dict[StormControlType, float] = field(default_factory=dict[StormControlType, float])
|
|
213
213
|
|
|
214
214
|
|
|
215
215
|
@dataclass
|
|
@@ -81,11 +81,11 @@ class InterfaceDhcpConfig:
|
|
|
81
81
|
interface: str
|
|
82
82
|
mode: DhcpMode = DhcpMode.DISABLED
|
|
83
83
|
lease_seconds: int = 86400
|
|
84
|
-
dns_servers: list[str] = field(default_factory=list)
|
|
85
|
-
options: list[DhcpOption] = field(default_factory=list)
|
|
86
|
-
reservations: list[DhcpReservation] = field(default_factory=list)
|
|
87
|
-
reserved_ranges: list[tuple[str, str]] = field(default_factory=list)
|
|
88
|
-
relay_targets: list[str] = field(default_factory=list)
|
|
84
|
+
dns_servers: list[str] = field(default_factory=list[str])
|
|
85
|
+
options: list[DhcpOption] = field(default_factory=list[DhcpOption])
|
|
86
|
+
reservations: list[DhcpReservation] = field(default_factory=list[DhcpReservation])
|
|
87
|
+
reserved_ranges: list[tuple[str, str]] = field(default_factory=list[tuple[str, str]])
|
|
88
|
+
relay_targets: list[str] = field(default_factory=list[str])
|
|
89
89
|
|
|
90
90
|
|
|
91
91
|
@dataclass
|
|
@@ -105,8 +105,8 @@ class OspfConfig:
|
|
|
105
105
|
enabled: bool
|
|
106
106
|
router_id: str
|
|
107
107
|
version: OspfVersion = OspfVersion.V2
|
|
108
|
-
areas: list[str] = field(default_factory=list)
|
|
109
|
-
interfaces: list[OspfInterfaceSettings] = field(default_factory=list)
|
|
108
|
+
areas: list[str] = field(default_factory=list[str])
|
|
109
|
+
interfaces: list[OspfInterfaceSettings] = field(default_factory=list[OspfInterfaceSettings])
|
|
110
110
|
|
|
111
111
|
|
|
112
112
|
@dataclass
|
|
@@ -5,7 +5,9 @@ policy application, SLA policies, and application-flow visibility.
|
|
|
5
5
|
|
|
6
6
|
Firewall-rule administration is **not** here — it moved to the dedicated
|
|
7
7
|
``l3_firewall`` / ``l7_firewall`` capabilities (coherent-domain split; see
|
|
8
|
-
SPLITS.md).
|
|
8
|
+
SPLITS.md). Also not here: moving flows between the overlay and internet
|
|
9
|
+
domains (overlay membership carve-outs) — see ``site_to_site_vpn``. Typed
|
|
10
|
+
path steering (``set_uplink_selection`` /
|
|
9
11
|
``get_uplink_selection`` over ordered ``UplinkSelectionRule``s; performance
|
|
10
12
|
classes reuse ``SLAPolicy`` by name) landed 2026-06-12; the default
|
|
11
13
|
(primary) uplink pair (``get_default_uplink`` / ``set_default_uplink``)
|
|
@@ -21,8 +21,12 @@ hub anywhere → ``HUB``; overlay enabled with hub list → ``SPOKE``) and on
|
|
|
21
21
|
write registers/dereferences the device in the relevant site configs. The
|
|
22
22
|
round-trip is intent-preserving even where the stored shape differs.
|
|
23
23
|
|
|
24
|
-
In scope: overlay participation (role, hubs + default route, subnets)
|
|
25
|
-
|
|
24
|
+
In scope: overlay participation (role, hubs + default route, subnets),
|
|
25
|
+
overlay exclusions (flow-level membership carve-outs — local breakout), and
|
|
26
|
+
peer status. Overlay *membership* — which flows are in the overlay at all
|
|
27
|
+
(role, hubs + default route, subnets, exclusions) — lives here; path
|
|
28
|
+
selection *within* a domain (e.g. which overlay uplink, or which internet
|
|
29
|
+
uplink) lives in ``sdwan_policy_manager``.
|
|
26
30
|
|
|
27
31
|
Out of scope: VPN-scoped firewall rules (see ``l3_firewall``), IPsec crypto
|
|
28
32
|
parameters (no driving test; highly vendor-divergent — add on evidence),
|
|
@@ -34,7 +38,11 @@ from __future__ import annotations
|
|
|
34
38
|
|
|
35
39
|
from typing import Protocol, runtime_checkable
|
|
36
40
|
|
|
37
|
-
from testprotocols.models.sdwan_appliance import
|
|
41
|
+
from testprotocols.models.sdwan_appliance import (
|
|
42
|
+
FlowMatch,
|
|
43
|
+
SiteToSiteVpnConfig,
|
|
44
|
+
VpnPeerStatus,
|
|
45
|
+
)
|
|
38
46
|
|
|
39
47
|
|
|
40
48
|
@runtime_checkable
|
|
@@ -59,3 +67,24 @@ class SiteToSiteVpn(Protocol):
|
|
|
59
67
|
Empty list when the device participates in no overlay.
|
|
60
68
|
"""
|
|
61
69
|
...
|
|
70
|
+
|
|
71
|
+
def get_vpn_exclusions(self) -> list[FlowMatch]:
|
|
72
|
+
"""Return the flow matches excluded from the overlay (local breakout).
|
|
73
|
+
|
|
74
|
+
Flows matching an entry bypass the site-to-site overlay and egress
|
|
75
|
+
the local uplink, even when an overlay route — including an overlay
|
|
76
|
+
default learned from a routing protocol — covers the destination.
|
|
77
|
+
Empty list when every flow is eligible for overlay routing.
|
|
78
|
+
"""
|
|
79
|
+
...
|
|
80
|
+
|
|
81
|
+
def set_vpn_exclusions(self, rules: list[FlowMatch]) -> None:
|
|
82
|
+
"""Replace the overlay-exclusion rule set with *rules*.
|
|
83
|
+
|
|
84
|
+
Whole-replace; read-modify-write is the caller's responsibility.
|
|
85
|
+
Products that cannot express a constrained field (e.g. source-side
|
|
86
|
+
matches) raise rather than approximate. Vendor rule kinds outside
|
|
87
|
+
this contract (e.g. application-based exclusions) are preserved
|
|
88
|
+
unchanged by the driver.
|
|
89
|
+
"""
|
|
90
|
+
...
|
|
@@ -17,9 +17,7 @@ from testprotocols.models.switch import AclDirection, SwitchAclRule
|
|
|
17
17
|
class SwitchAcl(Protocol):
|
|
18
18
|
"""Abstract contract for the switch ACL."""
|
|
19
19
|
|
|
20
|
-
def set_acl(
|
|
21
|
-
self, binding: str, direction: AclDirection, rules: list[SwitchAclRule]
|
|
22
|
-
) -> None:
|
|
20
|
+
def set_acl(self, binding: str, direction: AclDirection, rules: list[SwitchAclRule]) -> None:
|
|
23
21
|
"""Replace the ordered ACL bound to *binding* (a port or ``vlan:<id>``)
|
|
24
22
|
in *direction*."""
|
|
25
23
|
...
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: testprotocols
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Common Test Resource Layer — capability and device protocols for telco testing.
|
|
5
5
|
Author-email: Alottabits <rjvisser@alottabits.com>
|
|
6
6
|
Maintainer-email: Alottabits <rjvisser@alottabits.com>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Protocol-conformance tests for ApplianceVlans (runtime_checkable structural check)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from testprotocols.appliance_vlans import ApplianceVlans
|
|
6
|
+
from testprotocols.models.sdwan_appliance import DhcpLease, VlanConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class _Complete:
|
|
10
|
+
"""Has every ApplianceVlans member, including delete_vlan.
|
|
11
|
+
|
|
12
|
+
Signatures mirror the protocol for documentation only — ``runtime_checkable``
|
|
13
|
+
``isinstance`` checks member presence, not shape, so these bodies are never
|
|
14
|
+
reached.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def list_vlans(self) -> list[VlanConfig]:
|
|
18
|
+
raise NotImplementedError
|
|
19
|
+
|
|
20
|
+
def get_vlan(self, vlan_id: int) -> VlanConfig:
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
|
|
23
|
+
def set_vlan(self, config: VlanConfig) -> None: ...
|
|
24
|
+
|
|
25
|
+
def delete_vlan(self, vlan_id: int) -> None: ...
|
|
26
|
+
|
|
27
|
+
def get_dhcp_leases(self, vlan_id: int | None = None) -> list[DhcpLease]:
|
|
28
|
+
raise NotImplementedError
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class _NoDelete:
|
|
32
|
+
"""Missing delete_vlan — must NOT satisfy the protocol once delete_vlan is declared."""
|
|
33
|
+
|
|
34
|
+
def list_vlans(self) -> list[VlanConfig]:
|
|
35
|
+
raise NotImplementedError
|
|
36
|
+
|
|
37
|
+
def get_vlan(self, vlan_id: int) -> VlanConfig:
|
|
38
|
+
raise NotImplementedError
|
|
39
|
+
|
|
40
|
+
def set_vlan(self, config: VlanConfig) -> None: ...
|
|
41
|
+
|
|
42
|
+
def get_dhcp_leases(self, vlan_id: int | None = None) -> list[DhcpLease]:
|
|
43
|
+
raise NotImplementedError
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_complete_impl_satisfies_protocol() -> None:
|
|
47
|
+
assert isinstance(_Complete(), ApplianceVlans)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_delete_vlan_is_required_by_protocol() -> None:
|
|
51
|
+
# runtime_checkable isinstance requires every declared member be present.
|
|
52
|
+
assert not isinstance(_NoDelete(), ApplianceVlans)
|
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
import importlib
|
|
10
10
|
|
|
11
11
|
import pytest
|
|
12
|
+
from _helpers import protocol_attrs
|
|
12
13
|
|
|
13
14
|
PROTOCOLS = [
|
|
14
15
|
(
|
|
@@ -93,5 +94,5 @@ def test_is_runtime_checkable(class_name: str, module: str, expected_methods: se
|
|
|
93
94
|
def test_protocol_shape(class_name: str, module: str, expected_methods: set[str]) -> None:
|
|
94
95
|
"""Each Protocol declares at least the expected method set."""
|
|
95
96
|
cls = getattr(importlib.import_module(module), class_name)
|
|
96
|
-
actual =
|
|
97
|
+
actual = protocol_attrs(cls)
|
|
97
98
|
assert expected_methods <= actual, f"{class_name} missing: {expected_methods - actual}"
|
|
@@ -10,6 +10,7 @@ inventory-string -> Protocol binding and the per-archetype capability set
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
import pytest
|
|
13
|
+
from _helpers import protocol_attrs
|
|
13
14
|
from testprotocols.devices import DeviceTypeSpec, all_device_types, get_device_type
|
|
14
15
|
from testprotocols.devices.base import BaseDeviceProtocol
|
|
15
16
|
from testprotocols.devices.client import (
|
|
@@ -195,7 +196,7 @@ def test_sdwan_router_aggregates_expected_capabilities() -> None:
|
|
|
195
196
|
"nat",
|
|
196
197
|
"conntrack",
|
|
197
198
|
}
|
|
198
|
-
actual =
|
|
199
|
+
actual = protocol_attrs(SdwanRouterDevice)
|
|
199
200
|
assert expected <= actual, f"missing: {expected - actual}"
|
|
200
201
|
assert "netem" not in actual, "netem should not be on SdwanRouterDevice"
|
|
201
202
|
|
|
@@ -220,7 +221,7 @@ def test_sdwan_appliance_aggregates_expected_capabilities() -> None:
|
|
|
220
221
|
"syslog",
|
|
221
222
|
"vpn",
|
|
222
223
|
}
|
|
223
|
-
actual =
|
|
224
|
+
actual = protocol_attrs(SdwanApplianceDevice)
|
|
224
225
|
assert expected <= actual, f"missing: {expected - actual}"
|
|
225
226
|
# device_management is Linux-host-shaped (ps/memory/board-logs/file-content)
|
|
226
227
|
# and intentionally NOT on the managed-appliance archetype — see GAPS.md.
|
|
@@ -257,7 +258,7 @@ def test_cpe_aggregates_expected_capabilities() -> None:
|
|
|
257
258
|
"firewall_zones",
|
|
258
259
|
"ntp_client",
|
|
259
260
|
}
|
|
260
|
-
actual =
|
|
261
|
+
actual = protocol_attrs(CpeDevice)
|
|
261
262
|
assert expected <= actual, f"missing: {expected - actual}"
|
|
262
263
|
|
|
263
264
|
|
|
@@ -281,7 +282,7 @@ def test_lan_client_aggregates_expected_capabilities() -> None:
|
|
|
281
282
|
"arp_client",
|
|
282
283
|
"vlan_client",
|
|
283
284
|
}
|
|
284
|
-
actual =
|
|
285
|
+
actual = protocol_attrs(LanClientDevice)
|
|
285
286
|
assert expected <= actual, f"missing: {expected - actual}"
|
|
286
287
|
|
|
287
288
|
|
|
@@ -301,13 +302,13 @@ def test_wlan_client_aggregates_expected_capabilities() -> None:
|
|
|
301
302
|
"nmap_scanner",
|
|
302
303
|
"file_transfer",
|
|
303
304
|
}
|
|
304
|
-
actual =
|
|
305
|
+
actual = protocol_attrs(WlanClientDevice)
|
|
305
306
|
assert expected <= actual, f"missing: {expected - actual}"
|
|
306
307
|
|
|
307
308
|
|
|
308
309
|
def test_qoe_client_aggregates_expected_capabilities() -> None:
|
|
309
310
|
expected = {"qoe_browser", "ip_interface", "dhcp_client", "syslog"}
|
|
310
|
-
actual =
|
|
311
|
+
actual = protocol_attrs(QoeClientDevice)
|
|
311
312
|
assert expected <= actual, f"missing: {expected - actual}"
|
|
312
313
|
|
|
313
314
|
|
|
@@ -323,15 +324,13 @@ def test_qoe_measurement_client_aggregates_expected_capabilities() -> None:
|
|
|
323
324
|
"pcap",
|
|
324
325
|
"syslog",
|
|
325
326
|
}
|
|
326
|
-
actual =
|
|
327
|
+
actual = protocol_attrs(QoeMeasurementClientDevice)
|
|
327
328
|
assert expected <= actual, f"missing: {expected - actual}"
|
|
328
329
|
|
|
329
330
|
|
|
330
331
|
def test_qoe_measurement_client_is_a_qoe_client() -> None:
|
|
331
332
|
"""Strict-superset invariant: the measurement client subsumes QoeClientDevice."""
|
|
332
|
-
assert
|
|
333
|
-
QoeMeasurementClientDevice.__protocol_attrs__
|
|
334
|
-
)
|
|
333
|
+
assert protocol_attrs(QoeClientDevice) <= protocol_attrs(QoeMeasurementClientDevice)
|
|
335
334
|
|
|
336
335
|
|
|
337
336
|
def test_acs_aggregates_expected_capabilities() -> None:
|
|
@@ -341,43 +340,43 @@ def test_acs_aggregates_expected_capabilities() -> None:
|
|
|
341
340
|
"file_transfer",
|
|
342
341
|
"packet_filter",
|
|
343
342
|
}
|
|
344
|
-
actual =
|
|
343
|
+
actual = protocol_attrs(AcsDevice)
|
|
345
344
|
assert expected <= actual, f"missing: {expected - actual}"
|
|
346
345
|
|
|
347
346
|
|
|
348
347
|
def test_provisioner_aggregates_expected_capabilities() -> None:
|
|
349
348
|
expected = {"dhcp_server", "pcap", "file_transfer", "packet_filter"}
|
|
350
|
-
actual =
|
|
349
|
+
actual = protocol_attrs(ProvisionerDevice)
|
|
351
350
|
assert expected <= actual, f"missing: {expected - actual}"
|
|
352
351
|
|
|
353
352
|
|
|
354
353
|
def test_tftp_aggregates_expected_capabilities() -> None:
|
|
355
354
|
expected = {"tftp_server"}
|
|
356
|
-
actual =
|
|
355
|
+
actual = protocol_attrs(TftpDevice)
|
|
357
356
|
assert expected <= actual, f"missing: {expected - actual}"
|
|
358
357
|
|
|
359
358
|
|
|
360
359
|
def test_traffic_controller_aggregates_expected_capabilities() -> None:
|
|
361
360
|
expected = {"netem", "ip_interface", "pcap", "syslog"}
|
|
362
|
-
actual =
|
|
361
|
+
actual = protocol_attrs(TrafficControllerDevice)
|
|
363
362
|
assert expected <= actual, f"missing: {expected - actual}"
|
|
364
363
|
|
|
365
364
|
|
|
366
365
|
def test_iperf_traffic_generator_aggregates_expected_capabilities() -> None:
|
|
367
366
|
expected = {"iperf_generator", "ip_interface"}
|
|
368
|
-
actual =
|
|
367
|
+
actual = protocol_attrs(IperfTrafficGeneratorDevice)
|
|
369
368
|
assert expected <= actual, f"missing: {expected - actual}"
|
|
370
369
|
|
|
371
370
|
|
|
372
371
|
def test_sip_phone_device_aggregates_expected_capabilities() -> None:
|
|
373
372
|
expected = {"sip_phone", "ip_interface", "dhcp_client", "number"}
|
|
374
|
-
actual =
|
|
373
|
+
actual = protocol_attrs(SipPhoneDevice)
|
|
375
374
|
assert expected <= actual, f"missing: {expected - actual}"
|
|
376
375
|
|
|
377
376
|
|
|
378
377
|
def test_sip_server_device_aggregates_expected_capabilities() -> None:
|
|
379
378
|
expected = {"sip_server", "pcap", "file_transfer"}
|
|
380
|
-
actual =
|
|
379
|
+
actual = protocol_attrs(SipServerDevice)
|
|
381
380
|
assert expected <= actual, f"missing: {expected - actual}"
|
|
382
381
|
|
|
383
382
|
|
|
@@ -400,7 +399,7 @@ def test_wan_server_aggregates_expected_capabilities() -> None:
|
|
|
400
399
|
"nat",
|
|
401
400
|
"conntrack",
|
|
402
401
|
}
|
|
403
|
-
actual =
|
|
402
|
+
actual = protocol_attrs(WanServerDevice)
|
|
404
403
|
assert expected <= actual, f"missing: {expected - actual}"
|
|
405
404
|
|
|
406
405
|
|
|
@@ -411,7 +410,7 @@ def test_wan_server_aggregates_expected_capabilities() -> None:
|
|
|
411
410
|
|
|
412
411
|
def test_acs_firewall_facets() -> None:
|
|
413
412
|
"""ACS aggregates packet_filter only — no NAT / port-forwarding / conntrack / zones."""
|
|
414
|
-
attrs =
|
|
413
|
+
attrs = protocol_attrs(AcsDevice)
|
|
415
414
|
assert "packet_filter" in attrs
|
|
416
415
|
for absent in ("nat", "port_forwarding", "conntrack", "firewall_zones"):
|
|
417
416
|
assert absent not in attrs, f"AcsDevice unexpectedly aggregates {absent}"
|
|
@@ -419,13 +418,13 @@ def test_acs_firewall_facets() -> None:
|
|
|
419
418
|
|
|
420
419
|
def test_provisioner_firewall_facets() -> None:
|
|
421
420
|
"""Provisioner aggregates packet_filter."""
|
|
422
|
-
attrs =
|
|
421
|
+
attrs = protocol_attrs(ProvisionerDevice)
|
|
423
422
|
assert "packet_filter" in attrs
|
|
424
423
|
|
|
425
424
|
|
|
426
425
|
def test_lan_client_firewall_facets() -> None:
|
|
427
426
|
"""LanClientDevice aggregates packet_filter only."""
|
|
428
|
-
attrs =
|
|
427
|
+
attrs = protocol_attrs(LanClientDevice)
|
|
429
428
|
assert "packet_filter" in attrs
|
|
430
429
|
for absent in ("nat", "port_forwarding", "conntrack", "firewall_zones"):
|
|
431
430
|
assert absent not in attrs, f"LanClientDevice unexpectedly aggregates {absent}"
|
|
@@ -433,7 +432,7 @@ def test_lan_client_firewall_facets() -> None:
|
|
|
433
432
|
|
|
434
433
|
def test_wan_server_firewall_facets() -> None:
|
|
435
434
|
"""WanServerDevice aggregates packet_filter, nat, conntrack — no port-forwarding / zones."""
|
|
436
|
-
attrs =
|
|
435
|
+
attrs = protocol_attrs(WanServerDevice)
|
|
437
436
|
for present in ("packet_filter", "nat", "conntrack"):
|
|
438
437
|
assert present in attrs
|
|
439
438
|
for absent in ("port_forwarding", "firewall_zones"):
|
|
@@ -442,7 +441,7 @@ def test_wan_server_firewall_facets() -> None:
|
|
|
442
441
|
|
|
443
442
|
def test_cpe_full_firewall_surface() -> None:
|
|
444
443
|
"""CpeDevice aggregates the complete firewall surface."""
|
|
445
|
-
attrs =
|
|
444
|
+
attrs = protocol_attrs(CpeDevice)
|
|
446
445
|
for present in ("firewall", "nat", "conntrack", "firewall_zones"):
|
|
447
446
|
assert present in attrs
|
|
448
447
|
# PortForwarding is no longer a separate attribute — it's bundled into firewall.
|
|
@@ -455,7 +454,12 @@ def test_cpe_full_firewall_surface() -> None:
|
|
|
455
454
|
def test_devices_without_firewall() -> None:
|
|
456
455
|
"""These archetypes must not pull in any of the firewall capabilities."""
|
|
457
456
|
firewall_attrs = (
|
|
458
|
-
"packet_filter",
|
|
457
|
+
"packet_filter",
|
|
458
|
+
"firewall",
|
|
459
|
+
"nat",
|
|
460
|
+
"port_forwarding",
|
|
461
|
+
"conntrack",
|
|
462
|
+
"firewall_zones",
|
|
459
463
|
)
|
|
460
464
|
for archetype in (
|
|
461
465
|
TftpDevice,
|
|
@@ -466,7 +470,7 @@ def test_devices_without_firewall() -> None:
|
|
|
466
470
|
TrafficControllerDevice,
|
|
467
471
|
IperfTrafficGeneratorDevice,
|
|
468
472
|
):
|
|
469
|
-
attrs =
|
|
473
|
+
attrs = protocol_attrs(archetype)
|
|
470
474
|
for absent in firewall_attrs:
|
|
471
475
|
assert absent not in attrs, f"{archetype.__name__} unexpectedly aggregates {absent}"
|
|
472
476
|
|
|
@@ -497,10 +501,10 @@ def test_archetype_inherits_base_device_protocol(archetype: type) -> None:
|
|
|
497
501
|
@pytest.mark.parametrize("archetype", _ALL_ARCHETYPES, ids=lambda a: a.__name__)
|
|
498
502
|
def test_archetype_carries_universal_identity(archetype: type) -> None:
|
|
499
503
|
"""Every archetype's protocol-attrs include device_name and device_type."""
|
|
500
|
-
attrs =
|
|
504
|
+
attrs = protocol_attrs(archetype)
|
|
501
505
|
assert {"device_name", "device_type"} <= attrs, (
|
|
502
506
|
f"{archetype.__name__} missing universal-identity attrs: "
|
|
503
|
-
f"{ {'device_name', 'device_type'} - attrs}"
|
|
507
|
+
f"{ {'device_name', 'device_type'} - attrs }"
|
|
504
508
|
)
|
|
505
509
|
|
|
506
510
|
|
|
@@ -529,36 +533,57 @@ def test_l3_switch_routed_registered() -> None:
|
|
|
529
533
|
|
|
530
534
|
def test_l3_switch_is_strict_superset_of_l2() -> None:
|
|
531
535
|
# Protocol inheritance: every L3 attribute set includes the full L2 set.
|
|
532
|
-
l2_attrs =
|
|
533
|
-
l3_attrs =
|
|
536
|
+
l2_attrs = protocol_attrs(L2Switch)
|
|
537
|
+
l3_attrs = protocol_attrs(L3Switch)
|
|
534
538
|
assert l2_attrs <= l3_attrs, f"L3Switch missing L2 attrs: {l2_attrs - l3_attrs}"
|
|
535
539
|
expected_l3 = {
|
|
536
|
-
"routed_interfaces",
|
|
537
|
-
"
|
|
540
|
+
"routed_interfaces",
|
|
541
|
+
"static_routes",
|
|
542
|
+
"routing_read",
|
|
543
|
+
"ospf",
|
|
544
|
+
"interface_dhcp",
|
|
545
|
+
"gateway_redundancy",
|
|
538
546
|
}
|
|
539
547
|
assert expected_l3 <= l3_attrs, f"missing: {expected_l3 - l3_attrs}"
|
|
540
548
|
assert "bgp" not in l3_attrs, "bgp belongs only on L3SwitchRouted"
|
|
541
549
|
|
|
542
550
|
|
|
543
551
|
def test_l3_switch_routed_adds_bgp() -> None:
|
|
544
|
-
attrs =
|
|
552
|
+
attrs = protocol_attrs(L3SwitchRouted)
|
|
545
553
|
assert "bgp" in attrs
|
|
546
|
-
assert
|
|
554
|
+
assert protocol_attrs(L3Switch) <= attrs # still a superset of L3Switch
|
|
547
555
|
|
|
548
556
|
|
|
549
557
|
def test_l2_switch_aggregates_expected_capabilities() -> None:
|
|
550
558
|
expected = {
|
|
551
|
-
"switch_ports",
|
|
552
|
-
"
|
|
553
|
-
"
|
|
554
|
-
"
|
|
559
|
+
"switch_ports",
|
|
560
|
+
"switch_vlans",
|
|
561
|
+
"spanning_tree",
|
|
562
|
+
"link_aggregation",
|
|
563
|
+
"port_poe",
|
|
564
|
+
"port_security",
|
|
565
|
+
"radius",
|
|
566
|
+
"first_hop_security",
|
|
567
|
+
"storm_control",
|
|
568
|
+
"switch_acl",
|
|
569
|
+
"discovery",
|
|
570
|
+
"mac_table",
|
|
571
|
+
"port_status",
|
|
572
|
+
"switch_qos",
|
|
573
|
+
"syslog",
|
|
574
|
+
"ntp",
|
|
555
575
|
}
|
|
556
|
-
actual =
|
|
576
|
+
actual = protocol_attrs(L2Switch)
|
|
557
577
|
assert expected <= actual, f"missing: {expected - actual}"
|
|
558
578
|
# host-substrate levers stay on the Linux twins, never on a managed switch
|
|
559
579
|
for host_lever in (
|
|
560
|
-
"conntrack",
|
|
561
|
-
"
|
|
580
|
+
"conntrack",
|
|
581
|
+
"pcap",
|
|
582
|
+
"ip_interface",
|
|
583
|
+
"nat",
|
|
584
|
+
"packet_filter",
|
|
585
|
+
"firewall_zones",
|
|
586
|
+
"wan_admin",
|
|
562
587
|
):
|
|
563
588
|
assert host_lever not in actual, f"{host_lever} must not be on L2Switch"
|
|
564
589
|
|
|
@@ -570,19 +595,29 @@ def test_l3_switch_excludes_host_substrate_levers() -> None:
|
|
|
570
595
|
Linux-host-shaped ``ip_interface``; firewall / NAT / conntrack belong on the
|
|
571
596
|
CPE/router archetypes; wan_admin is a Linux-only forced link-down lever.
|
|
572
597
|
"""
|
|
573
|
-
actual =
|
|
598
|
+
actual = protocol_attrs(L3Switch)
|
|
574
599
|
for host_lever in (
|
|
575
|
-
"conntrack",
|
|
576
|
-
"
|
|
600
|
+
"conntrack",
|
|
601
|
+
"pcap",
|
|
602
|
+
"ip_interface",
|
|
603
|
+
"nat",
|
|
604
|
+
"packet_filter",
|
|
605
|
+
"firewall_zones",
|
|
606
|
+
"wan_admin",
|
|
577
607
|
):
|
|
578
608
|
assert host_lever not in actual, f"{host_lever} must not be on L3Switch"
|
|
579
609
|
|
|
580
610
|
|
|
581
611
|
def test_l3_switch_routed_excludes_host_substrate_levers() -> None:
|
|
582
612
|
"""L3SwitchRouted (the BGP-capable variant) likewise excludes host-substrate levers."""
|
|
583
|
-
actual =
|
|
613
|
+
actual = protocol_attrs(L3SwitchRouted)
|
|
584
614
|
for host_lever in (
|
|
585
|
-
"conntrack",
|
|
586
|
-
"
|
|
615
|
+
"conntrack",
|
|
616
|
+
"pcap",
|
|
617
|
+
"ip_interface",
|
|
618
|
+
"nat",
|
|
619
|
+
"packet_filter",
|
|
620
|
+
"firewall_zones",
|
|
621
|
+
"wan_admin",
|
|
587
622
|
):
|
|
588
623
|
assert host_lever not in actual, f"{host_lever} must not be on L3SwitchRouted"
|