testprotocols 0.8.0__tar.gz → 0.9.0__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.
Files changed (150) hide show
  1. {testprotocols-0.8.0/src/testprotocols.egg-info → testprotocols-0.9.0}/PKG-INFO +1 -1
  2. {testprotocols-0.8.0 → testprotocols-0.9.0}/pyproject.toml +1 -1
  3. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/__init__.py +4 -1
  4. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/devices/sdwan.py +7 -0
  5. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/models/sdwan_appliance.py +17 -0
  6. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/sdwan_policy_manager.py +42 -4
  7. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/site_to_site_vpn.py +27 -2
  8. testprotocols-0.9.0/src/testprotocols/uplink_ports.py +54 -0
  9. {testprotocols-0.8.0 → testprotocols-0.9.0/src/testprotocols.egg-info}/PKG-INFO +1 -1
  10. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols.egg-info/SOURCES.txt +1 -0
  11. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_boundary_capability_templates.py +22 -0
  12. {testprotocols-0.8.0 → testprotocols-0.9.0}/LICENSE +0 -0
  13. {testprotocols-0.8.0 → testprotocols-0.9.0}/NOTICE +0 -0
  14. {testprotocols-0.8.0 → testprotocols-0.9.0}/README.md +0 -0
  15. {testprotocols-0.8.0 → testprotocols-0.9.0}/setup.cfg +0 -0
  16. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/aftr_gateway.py +0 -0
  17. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/appliance_nat.py +0 -0
  18. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/appliance_uplinks.py +0 -0
  19. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/appliance_vlans.py +0 -0
  20. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/arp_client.py +0 -0
  21. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/bgp.py +0 -0
  22. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/config_ownership.py +0 -0
  23. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/conntrack.py +0 -0
  24. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/content_filtering.py +0 -0
  25. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/device_info.py +0 -0
  26. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/device_lifecycle.py +0 -0
  27. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/device_management.py +0 -0
  28. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/devices/__init__.py +0 -0
  29. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/devices/base.py +0 -0
  30. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/devices/client.py +0 -0
  31. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/devices/cpe.py +0 -0
  32. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/devices/infra.py +0 -0
  33. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/devices/switch.py +0 -0
  34. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/devices/traffic.py +0 -0
  35. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/devices/voice.py +0 -0
  36. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/devices/wan.py +0 -0
  37. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/dhcp_client.py +0 -0
  38. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/dhcp_server.py +0 -0
  39. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/discovery.py +0 -0
  40. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/dns_client.py +0 -0
  41. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/file_transfer.py +0 -0
  42. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/firewall.py +0 -0
  43. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/firewall_zones.py +0 -0
  44. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/first_hop_security.py +0 -0
  45. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/gateway_redundancy.py +0 -0
  46. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/http_client.py +0 -0
  47. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/http_server.py +0 -0
  48. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/hw_console.py +0 -0
  49. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/interface_dhcp.py +0 -0
  50. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/ip_interface.py +0 -0
  51. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/ip_routing.py +0 -0
  52. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/iperf_client.py +0 -0
  53. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/iperf_generator.py +0 -0
  54. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/iperf_server.py +0 -0
  55. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/l3_firewall.py +0 -0
  56. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/l7_firewall.py +0 -0
  57. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/link_aggregation.py +0 -0
  58. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/mac_table.py +0 -0
  59. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/models/__init__.py +0 -0
  60. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/models/dhcp.py +0 -0
  61. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/models/firewall.py +0 -0
  62. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/models/impairment.py +0 -0
  63. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/models/l2_common.py +0 -0
  64. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/models/multicast.py +0 -0
  65. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/models/networking.py +0 -0
  66. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/models/packets.py +0 -0
  67. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/models/qoe.py +0 -0
  68. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/models/radius.py +0 -0
  69. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/models/switch.py +0 -0
  70. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/models/switch_routing.py +0 -0
  71. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/models/tr069.py +0 -0
  72. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/models/traffic.py +0 -0
  73. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/models/wan_edge.py +0 -0
  74. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/models/wifi.py +0 -0
  75. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/multicast_client.py +0 -0
  76. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/nat.py +0 -0
  77. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/netem_controller.py +0 -0
  78. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/network_attachment.py +0 -0
  79. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/network_endpoint.py +0 -0
  80. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/network_probe.py +0 -0
  81. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/nmap_scanner.py +0 -0
  82. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/ntp_client.py +0 -0
  83. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/ntp_config.py +0 -0
  84. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/ospf.py +0 -0
  85. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/packet_filter.py +0 -0
  86. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/pcap_capture.py +0 -0
  87. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/pdu_controller.py +0 -0
  88. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/placement.py +0 -0
  89. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/port_poe.py +0 -0
  90. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/port_security.py +0 -0
  91. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/port_status.py +0 -0
  92. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/py.typed +0 -0
  93. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/qoe_browser.py +0 -0
  94. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/radius_client.py +0 -0
  95. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/radius_server.py +0 -0
  96. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/reachability_responder.py +0 -0
  97. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/routed_interfaces.py +0 -0
  98. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/router.py +0 -0
  99. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/routing_read.py +0 -0
  100. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/sip_phone.py +0 -0
  101. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/sip_server.py +0 -0
  102. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/snmp_client.py +0 -0
  103. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/spanning_tree.py +0 -0
  104. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/static_routes.py +0 -0
  105. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/storm_control.py +0 -0
  106. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/streaming_server.py +0 -0
  107. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/switch_acl.py +0 -0
  108. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/switch_ports.py +0 -0
  109. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/switch_qos.py +0 -0
  110. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/switch_vlans.py +0 -0
  111. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/syslog_config.py +0 -0
  112. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/tftp_server.py +0 -0
  113. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/threat_prevention.py +0 -0
  114. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/tr069_client.py +0 -0
  115. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/tr069_server.py +0 -0
  116. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/traffic_shaping.py +0 -0
  117. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/upnp_client.py +0 -0
  118. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/vlan_client.py +0 -0
  119. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/wan_link_admin.py +0 -0
  120. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/wifi_bss.py +0 -0
  121. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/wifi_client.py +0 -0
  122. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/wifi_mesh.py +0 -0
  123. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/wifi_onboarding.py +0 -0
  124. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/wifi_radio.py +0 -0
  125. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/wifi_rf.py +0 -0
  126. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/wifi_stations.py +0 -0
  127. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols/wifi_transitions.py +0 -0
  128. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols.egg-info/dependency_links.txt +0 -0
  129. {testprotocols-0.8.0 → testprotocols-0.9.0}/src/testprotocols.egg-info/top_level.txt +0 -0
  130. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_appliance_vlans.py +0 -0
  131. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_archetype_purity.py +0 -0
  132. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_cpe_templates.py +0 -0
  133. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_device_types.py +0 -0
  134. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_dhcp_templates.py +0 -0
  135. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_firewall_templates.py +0 -0
  136. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_infra_templates.py +0 -0
  137. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_ip_interface.py +0 -0
  138. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_ip_routing.py +0 -0
  139. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_network_tool_templates.py +0 -0
  140. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_package_imports.py +0 -0
  141. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_radius_templates.py +0 -0
  142. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_sdwan_appliance_models.py +0 -0
  143. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_sdwan_appliance_templates.py +0 -0
  144. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_switch_models.py +0 -0
  145. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_switch_routing_models.py +0 -0
  146. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_switch_templates.py +0 -0
  147. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_traffic_templates.py +0 -0
  148. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_voice_wifi_templates.py +0 -0
  149. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_wan_edge_templates.py +0 -0
  150. {testprotocols-0.8.0 → testprotocols-0.9.0}/tests/test_wifi_templates.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: testprotocols
