testprotocols 0.7.2__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 (151) hide show
  1. {testprotocols-0.7.2/src/testprotocols.egg-info → testprotocols-0.9.0}/PKG-INFO +1 -1
  2. {testprotocols-0.7.2 → testprotocols-0.9.0}/pyproject.toml +1 -1
  3. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/__init__.py +13 -1
  4. testprotocols-0.9.0/src/testprotocols/config_ownership.py +21 -0
  5. testprotocols-0.9.0/src/testprotocols/device_info.py +22 -0
  6. testprotocols-0.9.0/src/testprotocols/devices/__init__.py +80 -0
  7. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/devices/base.py +3 -1
  8. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/devices/client.py +10 -31
  9. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/devices/sdwan.py +17 -11
  10. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/devices/switch.py +9 -5
  11. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/devices/voice.py +4 -21
  12. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/sdwan_appliance.py +17 -0
  13. testprotocols-0.9.0/src/testprotocols/network_attachment.py +56 -0
  14. testprotocols-0.9.0/src/testprotocols/placement.py +44 -0
  15. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/sdwan_policy_manager.py +42 -4
  16. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/sip_phone.py +18 -0
  17. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/sip_server.py +8 -0
  18. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/site_to_site_vpn.py +27 -2
  19. testprotocols-0.9.0/src/testprotocols/uplink_ports.py +54 -0
  20. {testprotocols-0.7.2 → testprotocols-0.9.0/src/testprotocols.egg-info}/PKG-INFO +1 -1
  21. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols.egg-info/SOURCES.txt +7 -0
  22. testprotocols-0.9.0/tests/test_archetype_purity.py +59 -0
  23. testprotocols-0.9.0/tests/test_boundary_capability_templates.py +69 -0
  24. {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_device_types.py +21 -6
  25. {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_voice_wifi_templates.py +17 -0
  26. testprotocols-0.7.2/src/testprotocols/devices/__init__.py +0 -46
  27. {testprotocols-0.7.2 → testprotocols-0.9.0}/LICENSE +0 -0
  28. {testprotocols-0.7.2 → testprotocols-0.9.0}/NOTICE +0 -0
  29. {testprotocols-0.7.2 → testprotocols-0.9.0}/README.md +0 -0
  30. {testprotocols-0.7.2 → testprotocols-0.9.0}/setup.cfg +0 -0
  31. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/aftr_gateway.py +0 -0
  32. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/appliance_nat.py +0 -0
  33. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/appliance_uplinks.py +0 -0
  34. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/appliance_vlans.py +0 -0
  35. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/arp_client.py +0 -0
  36. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/bgp.py +0 -0
  37. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/conntrack.py +0 -0
  38. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/content_filtering.py +0 -0
  39. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/device_lifecycle.py +0 -0
  40. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/device_management.py +0 -0
  41. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/devices/cpe.py +0 -0
  42. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/devices/infra.py +0 -0
  43. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/devices/traffic.py +0 -0
  44. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/devices/wan.py +0 -0
  45. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/dhcp_client.py +0 -0
  46. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/dhcp_server.py +0 -0
  47. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/discovery.py +0 -0
  48. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/dns_client.py +0 -0
  49. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/file_transfer.py +0 -0
  50. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/firewall.py +0 -0
  51. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/firewall_zones.py +0 -0
  52. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/first_hop_security.py +0 -0
  53. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/gateway_redundancy.py +0 -0
  54. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/http_client.py +0 -0
  55. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/http_server.py +0 -0
  56. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/hw_console.py +0 -0
  57. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/interface_dhcp.py +0 -0
  58. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/ip_interface.py +0 -0
  59. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/ip_routing.py +0 -0
  60. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/iperf_client.py +0 -0
  61. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/iperf_generator.py +0 -0
  62. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/iperf_server.py +0 -0
  63. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/l3_firewall.py +0 -0
  64. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/l7_firewall.py +0 -0
  65. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/link_aggregation.py +0 -0
  66. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/mac_table.py +0 -0
  67. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/__init__.py +0 -0
  68. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/dhcp.py +0 -0
  69. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/firewall.py +0 -0
  70. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/impairment.py +0 -0
  71. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/l2_common.py +0 -0
  72. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/multicast.py +0 -0
  73. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/networking.py +0 -0
  74. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/packets.py +0 -0
  75. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/qoe.py +0 -0
  76. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/radius.py +0 -0
  77. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/switch.py +0 -0
  78. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/switch_routing.py +0 -0
  79. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/tr069.py +0 -0
  80. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/traffic.py +0 -0
  81. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/wan_edge.py +0 -0
  82. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/wifi.py +0 -0
  83. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/multicast_client.py +0 -0
  84. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/nat.py +0 -0
  85. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/netem_controller.py +0 -0
  86. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/network_endpoint.py +0 -0
  87. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/network_probe.py +0 -0
  88. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/nmap_scanner.py +0 -0
  89. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/ntp_client.py +0 -0
  90. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/ntp_config.py +0 -0
  91. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/ospf.py +0 -0
  92. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/packet_filter.py +0 -0
  93. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/pcap_capture.py +0 -0
  94. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/pdu_controller.py +0 -0
  95. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/port_poe.py +0 -0
  96. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/port_security.py +0 -0
  97. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/port_status.py +0 -0
  98. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/py.typed +0 -0
  99. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/qoe_browser.py +0 -0
  100. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/radius_client.py +0 -0
  101. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/radius_server.py +0 -0
  102. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/reachability_responder.py +0 -0
  103. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/routed_interfaces.py +0 -0
  104. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/router.py +0 -0
  105. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/routing_read.py +0 -0
  106. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/snmp_client.py +0 -0
  107. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/spanning_tree.py +0 -0
  108. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/static_routes.py +0 -0
  109. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/storm_control.py +0 -0
  110. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/streaming_server.py +0 -0
  111. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/switch_acl.py +0 -0
  112. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/switch_ports.py +0 -0
  113. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/switch_qos.py +0 -0
  114. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/switch_vlans.py +0 -0
  115. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/syslog_config.py +0 -0
  116. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/tftp_server.py +0 -0
  117. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/threat_prevention.py +0 -0
  118. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/tr069_client.py +0 -0
  119. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/tr069_server.py +0 -0
  120. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/traffic_shaping.py +0 -0
  121. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/upnp_client.py +0 -0
  122. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/vlan_client.py +0 -0
  123. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/wan_link_admin.py +0 -0
  124. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/wifi_bss.py +0 -0
  125. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/wifi_client.py +0 -0
  126. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/wifi_mesh.py +0 -0
  127. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/wifi_onboarding.py +0 -0
  128. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/wifi_radio.py +0 -0
  129. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/wifi_rf.py +0 -0
  130. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/wifi_stations.py +0 -0
  131. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/wifi_transitions.py +0 -0
  132. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols.egg-info/dependency_links.txt +0 -0
  133. {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols.egg-info/top_level.txt +0 -0
  134. {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_appliance_vlans.py +0 -0
  135. {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_cpe_templates.py +0 -0
  136. {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_dhcp_templates.py +0 -0
  137. {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_firewall_templates.py +0 -0
  138. {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_infra_templates.py +0 -0
  139. {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_ip_interface.py +0 -0
  140. {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_ip_routing.py +0 -0
  141. {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_network_tool_templates.py +0 -0
  142. {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_package_imports.py +0 -0
  143. {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_radius_templates.py +0 -0
  144. {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_sdwan_appliance_models.py +0 -0
  145. {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_sdwan_appliance_templates.py +0 -0
  146. {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_switch_models.py +0 -0
  147. {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_switch_routing_models.py +0 -0
  148. {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_switch_templates.py +0 -0
  149. {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_traffic_templates.py +0 -0
  150. {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_wan_edge_templates.py +0 -0
  151. {testprotocols-0.7.2 → 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.7.2
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.7.2"
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.7.2"
3
+ __version__ = "0.9.0"
4
4
 
5
5
  from testprotocols.aftr_gateway import AftrGateway
6
6
  from testprotocols.appliance_nat import ApplianceNat
@@ -8,8 +8,10 @@ from testprotocols.appliance_uplinks import ApplianceUplinks
8
8
  from testprotocols.appliance_vlans import ApplianceVlans
9
9
  from testprotocols.arp_client import ArpClient
10
10
  from testprotocols.bgp import Bgp
11
+ from testprotocols.config_ownership import ConfigOwnership
11
12
  from testprotocols.conntrack import Conntrack, ConntrackWhiteBox
12
13
  from testprotocols.content_filtering import ContentFiltering
14
+ from testprotocols.device_info import DeviceInfo
13
15
  from testprotocols.device_lifecycle import DeviceLifecycle
14
16
  from testprotocols.device_management import DeviceManagement
15
17
  from testprotocols.devices.base import BaseDeviceProtocol
@@ -60,6 +62,7 @@ from testprotocols.mac_table import MacTable
60
62
  from testprotocols.multicast_client import MulticastClient
61
63
  from testprotocols.nat import Nat
62
64
  from testprotocols.netem_controller import NetemController
65
+ from testprotocols.network_attachment import NetworkAttachment
63
66
  from testprotocols.nmap_scanner import NmapScanner
64
67
  from testprotocols.ntp_client import NtpClient
65
68
  from testprotocols.ntp_config import NtpConfig
@@ -67,6 +70,7 @@ from testprotocols.ospf import Ospf
67
70
  from testprotocols.packet_filter import PacketFilter, PacketFilterWhiteBox
68
71
  from testprotocols.pcap_capture import PcapCapture
69
72
  from testprotocols.pdu_controller import PduController
73
+ from testprotocols.placement import PlacementPipe, PlacementPorts
70
74
  from testprotocols.port_poe import PortPoe
71
75
  from testprotocols.port_security import PortSecurity
72
76
  from testprotocols.port_status import PortStatus
@@ -95,6 +99,7 @@ from testprotocols.threat_prevention import ThreatPrevention
95
99
  from testprotocols.tr069_client import Tr069Client
96
100
  from testprotocols.tr069_server import Tr069Server
97
101
  from testprotocols.traffic_shaping import TrafficShaping
102
+ from testprotocols.uplink_ports import UplinkPortRef, UplinkPorts
98
103
  from testprotocols.upnp_client import UpnpClient
99
104
  from testprotocols.vlan_client import VlanClient
100
105
  from testprotocols.wan_link_admin import WanLinkAdmin
@@ -116,10 +121,12 @@ __all__ = [
116
121
  "ArpClient",
117
122
  "BaseDeviceProtocol",
118
123
  "Bgp",
124
+ "ConfigOwnership",
119
125
  "Conntrack",
120
126
  "ConntrackWhiteBox",
121
127
  "ContentFiltering",
122
128
  "CpeDevice",
129
+ "DeviceInfo",
123
130
  "DeviceLifecycle",
124
131
  "DeviceManagement",
125
132
  "DhcpClient",
@@ -153,6 +160,7 @@ __all__ = [
153
160
  "MulticastClient",
154
161
  "Nat",
155
162
  "NetemController",
163
+ "NetworkAttachment",
156
164
  "NmapScanner",
157
165
  "NtpClient",
158
166
  "NtpConfig",
@@ -161,6 +169,8 @@ __all__ = [
161
169
  "PacketFilterWhiteBox",
162
170
  "PcapCapture",
163
171
  "PduController",
172
+ "PlacementPipe",
173
+ "PlacementPorts",
164
174
  "PortPoe",
165
175
  "PortSecurity",
166
176
  "PortStatus",
@@ -197,6 +207,8 @@ __all__ = [
197
207
  "Tr069Server",
198
208
  "TrafficControllerDevice",
199
209
  "TrafficShaping",
210
+ "UplinkPortRef",
211
+ "UplinkPorts",
200
212
  "UpnpClient",
201
213
  "VlanClient",
202
214
  "WanLinkAdmin",
@@ -0,0 +1,21 @@
1
+ """Config ownership template.
2
+
3
+ Defines the abstract contract for session write-ownership of a device's
4
+ network configuration — e.g. a warm-spare member whose primary owns the
5
+ writes. The natural home for future redundancy-role facts.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Protocol, runtime_checkable
11
+
12
+
13
+ @runtime_checkable
14
+ class ConfigOwnership(Protocol):
15
+ """Abstract contract for network-config write ownership."""
16
+
17
+ @property
18
+ def manages_network(self) -> bool:
19
+ """Whether this session owns the device's network configuration.
20
+ False for a warm-spare member whose primary owns the writes."""
21
+ ...
@@ -0,0 +1,22 @@
1
+ """Device info template.
2
+
3
+ Defines the abstract contract for a device's inventory identity — today the
4
+ hardware model. Vendor-neutral metadata the driver resolves from its
5
+ management API, inventory config, or class knowledge; the coverage axis for
6
+ model-parameterised tests. Extensible with further identity facts (serial,
7
+ firmware version) when a consumer needs them.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Protocol, runtime_checkable
13
+
14
+
15
+ @runtime_checkable
16
+ class DeviceInfo(Protocol):
17
+ """Abstract contract for device inventory identity."""
18
+
19
+ @property
20
+ def model(self) -> str:
21
+ """Hardware model identifier (e.g. ``"MX250"``)."""
22
+ ...
@@ -0,0 +1,80 @@
1
+ """Device archetype registry — maps inventory device_type strings to Protocols."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ from dataclasses import dataclass
7
+ from typing import Any, cast, get_type_hints
8
+
9
+ _registry: dict[str, DeviceTypeSpec] = {}
10
+
11
+ #: The sole sanctioned non-capability members — BaseDeviceProtocol's universal
12
+ #: identity pair (see devices/base.py).
13
+ IDENTITY_MEMBERS = frozenset({"device_name", "device_type"})
14
+
15
+
16
+ def non_capability_members(protocol: type) -> frozenset[str]:
17
+ """Members of *protocol* that are neither capability Protocols nor identity.
18
+
19
+ A capability member is annotated with a ``typing.Protocol`` class. Scalar
20
+ annotations (``str``, ``bool``, ``tuple``…) and property-declared members
21
+ (which carry no annotation at all) are both offenders. ``__protocol_attrs__``
22
+ is the CPython 3.12 runtime detail ``typing.get_protocol_members``
23
+ supersedes in 3.13; one cast keeps that knowledge here.
24
+ """
25
+ hints = get_type_hints(protocol)
26
+ members: frozenset[str] = frozenset(cast("Any", protocol).__protocol_attrs__)
27
+ return frozenset(
28
+ member
29
+ for member in members - IDENTITY_MEMBERS
30
+ if not getattr(hints.get(member), "_is_protocol", False)
31
+ )
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class DeviceTypeSpec:
36
+ """Registration record: inventory string -> @runtime_checkable Protocol class."""
37
+
38
+ name: str
39
+ protocol: type
40
+
41
+
42
+ def register_device_type(name: str, protocol: type) -> DeviceTypeSpec:
43
+ """Register *protocol* as the contract for inventory devices typed *name*.
44
+
45
+ Fails closed on archetype drift: every member must be a capability
46
+ Protocol (the BaseDeviceProtocol identity pair excepted) — see
47
+ docs/architecture/capability-only-archetypes.md. Returns the spec; also
48
+ stored in the module-level registry so ``get_device_type(name)`` can look
49
+ it up by inventory string.
50
+ """
51
+ offenders = non_capability_members(protocol)
52
+ if offenders:
53
+ raise TypeError(
54
+ f"device archetype {protocol.__name__!r} ({name!r}) declares "
55
+ f"non-capability members {sorted(offenders)}; archetypes compose "
56
+ "capability protocols only — move these into a capability protocol "
57
+ "(docs/architecture/capability-only-archetypes.md)"
58
+ )
59
+ spec = DeviceTypeSpec(name=name, protocol=protocol)
60
+ _registry[name] = spec
61
+ return spec
62
+
63
+
64
+ def get_device_type(name: str) -> DeviceTypeSpec | None:
65
+ """Return the spec for inventory *name*, or None if not registered."""
66
+ return _registry.get(name)
67
+
68
+
69
+ def all_device_types() -> dict[str, DeviceTypeSpec]:
70
+ """Return a copy of the full registry."""
71
+ return dict(_registry)
72
+
73
+
74
+ # Auto-import all archetype modules to trigger registration as a side effect.
75
+ # ``importlib.import_module`` avoids the unused-import / out-of-order-import
76
+ # friction that direct ``from … import …`` statements would create here, and
77
+ # keeps the import section at the top of the file as PEP 8 prefers.
78
+ for _mod in ("client", "cpe", "infra", "sdwan", "switch", "traffic", "voice", "wan"):
79
+ importlib.import_module(f"testprotocols.devices.{_mod}")
80
+ del _mod
@@ -21,7 +21,9 @@ class BaseDeviceProtocol(Protocol):
21
21
  Deliberately scoped to the universal pair — name + type. Anything a
22
22
  specific framework wants to expose (vitro's ``config`` /
23
23
  ``get_interactive_consoles``, boardfarm's lifecycle hooks, etc.) is
24
- the framework's own concern. Frameworks declare a connector Protocol
24
+ the framework's own concern. Every other archetype member must be a
25
+ capability Protocol — enforced by ``register_device_type``; see
26
+ docs/architecture/capability-only-archetypes.md. Frameworks declare a connector Protocol
25
27
  on their side that extends this base; testbed plugins compose
26
28
  archetype + framework-connector into a combined Protocol where step
27
29
  defs need both.
@@ -17,12 +17,14 @@ from testprotocols.ip_routing import IpRouting
17
17
  from testprotocols.iperf_client import IperfClient
18
18
  from testprotocols.iperf_server import IperfServer
19
19
  from testprotocols.multicast_client import MulticastClient
20
+ from testprotocols.network_attachment import NetworkAttachment
20
21
  from testprotocols.network_endpoint import NetworkEndpoint
21
22
  from testprotocols.network_probe import NetworkProbe
22
23
  from testprotocols.nmap_scanner import NmapScanner
23
24
  from testprotocols.ntp_client import NtpClient
24
25
  from testprotocols.packet_filter import PacketFilter
25
26
  from testprotocols.pcap_capture import PcapCapture
27
+ from testprotocols.placement import PlacementPipe
26
28
  from testprotocols.qoe_browser import QoeBrowser
27
29
  from testprotocols.reachability_responder import ReachabilityResponder
28
30
  from testprotocols.syslog_config import SyslogConfig
@@ -128,37 +130,14 @@ class QoeMeasurementClientDevice(QoeClientDevice, Protocol):
128
130
  responder: ReachabilityResponder
129
131
  pcap: PcapCapture
130
132
 
131
- test_ip: str
132
- """The client's test-plane address as a CIDR (e.g. ``"10.1.30.50/24"``), empty
133
- if the client is not homed to a test segment. Vendor-neutral metadata the
134
- driver resolves from its homing config — read it here rather than reaching into
135
- a framework-specific config object."""
136
-
137
- attachment_segment: str
138
- """The declared segment LABEL of a guest-attached client (a client whose
139
- test leg sits on a segment the testbed does not manage), empty for managed
140
- homing or when undeclared. A stable role name (e.g. ``"provider-vpn"``),
141
- never an address: it lets tests resolve "the client on segment X" by
142
- declaration when address facts are foreign-owned or discovered late.
143
- Vendor-neutral metadata from the driver's attachment config."""
144
-
145
- attachment_subnet: str
146
- """The declared subnet (CIDR) of a guest attachment, empty when undeclared.
147
- Vendor-neutral metadata from the driver's attachment config — read it here
148
- rather than reaching into a framework-specific config object."""
149
-
150
- mgmt_ip: str
151
- """The client's management-plane address, empty when undeclared. Published
152
- by the driver from its own config so isolation checks never consume a
153
- framework-specific config object."""
154
-
155
- pipe_switch: str
156
- """Name of the switch carrying this client's dedicated access port (its
157
- placement pipe), empty when the client has no declared pipe."""
158
-
159
- pipe_port: str
160
- """The dedicated access port whose VLAN is this client's placement knob,
161
- empty when the client has no declared pipe."""
133
+ attachment: NetworkAttachment
134
+ """Homing metadata — test/mgmt plane addresses and the declared guest
135
+ attachment (segment label + subnet). See ``NetworkAttachment`` for the
136
+ boundary against ``NetworkEndpoint``."""
137
+
138
+ pipe: PlacementPipe
139
+ """This client's declared placement pipe (dedicated access port on a
140
+ managed switch); counterpart of the switch's ``placement`` authorization."""
162
141
 
163
142
 
164
143
  register_device_type("linux_lan_client", LanClientDevice)
@@ -8,8 +8,10 @@ from testprotocols.appliance_nat import ApplianceNat
8
8
  from testprotocols.appliance_uplinks import ApplianceUplinks
9
9
  from testprotocols.appliance_vlans import ApplianceVlans
10
10
  from testprotocols.bgp import Bgp
11
+ from testprotocols.config_ownership import ConfigOwnership
11
12
  from testprotocols.conntrack import Conntrack
12
13
  from testprotocols.content_filtering import ContentFiltering
14
+ from testprotocols.device_info import DeviceInfo
13
15
  from testprotocols.devices import register_device_type
14
16
  from testprotocols.devices.base import BaseDeviceProtocol
15
17
  from testprotocols.ip_interface import IpInterface
@@ -24,6 +26,7 @@ from testprotocols.static_routes import StaticRoutes
24
26
  from testprotocols.syslog_config import SyslogConfig
25
27
  from testprotocols.threat_prevention import ThreatPrevention
26
28
  from testprotocols.traffic_shaping import TrafficShaping
29
+ from testprotocols.uplink_ports import UplinkPorts
27
30
  from testprotocols.wan_link_admin import WanLinkAdmin
28
31
 
29
32
 
@@ -93,17 +96,20 @@ class SdwanApplianceDevice(BaseDeviceProtocol, Protocol):
93
96
  lan: ApplianceVlans
94
97
  syslog: SyslogConfig
95
98
 
96
- model: str
97
- """Hardware model identifier (e.g. ``"MX250"``) — vendor-neutral metadata the
98
- driver resolves from its management API / inventory. The coverage axis for
99
- model-parameterised tests; read it here rather than reaching into a
100
- framework-specific config object."""
101
-
102
- manages_network: bool
103
- """Whether this session owns the device's network configuration. False for
104
- a warm-spare member whose primary owns the writes. Vendor-neutral metadata
105
- from the driver's config — read it here rather than reaching into a
106
- framework-specific config object."""
99
+ info: DeviceInfo
100
+ """Inventory identity (hardware model, e.g. ``"MX250"``) — the coverage
101
+ axis for model-parameterised tests, resolved by the driver from its
102
+ management API / inventory."""
103
+
104
+ ownership: ConfigOwnership
105
+ """Whether this session owns the appliance's network configuration —
106
+ False for a warm-spare member whose primary owns the writes."""
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."""
107
113
 
108
114
 
109
115
  register_device_type("sdwan_appliance", SdwanApplianceDevice)
@@ -12,6 +12,7 @@ from __future__ import annotations
12
12
  from typing import Protocol, runtime_checkable
13
13
 
14
14
  from testprotocols.bgp import Bgp
15
+ from testprotocols.device_info import DeviceInfo
15
16
  from testprotocols.devices import register_device_type
16
17
  from testprotocols.devices.base import BaseDeviceProtocol
17
18
  from testprotocols.discovery import Discovery
@@ -22,6 +23,7 @@ from testprotocols.link_aggregation import LinkAggregation
22
23
  from testprotocols.mac_table import MacTable
23
24
  from testprotocols.ntp_config import NtpConfig
24
25
  from testprotocols.ospf import Ospf
26
+ from testprotocols.placement import PlacementPorts
25
27
  from testprotocols.port_poe import PortPoe
26
28
  from testprotocols.port_security import PortSecurity
27
29
  from testprotocols.port_status import PortStatus
@@ -68,11 +70,13 @@ class L2Switch(BaseDeviceProtocol, Protocol):
68
70
  syslog: SyslogConfig
69
71
  ntp: NtpConfig
70
72
 
71
- placement_ports: tuple[str, ...]
72
- """Designated ports a testbed may re-VLAN to place a client; empty means
73
- no placement write is permitted on this switch. Independent of which
74
- device's pipe enters a port: pipe ownership is topology, this is write
75
- authorization — declared by the switch, the resource's owner."""
73
+ placement: PlacementPorts
74
+ """Placement-write authorization — which ports a testbed may re-VLAN to
75
+ place a client; counterpart of a client's ``pipe`` declaration."""
76
+
77
+ info: DeviceInfo
78
+ """Inventory identity (hardware model) — the coverage axis for
79
+ model-parameterised switch tests."""
76
80
 
77
81
 
78
82
  register_device_type("managed_switch_l2", L2Switch)
@@ -21,20 +21,8 @@ class SipPhoneDevice(BaseDeviceProtocol, Protocol):
21
21
  Used to drive call-control scenarios (register / invite / cancel / bye) and
22
22
  media-plane verification against a SIP server under test.
23
23
 
24
- A phone has one or more SIP accounts (lines). Most testbeds run single-line
25
- phones; multi-line phones expose additional entries in ``aors``.
26
-
27
- ``aors`` is the list of registered SIP URIs in RFC 3261 §6 address-of-record
28
- form (``sip:user@host`` with optional ``;params``). ``host`` may be an FQDN,
29
- an IPv4 literal (``10.0.0.5``), or an IPv6 bracketed literal
30
- (``[2001:db8::5]``) — the driver decides which form matches its testbed's
31
- address-resolution context. The list is never empty; ``aors[0]`` is the
32
- primary line that single-line operations (``sip_phone.dial``,
33
- ``sip_phone.answer``, MWI subscriptions, …) act on.
34
-
35
- ``number`` is the user-part of ``aors[0]`` — the SIP phone's E.164 /
36
- dial-string identifier for its primary line. Provided as a convenience so
37
- single-line callers don't have to parse SIP URIs.
24
+ Line identity — ``number`` / ``aors`` — lives on the ``SipPhone``
25
+ capability protocol.
38
26
 
39
27
  ``device_name`` / ``device_type`` come from :class:`BaseDeviceProtocol`.
40
28
  """
@@ -42,8 +30,6 @@ class SipPhoneDevice(BaseDeviceProtocol, Protocol):
42
30
  sip_phone: SipPhone
43
31
  ip_interface: IpInterface
44
32
  dhcp_client: DhcpClient
45
- number: str
46
- aors: list[str]
47
33
 
48
34
 
49
35
  @runtime_checkable
@@ -53,16 +39,13 @@ class SipServerDevice(BaseDeviceProtocol, Protocol):
53
39
  Provides the server-side SIP surface plus packet capture and file transfer
54
40
  for inspecting and exporting signalling exchanges.
55
41
 
56
- ``aor_domain`` is the host part that callers should embed in SIP AORs
57
- targeting this server (``sip:user@<aor_domain>``). May be an FQDN, an
58
- IPv4 literal, or an IPv6 bracketed literal — the driver decides which
59
- form matches its testbed's address-resolution context.
42
+ The AOR host part — ``aor_domain`` — lives on the ``SipServer``
43
+ capability protocol.
60
44
  """
61
45
 
62
46
  sip_server: SipServer
63
47
  pcap: PcapCapture
64
48
  file_transfer: FileTransfer
65
- aor_domain: str
66
49
 
67
50
 
68
51
  register_device_type("linux_sip_phone", SipPhoneDevice)
@@ -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
 
@@ -0,0 +1,56 @@
1
+ """Network attachment template.
2
+
3
+ Defines the abstract contract for an endpoint's *declared or discovered
4
+ homing* onto the testbed's network planes: its test-plane address, its
5
+ management-plane address, and — for a guest-attached endpoint (one whose
6
+ test leg sits on a segment the testbed does not manage) — the declared
7
+ segment label and subnet.
8
+
9
+ Distinct from ``NetworkEndpoint``: that is the live role-address query
10
+ ("give me the WAN address, now"), answered by whatever means the driver
11
+ chooses. ``NetworkAttachment`` is homing *metadata* the driver resolves
12
+ from its own config or discovers at attach time (e.g. a DHCP lease),
13
+ published so consumers never reach into a framework-specific config
14
+ object. Archetypes whose drivers already answer addressing via
15
+ ``NetworkEndpoint`` should not also compose this; it earns its place
16
+ where declared homing (segment labels, plane separation) is the
17
+ mechanism under test.
18
+
19
+ All values are vendor-neutral; empty string means undeclared/not homed.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from typing import Protocol, runtime_checkable
25
+
26
+
27
+ @runtime_checkable
28
+ class NetworkAttachment(Protocol):
29
+ """Abstract contract for an endpoint's homing metadata."""
30
+
31
+ @property
32
+ def test_ip(self) -> str:
33
+ """The endpoint's test-plane address as a CIDR (e.g. ``"10.1.30.50/24"``),
34
+ empty if not homed to a test segment. May be discovered at attach time
35
+ (e.g. from a DHCP lease) rather than declared."""
36
+ ...
37
+
38
+ @property
39
+ def mgmt_ip(self) -> str:
40
+ """The endpoint's management-plane address, empty when undeclared.
41
+ Published so isolation checks never consume a framework-specific
42
+ config object."""
43
+ ...
44
+
45
+ @property
46
+ def segment(self) -> str:
47
+ """Declared segment LABEL of a guest attachment — a stable role name
48
+ (e.g. ``"provider-vpn"``), never an address — empty for managed homing
49
+ or when undeclared. Lets tests resolve "the endpoint on segment X" by
50
+ declaration when address facts are foreign-owned or discovered late."""
51
+ ...
52
+
53
+ @property
54
+ def subnet(self) -> str:
55
+ """Declared subnet (CIDR) of a guest attachment, empty when undeclared."""
56
+ ...
@@ -0,0 +1,44 @@
1
+ """Client-placement templates — both halves of the placement mechanism.
2
+
3
+ A *placement pipe* is a client's dedicated access port on a managed switch:
4
+ re-VLANing that port is the client's placement knob. The mechanism has two
5
+ sides owned by two devices: the **client** declares which switch/port carries
6
+ its pipe (``PlacementPipe`` — topology fact), and the **switch** declares
7
+ which ports a testbed may re-VLAN at all (``PlacementPorts`` — write
8
+ authorization, owned by the resource's owner). Pipe ownership and write
9
+ authorization are deliberately independent declarations.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import Protocol, runtime_checkable
15
+
16
+
17
+ @runtime_checkable
18
+ class PlacementPipe(Protocol):
19
+ """Abstract contract for a client's declared placement pipe."""
20
+
21
+ @property
22
+ def switch(self) -> str:
23
+ """Name of the switch carrying this client's dedicated access port,
24
+ empty when the client has no declared pipe."""
25
+ ...
26
+
27
+ @property
28
+ def port(self) -> str:
29
+ """The dedicated access port whose VLAN is this client's placement
30
+ knob, empty when the client has no declared pipe."""
31
+ ...
32
+
33
+
34
+ @runtime_checkable
35
+ class PlacementPorts(Protocol):
36
+ """Abstract contract for a switch's placement-write authorization."""
37
+
38
+ @property
39
+ def ports(self) -> tuple[str, ...]:
40
+ """Designated ports a testbed may re-VLAN to place a client; empty
41
+ means no placement write is permitted on this switch. Independent of
42
+ which device's pipe enters a port: pipe ownership is topology, this is
43
+ write authorization — declared by the switch, the resource's owner."""
44
+ ...
@@ -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
+ ...
@@ -13,6 +13,24 @@ from typing import Protocol, runtime_checkable
13
13
  class SipPhone(Protocol):
14
14
  """Abstract contract for SIP phone operations."""
15
15
 
16
+ @property
17
+ def number(self) -> str:
18
+ """The user-part of ``aors[0]`` — the phone's E.164 / dial-string
19
+ identifier for its primary line. A convenience so single-line callers
20
+ don't have to parse SIP URIs."""
21
+ ...
22
+
23
+ @property
24
+ def aors(self) -> list[str]:
25
+ """Registered SIP URIs in RFC 3261 §6 address-of-record form
26
+ (``sip:user@host`` with optional ``;params``). ``host`` may be an FQDN,
27
+ an IPv4 literal, or an IPv6 bracketed literal — the driver decides
28
+ which form matches its testbed's address-resolution context. Never
29
+ empty once configured; ``aors[0]`` is the primary line that single-line
30
+ operations (``dial``, ``answer``, MWI subscriptions, …) act on.
31
+ Multi-line phones expose additional entries."""
32
+ ...
33
+
16
34
  def phone_start(self) -> None:
17
35
  """Start the SIP phone software."""
18
36
  ...