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.
- {testprotocols-0.7.2/src/testprotocols.egg-info → testprotocols-0.9.0}/PKG-INFO +1 -1
- {testprotocols-0.7.2 → testprotocols-0.9.0}/pyproject.toml +1 -1
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/__init__.py +13 -1
- testprotocols-0.9.0/src/testprotocols/config_ownership.py +21 -0
- testprotocols-0.9.0/src/testprotocols/device_info.py +22 -0
- testprotocols-0.9.0/src/testprotocols/devices/__init__.py +80 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/devices/base.py +3 -1
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/devices/client.py +10 -31
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/devices/sdwan.py +17 -11
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/devices/switch.py +9 -5
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/devices/voice.py +4 -21
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/sdwan_appliance.py +17 -0
- testprotocols-0.9.0/src/testprotocols/network_attachment.py +56 -0
- testprotocols-0.9.0/src/testprotocols/placement.py +44 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/sdwan_policy_manager.py +42 -4
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/sip_phone.py +18 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/sip_server.py +8 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/site_to_site_vpn.py +27 -2
- testprotocols-0.9.0/src/testprotocols/uplink_ports.py +54 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0/src/testprotocols.egg-info}/PKG-INFO +1 -1
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols.egg-info/SOURCES.txt +7 -0
- testprotocols-0.9.0/tests/test_archetype_purity.py +59 -0
- testprotocols-0.9.0/tests/test_boundary_capability_templates.py +69 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_device_types.py +21 -6
- {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_voice_wifi_templates.py +17 -0
- testprotocols-0.7.2/src/testprotocols/devices/__init__.py +0 -46
- {testprotocols-0.7.2 → testprotocols-0.9.0}/LICENSE +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/NOTICE +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/README.md +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/setup.cfg +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/aftr_gateway.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/appliance_nat.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/appliance_uplinks.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/appliance_vlans.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/arp_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/bgp.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/conntrack.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/content_filtering.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/device_lifecycle.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/device_management.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/devices/cpe.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/devices/infra.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/devices/traffic.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/devices/wan.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/dhcp_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/dhcp_server.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/discovery.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/dns_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/file_transfer.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/firewall.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/firewall_zones.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/first_hop_security.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/gateway_redundancy.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/http_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/http_server.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/hw_console.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/interface_dhcp.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/ip_interface.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/ip_routing.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/iperf_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/iperf_generator.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/iperf_server.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/l3_firewall.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/l7_firewall.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/link_aggregation.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/mac_table.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/__init__.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/dhcp.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/firewall.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/impairment.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/l2_common.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/multicast.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/networking.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/packets.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/qoe.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/radius.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/switch.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/switch_routing.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/tr069.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/traffic.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/wan_edge.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/models/wifi.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/multicast_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/nat.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/netem_controller.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/network_endpoint.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/network_probe.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/nmap_scanner.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/ntp_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/ntp_config.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/ospf.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/packet_filter.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/pcap_capture.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/pdu_controller.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/port_poe.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/port_security.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/port_status.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/py.typed +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/qoe_browser.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/radius_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/radius_server.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/reachability_responder.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/routed_interfaces.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/router.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/routing_read.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/snmp_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/spanning_tree.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/static_routes.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/storm_control.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/streaming_server.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/switch_acl.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/switch_ports.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/switch_qos.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/switch_vlans.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/syslog_config.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/tftp_server.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/threat_prevention.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/tr069_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/tr069_server.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/traffic_shaping.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/upnp_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/vlan_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/wan_link_admin.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/wifi_bss.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/wifi_client.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/wifi_mesh.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/wifi_onboarding.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/wifi_radio.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/wifi_rf.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/wifi_stations.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols/wifi_transitions.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols.egg-info/dependency_links.txt +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/src/testprotocols.egg-info/top_level.txt +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_appliance_vlans.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_cpe_templates.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_dhcp_templates.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_firewall_templates.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_infra_templates.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_ip_interface.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_ip_routing.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_network_tool_templates.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_package_imports.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_radius_templates.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_sdwan_appliance_models.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_sdwan_appliance_templates.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_switch_models.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_switch_routing_models.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_switch_templates.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_traffic_templates.py +0 -0
- {testprotocols-0.7.2 → testprotocols-0.9.0}/tests/test_wan_edge_templates.py +0 -0
- {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.
|
|
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
|
"""testprotocols — capability and device Protocols for telco resources under test."""
|
|
2
2
|
|
|
3
|
-
__version__ = "0.
|
|
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.
|
|
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
|
-
|
|
132
|
-
"""
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
97
|
-
"""
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
72
|
-
"""
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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.
|
|
16
|
-
the
|
|
17
|
-
|
|
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
|
|
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
|
...
|