3
- Version: 0.8.0
3
+ Version: 0.9.0
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>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "testprotocols"
3
- version = "0.8.0"
3
+ version = "0.9.0"
4
4
  description = "Common Test Resource Layer — capability and device protocols for telco testing."
5
5
  requires-python = ">=3.12"
6
6
  dependencies = []
@@ -1,6 +1,6 @@
1
1
  """testprotocols — capability and device Protocols for telco resources under test."""
2
2
 
3
- __version__ = "0.8.0"
3
+ __version__ = "0.9.0"
4
4
 
5
5
  from testprotocols.aftr_gateway import AftrGateway
6
6
  from testprotocols.appliance_nat import ApplianceNat
@@ -99,6 +99,7 @@ from testprotocols.threat_prevention import ThreatPrevention
99
99
  from testprotocols.tr069_client import Tr069Client
100
100
  from testprotocols.tr069_server import Tr069Server
101
101
  from testprotocols.traffic_shaping import TrafficShaping
102
+ from testprotocols.uplink_ports import UplinkPortRef, UplinkPorts
102
103
  from testprotocols.upnp_client import UpnpClient
103
104
  from testprotocols.vlan_client import VlanClient
104
105
  from testprotocols.wan_link_admin import WanLinkAdmin
@@ -206,6 +207,8 @@ __all__ = [
206
207
  "Tr069Server",
207
208
  "TrafficControllerDevice",
208
209
  "TrafficShaping",
210
+ "UplinkPortRef",
211
+ "UplinkPorts",
209
212
  "UpnpClient",
210
213
  "VlanClient",
211
214
  "WanLinkAdmin",
@@ -26,6 +26,7 @@ from testprotocols.static_routes import StaticRoutes
26
26
  from testprotocols.syslog_config import SyslogConfig
27
27
  from testprotocols.threat_prevention import ThreatPrevention
28
28
  from testprotocols.traffic_shaping import TrafficShaping
29
+ from testprotocols.uplink_ports import UplinkPorts
29
30
  from testprotocols.wan_link_admin import WanLinkAdmin
30
31
 
31
32
 
@@ -104,5 +105,11 @@ class SdwanApplianceDevice(BaseDeviceProtocol, Protocol):
104
105
  """Whether this session owns the appliance's network configuration —
105
106
  False for a warm-spare member whose primary owns the writes."""
106
107
 
108
+ uplink_ports: UplinkPorts
109
+ """Declared uplink→switch-port wiring — which managed-switch ports carry
110
+ each WAN uplink, one entry per pair member (see ``uplink_ports``). A
111
+ testbed that splices or re-VLANs an uplink resolves the ports here; the
112
+ switch's ``PlacementPorts`` stays the write authorization."""
113
+
107
114
 
108
115
  register_device_type("sdwan_appliance", SdwanApplianceDevice)
@@ -555,6 +555,23 @@ class UplinkSelectionRule:
555
555
  performance_class: str | None = None
556
556
 
557
557
 
558
+ @dataclass(frozen=True)
559
+ class UplinkSelectionSettings:
560
+ """The scalar half of the uplink-selection surface (read-only snapshot).
561
+
562
+ The network-wide settings that frame the ordered rule list: which uplink
563
+ carries default-routed traffic, whether load balancing across uplinks is
564
+ on, and whether the overlay runs tunnels over all active uplinks
565
+ concurrently (``active_active_vpn`` — a prerequisite on some products for
566
+ performance-class steering to evaluate at all). Frozen: a captured
567
+ snapshot is a restore target, so it must not mutate after capture.
568
+ """
569
+
570
+ default_uplink: str
571
+ load_balancing_enabled: bool
572
+ active_active_vpn: bool
573
+
574
+
558
575
  # --- Static routes ---
559
576
 
560
577
 
@@ -12,16 +12,23 @@ path steering (``set_uplink_selection`` /
12
12
  classes reuse ``SLAPolicy`` by name) landed 2026-06-12; the default
13
13
  (primary) uplink pair (``get_default_uplink`` / ``set_default_uplink``)
14
14
  landed 2026-07-03 — the network-wide "which uplink carries default-routed
15
- traffic" knob, distinct from per-flow steering. ``apply_policy`` remains
16
- the generic escape hatch for vendor-shaped policies beyond that surface.
17
- Application-match steering grows on evidence.
15
+ traffic" knob, distinct from per-flow steering. The SLA read-back
16
+ (``get_sla_policies``), the scalar-settings snapshot
17
+ (``get_uplink_selection_settings``) and the concurrent-overlay-tunnels
18
+ write (``set_active_active_vpn``) landed 2026-08-10 — the read-back and
19
+ converge surface for the settings the rule list rides on. ``apply_policy``
20
+ remains the generic escape hatch for vendor-shaped policies beyond that
21
+ surface. Application-match steering grows on evidence.
18
22
  """
19
23
 
20
24
  from __future__ import annotations
21
25
 
22
26
  from typing import Any, Protocol, runtime_checkable
23
27
 
24
- from testprotocols.models.sdwan_appliance import UplinkSelectionRule
28
+ from testprotocols.models.sdwan_appliance import (
29
+ UplinkSelectionRule,
30
+ UplinkSelectionSettings,
31
+ )
25
32
  from testprotocols.models.wan_edge import AppFlow, SLAPolicy
26
33
 
27
34
 
@@ -86,3 +93,34 @@ class SdwanPolicyManager(Protocol):
86
93
  def get_uplink_selection(self) -> list[UplinkSelectionRule]:
87
94
  """Return the uplink-selection rules in evaluation order."""
88
95
  ...
96
+
97
+ def get_sla_policies(self) -> list[SLAPolicy]:
98
+ """Return the configured SLA policies (performance classes).
99
+
100
+ The read-back for ``configure_sla_policy`` / ``remove_sla_policy``:
101
+ thresholds as the product stores them, names as configured. Policies
102
+ the product created outside this session are included — a caller
103
+ arranging classes captures this read as its restore reference and
104
+ removes only what it configured.
105
+ """
106
+ ...
107
+
108
+ def get_uplink_selection_settings(self) -> UplinkSelectionSettings:
109
+ """Return the scalar settings framing the uplink-selection rule list.
110
+
111
+ One snapshot of the network-wide knobs — default uplink, load
112
+ balancing, concurrent overlay tunnels — read together so a caller
113
+ converging or restoring them works from a single consistent capture.
114
+ """
115
+ ...
116
+
117
+ def set_active_active_vpn(self, enabled: bool) -> None:
118
+ """Enable or disable concurrent overlay tunnels over all active uplinks.
119
+
120
+ On products where performance-class steering only evaluates with
121
+ concurrent tunnels enabled, this is the gate a testbed converges
122
+ before arranging classes. Read-back via
123
+ ``get_uplink_selection_settings``; the ordered rule list and the
124
+ default uplink are left untouched.
125
+ """
126
+ ...
@@ -22,8 +22,11 @@ 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
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
25
+ overlay exclusions (flow-level membership carve-outs — local breakout),
26
+ peer status, and observed overlay path quality per local uplink
27
+ (``get_vpn_path_metrics`` — the *tunnel* quantity, distinct from the
28
+ underlay probe metrics on ``Router.get_wan_path_metrics``; see SPLITS.md).
29
+ Overlay *membership* — which flows are in the overlay at all
27
30
  (role, hubs + default route, subnets, exclusions) — lives here; path
28
31
  selection *within* a domain (e.g. which overlay uplink, or which internet
29
32
  uplink) lives in ``sdwan_policy_manager``.
@@ -43,6 +46,7 @@ from testprotocols.models.sdwan_appliance import (
43
46
  SiteToSiteVpnConfig,
44
47
  VpnPeerStatus,
45
48
  )
49
+ from testprotocols.models.wan_edge import PathMetrics
46
50
 
47
51
 
48
52
  @runtime_checkable
@@ -88,3 +92,24 @@ class SiteToSiteVpn(Protocol):
88
92
  unchanged by the driver.
89
93
  """
90
94
  ...
95
+
96
+ def get_vpn_path_metrics(
97
+ self, timespan_s: int, peer: str | None = None
98
+ ) -> dict[str, PathMetrics]:
99
+ """Return observed overlay path quality per local uplink.
100
+
101
+ Keyed by local uplink name (``PathMetrics.link_name`` repeats the
102
+ key); aggregated over every overlay peer, or over the single peer
103
+ named by *peer* (its testbed-level site name, as ``get_vpn_peers``
104
+ reports it). This is the **tunnel** quantity — the path quality the
105
+ product's own overlay steering evaluates — not the underlay probe
106
+ metrics of ``Router.get_wan_path_metrics``.
107
+
108
+ *timespan_s* is explicit because products report this as a windowed
109
+ aggregate: the caller owns the freshness/staleness trade and sizes
110
+ its observation intervals beyond the product's reporting lag. An
111
+ uplink absent from the result carried no measured overlay path in
112
+ the window — presence is itself evidence a tunnel rode that uplink.
113
+ Empty dict when the device participates in no overlay.
114
+ """
115
+ ...
@@ -0,0 +1,54 @@
1
+ """Uplink switch-port wiring — boundary view of a WAN edge's declared uplink ports.
2
+
3
+ A testbed that instruments a WAN uplink (splicing an impairment path into it,
4
+ re-VLANing the port that carries it) needs to know **which managed-switch
5
+ ports carry which uplink**. That is a declared-topology fact the device's
6
+ driver resolves from its own configuration — a boundary view in the
7
+ ``DeviceInfo`` / ``PlacementPipe`` family, not behaviour.
8
+
9
+ The placement split carries over: the WAN-edge device declares which ports
10
+ carry its uplinks (**topology**, this view — the ``PlacementPipe`` analogue),
11
+ while the switch's ``PlacementPorts`` remains the **write authorization** for
12
+ re-VLANing anything. The two declarations stay independent, owned by the two
13
+ resources they describe.
14
+
15
+ One uplink maps to a *tuple* of port references — one per chassis / redundant
16
+ pair member — because a warm-spare pair shares its WAN segments: moving one
17
+ member's port and not its twin's splits the segment, so a consumer must see
18
+ the full member set to act on an uplink coherently.
19
+
20
+ The **as-found VLAN is deliberately absent**: port VLANs drift, so a recorded
21
+ value would rot; a consumer captures the as-found state live from the switch
22
+ at the moment it writes.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ from collections.abc import Mapping
28
+ from dataclasses import dataclass
29
+ from typing import Protocol, runtime_checkable
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class UplinkPortRef:
34
+ """One switch port carrying (one member's leg of) a WAN uplink."""
35
+
36
+ member: str
37
+ """Device (chassis / redundant-pair member) whose WAN port this is."""
38
+
39
+ switch: str
40
+ """Name of the managed switch carrying the port."""
41
+
42
+ port: str
43
+ """The switch port carrying this member's uplink."""
44
+
45
+
46
+ @runtime_checkable
47
+ class UplinkPorts(Protocol):
48
+ """Abstract contract for a WAN edge's declared uplink→switch-port wiring."""
49
+
50
+ @property
51
+ def wan_ports(self) -> Mapping[str, tuple[UplinkPortRef, ...]]:
52
+ """Uplink name → the switch ports carrying that uplink, one entry per
53
+ chassis / pair member; empty mapping when the wiring is undeclared."""
54
+ ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: testprotocols
3
- Version: 0.8.0
3
+ Version: 0.9.0
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>
@@ -81,6 +81,7 @@ src/testprotocols/threat_prevention.py
81
81
  src/testprotocols/tr069_client.py
82
82
  src/testprotocols/tr069_server.py
83
83
  src/testprotocols/traffic_shaping.py
84
+ src/testprotocols/uplink_ports.py
84
85
  src/testprotocols/upnp_client.py
85
86
  src/testprotocols/vlan_client.py
86
87
  src/testprotocols/wan_link_admin.py
@@ -13,6 +13,7 @@ from testprotocols.config_ownership import ConfigOwnership
13
13
  from testprotocols.device_info import DeviceInfo
14
14
  from testprotocols.network_attachment import NetworkAttachment
15
15
  from testprotocols.placement import PlacementPipe, PlacementPorts
16
+ from testprotocols.uplink_ports import UplinkPortRef, UplinkPorts
16
17
 
17
18
 
18
19
  def test_network_attachment_members() -> None:
@@ -35,6 +36,27 @@ def test_config_ownership_members() -> None:
35
36
  assert protocol_attrs(ConfigOwnership) == {"manages_network"}
36
37
 
37
38
 
39
+ def test_uplink_ports_members() -> None:
40
+ assert protocol_attrs(UplinkPorts) == {"wan_ports"}
41
+
42
+
43
+ def test_uplink_ports_view_satisfies_runtime_check() -> None:
44
+ """A driver view exposing the wiring mapping satisfies the runtime check."""
45
+
46
+ class _View:
47
+ @property
48
+ def wan_ports(self) -> dict[str, tuple[UplinkPortRef, ...]]:
49
+ return {
50
+ "wan1": (
51
+ UplinkPortRef(member="edge-a", switch="dist-1", port="7"),
52
+ UplinkPortRef(member="edge-b", switch="dist-1", port="8"),
53
+ ),
54
+ }
55
+
56
+ assert isinstance(_View(), UplinkPorts)
57
+ assert _View().wan_ports["wan1"][0].switch == "dist-1"
58
+
59
+
38
60
  def test_runtime_checkable_with_plain_attributes() -> None:
39
61
  """A driver view exposing plain attributes satisfies the runtime check."""
40
62
 
File without changes
File without changes
File without changes
File without changes