test: add some basic ptp tests

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2026-04-24 14:29:58 +02:00
parent 3426c2527b
commit 95bd795347
122 changed files with 2483 additions and 132 deletions
+47
View File
@@ -359,6 +359,53 @@ $ make test-spec
...
```
### Node and Link Capabilities
Logical topology files (`topology.dot`) declare what each node and link
*requires*; physical topology files declare what each node and link
*provides*. When mapping a logical topology to physical hardware, infamy
only assigns a physical node to a logical node when the physical node's
`provides` set is a superset of the logical node's `requires` set. Tests
are skipped if no matching physical topology can be found.
#### Declaring requirements (logical topology)
```dot
dut [
requires="infix",
];
host:data -- dut:data [requires="ptp-hwts"]
```
#### Declaring capabilities (physical topology)
```dot
switch1 [
provides="infix",
];
switch1:eth0 -- switch2:eth0 [provides="ptp-hwts"]
```
#### Node capabilities
| Capability | Meaning |
|-------------------|-------------------------------------------------------------------------|
| `controller` | Reserved for the host/controller node; never assigned to a DUT |
| `infix` | Node runs Infix OS — required by virtually all DUT nodes |
| `gps` | Node has a GPS receiver available as a time reference |
| `watchdog` | Node has a hardware watchdog device |
#### Link capabilities
| Capability | Meaning |
|-------------------|-------------------------------------------------------------------------|
| `mgmt` | Link is a management path (typically coloured grey in diagrams) |
| `ieee-mc` | Link carries IEEE multicast traffic (required by LAG and some L2 tests) |
| `link-ctrl copper`| Link supports copper speed/duplex control |
| `ptp-hwts` | Both ends of this link support PTP hardware timestamping (PHC); required for sub-microsecond accuracy |
### Test Development
For adding a new test to the automated regression test suite, it's best
+16 -6
View File
@@ -39,7 +39,7 @@ static const char *instance_type_to_clock_type(const char *type)
* gmCapable, follow_up_info, assume_two_step, path_trace_enabled,
* and the tighter neighborPropDelayThresh.
*/
static bool emit_profile_globals(FILE *fp, const char *profile)
static bool emit_profile_globals(FILE *fp, const char *profile, bool hw_ts)
{
bool dot1as = profile && !strcmp(profile, "ieee802-dot1as");
@@ -53,7 +53,16 @@ static bool emit_profile_globals(FILE *fp, const char *profile)
fprintf(fp, "follow_up_info 1\n");
fprintf(fp, "assume_two_step 1\n");
fprintf(fp, "path_trace_enabled 1\n");
fprintf(fp, "neighborPropDelayThresh 800\n");
/*
* 802.1AS P2P gate: if meanLinkDelay exceeds this threshold the
* port stays asCapable=false and never leaves LISTENING. 800 ns
* is the 802.1AS default and is appropriate only for hardware
* timestamping — software timestamps (QEMU, tap interfaces) can
* easily produce peer delays in the microsecond range, which
* would keep both ports stuck in LISTENING indefinitely.
*/
if (hw_ts)
fprintf(fp, "neighborPropDelayThresh 800\n");
} else {
fprintf(fp, "transportSpecific 0\n");
}
@@ -134,7 +143,7 @@ static const char *instance_time_stamping(struct lyd_node *inst, json_t *root)
*/
static int write_instance_conf(struct lyd_node *inst, json_t *root)
{
const char *instance_type, *clock_type, *profile;
const char *instance_type, *clock_type, *profile, *ts;
struct lyd_node *default_ds, *port, *port_ds, *servo;
bool tc, bc, dot1as;
char path[256];
@@ -159,7 +168,7 @@ static int write_instance_conf(struct lyd_node *inst, json_t *root)
default_ds = lydx_get_child(inst, "default-ds");
instance_type = lydx_get_cattr(default_ds, "instance-type");
profile = lydx_get_cattr(default_ds, "infix-ptp:profile");
profile = lydx_get_cattr(default_ds, "profile");
clock_type = instance_type_to_clock_type(instance_type);
tc = (clock_type != NULL);
@@ -169,10 +178,11 @@ static int write_instance_conf(struct lyd_node *inst, json_t *root)
fprintf(fp, "uds_address /var/run/ptp4l-%u\n", idx);
/* Timestamping mode: hardware if all ports support it, software otherwise */
fprintf(fp, "time_stamping %s\n", instance_time_stamping(inst, root));
ts = instance_time_stamping(inst, root);
fprintf(fp, "time_stamping %s\n", ts);
/* Profile — sets transportSpecific and all protocol-mandatory options */
dot1as = emit_profile_globals(fp, profile);
dot1as = emit_profile_globals(fp, profile, !strcmp(ts, "hardware"));
/* domainNumber */
v = lydx_get_cattr(default_ds, "domain-number");
+3
View File
@@ -39,6 +39,9 @@
- name: "NTP Server"
suite: ntp/all.yaml
- name: "PTP"
suite: ptp/all.yaml
- name: "Routing"
suite: routing/all.yaml
+2 -2
View File
@@ -1,4 +1,4 @@
=== Container basic
=== Container Basic
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/containers/basic]
@@ -12,7 +12,7 @@ The RPC actions: stop + start, and restart are also verified.
==== Topology
image::topology.svg[Container basic topology, align=center, scaledwidth=75%]
image::topology.svg[Container Basic topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== Container with bridge network
=== Container with Bridge Network
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/containers/bridge]
@@ -13,7 +13,7 @@ port accessed from the host.
==== Topology
image::topology.svg[Container with bridge network topology, align=center, scaledwidth=75%]
image::topology.svg[Container with Bridge Network topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== Container enabled/disabled
=== Container Enabled/Disabled
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/containers/enabled]
@@ -15,7 +15,7 @@ Uses operational datastore to verify container running status.
==== Topology
image::topology.svg[Container enabled/disabled topology, align=center, scaledwidth=75%]
image::topology.svg[Container Enabled/Disabled topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== Container environment variables
=== Container Environment Variables
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/containers/environment]
@@ -15,7 +15,7 @@ changing an environment variable triggers a container restart.
==== Topology
image::topology.svg[Container environment variables topology, align=center, scaledwidth=75%]
image::topology.svg[Container Environment Variables topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== Container with physical interface
=== Container with Physical Interface
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/containers/phys]
@@ -9,7 +9,7 @@ given a physical interface instead of an end of a VETH pair.
==== Topology
image::topology.svg[Container with physical interface topology, align=center, scaledwidth=75%]
image::topology.svg[Container with Physical Interface topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== Container with VETH pair
=== Container with VETH Pair
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/containers/veth]
@@ -19,7 +19,7 @@ regular bridge, a VETH pair connects the container to the bridge.
==== Topology
image::topology.svg[Container with VETH pair topology, align=center, scaledwidth=75%]
image::topology.svg[Container with VETH Pair topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== DHCP option 121 vs option 3
=== DHCP Option 121 vs Option 3
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/dhcp/client_routes]
@@ -20,7 +20,7 @@ via 192.168.0.1 and a default route (option 121) via 192.168.0.254.
==== Topology
image::topology.svg[DHCP option 121 vs option 3 topology, align=center, scaledwidth=75%]
image::topology.svg[DHCP Option 121 vs Option 3 topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== GPS receiver basic test
=== GPS Receiver Basic Test
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/hardware/gps_simple]
@@ -12,7 +12,7 @@ which appear as virtio serial ports inside the guest.
==== Topology
image::topology.svg[GPS receiver basic test topology, align=center, scaledwidth=75%]
image::topology.svg[GPS Receiver Basic Test topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== USB configuration
=== USB Configuration
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/hardware/usb]
@@ -16,7 +16,7 @@ port is handled correctly.
==== Topology
image::topology.svg[USB configuration topology, align=center, scaledwidth=75%]
image::topology.svg[USB Configuration topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== USB configuration with two USB ports
=== USB Configuration with Two USB Ports
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/hardware/usb_two_ports]
@@ -9,7 +9,7 @@ when having two USB ports.
==== Topology
image::topology.svg[USB configuration with two USB ports topology, align=center, scaledwidth=75%]
image::topology.svg[USB Configuration with Two USB Ports topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== Watchdog reset on system lockup
=== Watchdog Reset on System Lockup
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/hardware/watchdog]
@@ -14,7 +14,7 @@ timeout.
==== Topology
image::topology.svg[Watchdog reset on system lockup topology, align=center, scaledwidth=75%]
image::topology.svg[Watchdog Reset on System Lockup topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== Bridge basic
=== Bridge Basic
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/bridge_basic]
@@ -16,7 +16,7 @@ Test basic connectivity to a bridge
==== Topology
image::topology.svg[Bridge basic topology, align=center, scaledwidth=75%]
image::topology.svg[Bridge Basic topology, align=center, scaledwidth=75%]
==== Sequence
@@ -1,4 +1,4 @@
=== Bridge forwarding dual DUTs
=== Bridge Forwarding Dual DUTs
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/bridge_fwd_dual_dut]
@@ -28,7 +28,7 @@ Ping through two bridges on two different DUTs.
==== Topology
image::topology.svg[Bridge forwarding dual DUTs topology, align=center, scaledwidth=75%]
image::topology.svg[Bridge Forwarding Dual DUTs topology, align=center, scaledwidth=75%]
==== Sequence
@@ -1,4 +1,4 @@
=== Bridge forwarding single DUTs
=== Bridge Forwarding Single DUTs
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/bridge_fwd_sgl_dut]
@@ -28,7 +28,7 @@ Tests forwarding through a DUT with two bridged interfaces on one DUT.
==== Topology
image::topology.svg[Bridge forwarding single DUTs topology, align=center, scaledwidth=75%]
image::topology.svg[Bridge Forwarding Single DUTs topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== Bridge with a physical port and a veth
=== Bridge with a Physical Port and a Veth
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/bridge_veth]
@@ -18,7 +18,7 @@ PING --> br0
==== Topology
image::topology.svg[Bridge with a physical port and a veth topology, align=center, scaledwidth=75%]
image::topology.svg[Bridge with a Physical Port and a Veth topology, align=center, scaledwidth=75%]
==== Sequence
@@ -1,4 +1,4 @@
=== Bridge VLAN separation
=== Bridge VLAN Separation
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/bridge_vlan_separation]
@@ -27,7 +27,7 @@ Test that two VLANs are correctly separated in the bridge
==== Topology
image::topology.svg[Bridge VLAN separation topology, align=center, scaledwidth=75%]
image::topology.svg[Bridge VLAN Separation topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== Dual bridges on one device
=== Dual Bridges on One Device
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/dual_bridge]
@@ -15,7 +15,7 @@ PC - target:data veth0a - veth0b
==== Topology
image::topology.svg[Dual bridges on one device topology, align=center, scaledwidth=75%]
image::topology.svg[Dual Bridges on One Device topology, align=center, scaledwidth=75%]
==== Sequence
@@ -1,4 +1,4 @@
=== Interface status
=== Interface Status
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/iface_enable_disable]
@@ -11,7 +11,7 @@ Both admin-status and oper-status are verified.
==== Topology
image::topology.svg[Interface status topology, align=center, scaledwidth=75%]
image::topology.svg[Interface Status topology, align=center, scaledwidth=75%]
==== Sequence
@@ -1,4 +1,4 @@
=== Custom MAC address on interface
=== Custom MAC Address on Interface
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/iface_phys_address]
@@ -10,7 +10,7 @@ an offset applied.
==== Topology
image::topology.svg[Custom MAC address on interface topology, align=center, scaledwidth=75%]
image::topology.svg[Custom MAC Address on Interface topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== IGMP basic
=== IGMP Basic
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/igmp_basic]
@@ -33,7 +33,7 @@ the group.
==== Topology
image::topology.svg[IGMP basic topology, align=center, scaledwidth=75%]
image::topology.svg[IGMP Basic topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== Interface IPv6 autoconf for bridges
=== Interface IPv6 Autoconf for Bridges
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/ipv6_address]
@@ -9,7 +9,7 @@ See issue #473 for details.
==== Topology
image::topology.svg[Interface IPv6 autoconf for bridges topology, align=center, scaledwidth=75%]
image::topology.svg[Interface IPv6 Autoconf for Bridges topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== Ling Aggregation Basic
=== Link Aggregation Basic
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/lag_basic]
@@ -15,7 +15,7 @@ each test step using the `mon` interface.
==== Topology
image::topology.svg[Ling Aggregation Basic topology, align=center, scaledwidth=75%]
image::topology.svg[Link Aggregation Basic topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== Routing basic
=== Routing Basic
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/routing_basic]
@@ -12,7 +12,7 @@ expected to be lost.
==== Topology
image::topology.svg[Routing basic topology, align=center, scaledwidth=75%]
image::topology.svg[Routing Basic topology, align=center, scaledwidth=75%]
==== Sequence
@@ -1,4 +1,4 @@
=== Static multicast filters
=== Static Multicast Filters
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/static_multicast_filters]
@@ -24,7 +24,7 @@ enabled when using static multicast filters)
==== Topology
image::topology.svg[Static multicast filters topology, align=center, scaledwidth=75%]
image::topology.svg[Static Multicast Filters topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== GRE point-to-point
=== GRE Point-to-Point
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/tunnel_basic]
@@ -14,7 +14,7 @@ connectivity with the second DUT through the tunnel.
==== Topology
image::topology.svg[GRE point-to-point topology, align=center, scaledwidth=75%]
image::topology.svg[GRE Point-to-Point topology, align=center, scaledwidth=75%]
==== Sequence
@@ -1,4 +1,4 @@
=== GRETAP point-to-point
=== GRETAP Point-to-Point
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/tunnel_basic]
@@ -14,7 +14,7 @@ connectivity with the second DUT through the tunnel.
==== Topology
image::topology.svg[GRETAP point-to-point topology, align=center, scaledwidth=75%]
image::topology.svg[GRETAP Point-to-Point topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== VXLAN point-to-point
=== VXLAN Point-to-Point
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/tunnel_basic]
@@ -14,7 +14,7 @@ connectivity with the second DUT through the tunnel.
==== Topology
image::topology.svg[VXLAN point-to-point topology, align=center, scaledwidth=75%]
image::topology.svg[VXLAN Point-to-Point topology, align=center, scaledwidth=75%]
==== Sequence
@@ -1,4 +1,4 @@
=== GRETAP bridged with physical interface
=== GRETAP Bridged with Physical Interface
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/tunnel_bridged]
@@ -13,7 +13,7 @@ first DUT. On host, verify connectivity with the second DUT through tunnel.
==== Topology
image::topology.svg[GRETAP bridged with physical interface topology, align=center, scaledwidth=75%]
image::topology.svg[GRETAP Bridged with Physical Interface topology, align=center, scaledwidth=75%]
==== Sequence
@@ -1,4 +1,4 @@
=== VXLAN bridged with physical interface
=== VXLAN Bridged with Physical Interface
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/tunnel_bridged]
@@ -13,7 +13,7 @@ first DUT. On host, verify connectivity with the second DUT through tunnel.
==== Topology
image::topology.svg[VXLAN bridged with physical interface topology, align=center, scaledwidth=75%]
image::topology.svg[VXLAN Bridged with Physical Interface topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== GRE Tunnel TTL verification
=== GRE Tunnel TTL Verification
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/tunnel_ttl]
@@ -18,7 +18,7 @@ many hops and the TTL would reach zero before the last routing step.)
==== Topology
image::topology.svg[GRE Tunnel TTL verification topology, align=center, scaledwidth=75%]
image::topology.svg[GRE Tunnel TTL Verification topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== VXLAN Tunnel TTL verification
=== VXLAN Tunnel TTL Verification
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/tunnel_ttl]
@@ -18,7 +18,7 @@ many hops and the TTL would reach zero before the last routing step.)
==== Topology
image::topology.svg[VXLAN Tunnel TTL verification topology, align=center, scaledwidth=75%]
image::topology.svg[VXLAN Tunnel TTL Verification topology, align=center, scaledwidth=75%]
==== Sequence
@@ -1,4 +1,4 @@
=== Verify that all interface types can be created
=== Verify that All Interface Types Can Be Created
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/verify_all_interface_types]
@@ -20,7 +20,7 @@ slightly longer than sending the entire configuration at once.
==== Topology
image::topology.svg[Verify that all interface types can be created topology, align=center, scaledwidth=75%]
image::topology.svg[Verify that All Interface Types Can Be Created topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== Verify that VETH pairs can be deleted
=== Verify that VETH Pairs Can Be Deleted
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/veth_delete]
@@ -14,7 +14,7 @@ from any other step. This to trigger a new configuration "generation".
==== Topology
image::topology.svg[Verify that VETH pairs can be deleted topology, align=center, scaledwidth=75%]
image::topology.svg[Verify that VETH Pairs Can Be Deleted topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== VLAN ping connectivity
=== VLAN Ping Connectivity
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/vlan_ping]
@@ -8,7 +8,7 @@ Very basic test if the VLAN interface configuration works.
==== Topology
image::topology.svg[VLAN ping connectivity topology, align=center, scaledwidth=75%]
image::topology.svg[VLAN Ping Connectivity topology, align=center, scaledwidth=75%]
==== Sequence
@@ -1,4 +1,4 @@
=== WireGuard multipoint
=== WireGuard Multipoint
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/wireguard_multipoint]
@@ -41,7 +41,7 @@ Security boundaries:
==== Topology
image::topology.svg[WireGuard multipoint topology, align=center, scaledwidth=75%]
image::topology.svg[WireGuard Multipoint topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== Get operational
=== Get Operational
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/misc/operational_all]
@@ -8,7 +8,7 @@ Basic test just to get operational from test-config without errors.
==== Topology
image::topology.svg[Get operational topology, align=center, scaledwidth=75%]
image::topology.svg[Get Operational topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== Support data collection
=== Support Data Collection
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/misc/support_collect]
@@ -10,7 +10,7 @@ encryption (when available on target).
==== Topology
image::topology.svg[Support data collection topology, align=center, scaledwidth=75%]
image::topology.svg[Support Data Collection topology, align=center, scaledwidth=75%]
==== Sequence
@@ -1,4 +1,4 @@
=== NTP client stratum selection
=== NTP Client Stratum Selection
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ntp/client_stratum_selection]
@@ -20,7 +20,7 @@ should then select srv1 (lower stratum) as its sync source.
==== Topology
image::topology.svg[NTP client stratum selection topology, align=center, scaledwidth=75%]
image::topology.svg[NTP Client Stratum Selection topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== NTP server and client interoperability
=== NTP Server and Client Interoperability
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ntp/server_client]
@@ -14,7 +14,7 @@ Verify NTP server and client work together:
==== Topology
image::topology.svg[NTP server and client interoperability topology, align=center, scaledwidth=75%]
image::topology.svg[NTP Server and Client Interoperability topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== NTP peer mode
=== NTP Peer Mode
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ntp/server_mode_peer]
@@ -22,7 +22,7 @@ selected as sync source by the other peer.
==== Topology
image::topology.svg[NTP peer mode topology, align=center, scaledwidth=75%]
image::topology.svg[NTP Peer Mode topology, align=center, scaledwidth=75%]
==== Sequence
+2 -2
View File
@@ -1,4 +1,4 @@
=== NTP server mode
=== NTP Server Mode
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ntp/server_mode_server]
@@ -18,7 +18,7 @@ The test verifies both servers operate correctly and serve accurate time.
==== Topology
image::topology.svg[NTP server mode topology, align=center, scaledwidth=75%]
image::topology.svg[NTP Server Mode topology, align=center, scaledwidth=75%]
==== Sequence
@@ -1,4 +1,4 @@
=== NTP server standalone mode
=== NTP Server Standalone Mode
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ntp/server_mode_standalone]
@@ -12,7 +12,7 @@ syncing from any upstream sources.
==== Topology
image::topology.svg[NTP server standalone mode topology, align=center, scaledwidth=75%]
image::topology.svg[NTP Server Standalone Mode topology, align=center, scaledwidth=75%]
==== Sequence
+46
View File
@@ -0,0 +1,46 @@
:testgroup:
== PTP Tests
Tests for PTP (IEEE 1588 Precision Time Protocol) functionality across
different clock types and operational scenarios:
- Basic smoke test: full stack end-to-end
- BTCA: grandmaster election and runtime re-election via `priority1`
- Boundary Clock: two-port BC forwarding time with `steps-removed` accounting
- Transparent Clock: E2E and P2P TC on hardware-timestamping nodes
- Port fault recovery: link-down/link-up state machine and re-convergence
The offset convergence threshold in the tests varies with the timestamping
capability of the nodes under test:
[cols="1,1,3",options="header"]
|===
| Timestamping | Default Threshold | Typical scenario
| Software | 100 000 ns | Virtual machines, QEMU tap interfaces
| Hardware | 1 000 ns | Nodes with PHC hardware timestamping
|===
Tests that accept a `--threshold-ns` option may use that value instead.
When no option is given, the threshold is selected automatically based
on the `ptp-hwts` capability of the relevant node in the physical test
system's topology file.
<<<
include::basic/Readme.adoc[]
<<<
include::bmca/Readme.adoc[]
<<<
include::boundary_clock/Readme.adoc[]
<<<
include::transparent_clock/Readme.adoc[]
<<<
include::port_recovery/Readme.adoc[]
+18
View File
@@ -0,0 +1,18 @@
---
- name: PTP basic
suite: basic/test.yaml
- name: PTP BTCA grandmaster election
suite: bmca/test.yaml
- name: PTP boundary clock
suite: boundary_clock/test.yaml
- name: PTP transparent clock
suite: transparent_clock/test.yaml
- name: PTP port fault recovery
case: port_recovery/test.py
- name: PTP servo step-threshold
case: servo/test.py
+6
View File
@@ -0,0 +1,6 @@
include::ieee1588.adoc[]
<<<
include::ieee802dot1as.adoc[]
+28
View File
@@ -0,0 +1,28 @@
=== PTP basic (IEEE 1588)
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/basic]
==== Description
Verify basic PTP operation end-to-end: clock configuration, port state
transitions, and clock servo convergence.
Two Ordinary Clocks are connected back-to-back. The grandmaster is
configured with `priority1=1` so it always wins the BTCA election; the
time receiver is configured with `time-receiver-only` so it never
attempts to become grandmaster. The test is run once per supported
profile, covering both IEEE 1588-2019 (UDP/IPv4, E2E) and IEEE 802.1AS
(Layer 2, P2P).
==== Topology
image::topology.svg[PTP basic (IEEE 1588) topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to DUTs
. Configure grandmaster (OC, ieee1588, priority1=1) and time receiver (ieee1588, priority1=128, client-only)
. Wait for grandmaster and time receiver ports to reach active states
. Wait for time receiver offset to converge
+1
View File
@@ -0,0 +1 @@
test.py
+28
View File
@@ -0,0 +1,28 @@
=== PTP basic (IEEE 802.1AS)
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/basic]
==== Description
Verify basic PTP operation end-to-end: clock configuration, port state
transitions, and clock servo convergence.
Two Ordinary Clocks are connected back-to-back. The grandmaster is
configured with `priority1=1` so it always wins the BTCA election; the
time receiver is configured with `time-receiver-only` so it never
attempts to become grandmaster. The test is run once per supported
profile, covering both IEEE 1588-2019 (UDP/IPv4, E2E) and IEEE 802.1AS
(Layer 2, P2P).
==== Topology
image::topology.svg[PTP basic (IEEE 802.1AS) topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to DUTs
. Configure grandmaster (OC, ieee802-dot1as, priority1=1) and time receiver (ieee802-dot1as, priority1=128, client-only)
. Wait for grandmaster and time receiver ports to reach active states
. Wait for time receiver offset to converge
+1
View File
@@ -0,0 +1 @@
test.py
+29
View File
@@ -0,0 +1,29 @@
=== PTP basic
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/basic]
==== Description
Verify basic PTP operation end-to-end: clock configuration, port state
transitions, and clock servo convergence.
Two Ordinary Clocks are connected back-to-back. The grandmaster is
configured with `priority1=1` so it always wins the BTCA election; the
time receiver is configured with `time-receiver-only` so it never
attempts to become grandmaster. Software timestamping is used, making
the test suitable for virtual machines as well as real hardware.
This is the smoke test: if it passes, the full PTP stack is working.
==== Topology
image::topology.svg[PTP basic topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to DUTs
. Configure grandmaster (priority1=1) and time receiver (priority1=128, client-only)
. Wait for grandmaster and time receiver ports to reach active states
. Wait for time receiver offset to converge
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""PTP basic
Verify basic PTP operation end-to-end: clock configuration, port state
transitions, and clock servo convergence.
Two Ordinary Clocks are connected back-to-back. The grandmaster is
configured with `priority1=1` so it always wins the BTCA election; the
time receiver is configured with `time-receiver-only` so it never
attempts to become grandmaster. The test is run once per supported
profile, covering both IEEE 1588-2019 (UDP/IPv4, E2E) and IEEE 802.1AS
(Layer 2, P2P).
"""
import infamy
import infamy.ptp as ptp
from infamy import until
from infamy.util import parallel
class ArgumentParser(infamy.ArgumentParser):
def __init__(self):
super().__init__()
self.args.add_argument("--profile", default="ieee1588",
choices=["ieee1588", "ieee802-dot1as"])
self.args.add_argument("--threshold-ns", type=int, default=None)
def configure_oc(iface, priority1, client_only, profile, ip=None):
config = {
"ieee1588-ptp-tt": {
"ptp": {
"instances": {
"instance": [{
"instance-index": 0,
"default-ds": {
"instance-type": "oc",
"domain-number": 0,
"priority1": priority1,
"priority2": 128,
"infix-ptp:profile": profile,
"time-receiver-only": client_only,
},
"ports": {
"port": [{
"port-index": 1,
"underlying-interface": iface,
}]
}
}]
}
}
}
}
# Always enable the underlying interface — ptp4l needs it up regardless
# of profile. IEEE 1588 also needs an IPv4 address for UDP transport.
iface_cfg = {"name": iface, "enabled": True}
if profile == "ieee1588":
iface_cfg["ipv4"] = {"address": [{"ip": ip, "prefix-length": 30}]}
config["ietf-interfaces"] = {
"interfaces": {"interface": [iface_cfg]}
}
# Fast timers: 250 ms announce/sync intervals speed up port state transitions
# and convergence compared to the 1 s defaults.
port_ds = {
"log-announce-interval": -2,
"announce-receipt-timeout": 2,
"log-sync-interval": -2,
}
if profile == "ieee1588":
port_ds["delay-mechanism"] = "e2e"
config["ieee1588-ptp-tt"]["ptp"]["instances"]["instance"][0] \
["ports"]["port"][0]["port-ds"] = port_ds
return config
with infamy.Test() as test:
with test.step("Set up topology and attach to DUTs"):
arg = ArgumentParser()
env = infamy.Env(args=arg)
profile = env.args.profile
gm = env.attach("gm", "mgmt")
receiver = env.attach("receiver", "mgmt")
_, gm_iface = env.ltop.xlate("gm", "data")
_, receiver_iface = env.ltop.xlate("receiver", "data")
threshold_ns = env.args.threshold_ns or ptp.default_threshold(env, "receiver")
with test.step(f"Configure grandmaster (OC, {profile}, priority1=1) and time receiver ({profile}, priority1=128, client-only)"):
gm.put_config_dicts(configure_oc(gm_iface, priority1=1,
client_only=False, profile=profile,
ip="192.168.100.1"))
receiver.put_config_dicts(configure_oc(receiver_iface, priority1=128,
client_only=True, profile=profile,
ip="192.168.100.2"))
with test.step("Wait for grandmaster and time receiver ports to reach active states"):
parallel(lambda: until(lambda: ptp.is_time_transmitter(gm), attempts=60),
lambda: until(lambda: ptp.is_time_receiver(receiver), attempts=60))
with test.step("Wait for time receiver offset to converge"):
until(lambda: ptp.has_converged(receiver, threshold_ns), attempts=120)
test.succeed()
+11
View File
@@ -0,0 +1,11 @@
---
- settings:
test-spec: <case>.adoc
- name: PTP basic (IEEE 1588)
case: ieee1588.py
opts: ["--profile", "ieee1588"]
- name: PTP basic (IEEE 802.1AS)
case: ieee802dot1as.py
opts: ["--profile", "ieee802-dot1as"]
+33
View File
@@ -0,0 +1,33 @@
graph "ptp-basic" {
layout="neato";
overlap="false";
esep="+22";
node [shape=record, fontname="DejaVu Sans Mono, Book"];
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
host [
label="{ <mgmt1> mgmt1 | <host> \n\nhost\n\n\n | <mgmt2> mgmt2 }",
pos="0,15!",
requires="controller",
];
gm [
label="{ <mgmt> mgmt | <data> data } | { gm\npriority1=1 }",
pos="2,15.25!",
fontsize=12,
requires="infix",
];
receiver [
label="{ <data> data | <mgmt> mgmt } | { receiver\npriority1=128 }",
pos="2,14.75!",
fontsize=12,
requires="infix",
];
host:mgmt1 -- gm:mgmt [requires="mgmt", color="lightgray"]
host:mgmt2 -- receiver:mgmt [requires="mgmt", color="lightgray"]
gm:data -- receiver:data [label="\n\n192.168.100.0/30 ", dir="both"]
}
+62
View File
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: ptp&#45;basic Pages: 1 -->
<svg width="510pt" height="149pt"
viewBox="0.00 0.00 509.54 149.01" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 145.01)">
<title>ptp&#45;basic</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-145.01 505.54,-145.01 505.54,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="black" points="0,-4 0,-137 58,-137 58,-4 0,-4"/>
<text text-anchor="middle" x="29" y="-121.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt1</text>
<polyline fill="none" stroke="black" points="0,-114 58,-114 "/>
<text text-anchor="middle" x="29" y="-66.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
<polyline fill="none" stroke="black" points="0,-27 58,-27 "/>
<text text-anchor="middle" x="29" y="-11.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt2</text>
</g>
<!-- gm -->
<g id="node2" class="node">
<title>gm</title>
<polygon fill="none" stroke="black" points="348.04,-98.51 348.04,-140.51 494.04,-140.51 494.04,-98.51 348.04,-98.51"/>
<text text-anchor="middle" x="371.54" y="-126.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt</text>
<polyline fill="none" stroke="black" points="348.04,-119.51 395.04,-119.51 "/>
<text text-anchor="middle" x="371.54" y="-105.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">data</text>
<polyline fill="none" stroke="black" points="395.04,-98.51 395.04,-140.51 "/>
<text text-anchor="middle" x="444.54" y="-122.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">gm</text>
<text text-anchor="middle" x="444.54" y="-109.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">priority1=1</text>
</g>
<!-- host&#45;&#45;gm -->
<g id="edge1" class="edge">
<title>host:mgmt1&#45;&#45;gm:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M58,-125.5C58,-125.5 348.04,-130.51 348.04,-130.51"/>
</g>
<!-- receiver -->
<g id="node3" class="node">
<title>receiver</title>
<polygon fill="none" stroke="black" points="340.54,-0.5 340.54,-42.5 501.54,-42.5 501.54,-0.5 340.54,-0.5"/>
<text text-anchor="middle" x="364.04" y="-28.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">data</text>
<polyline fill="none" stroke="black" points="340.54,-21.5 387.54,-21.5 "/>
<text text-anchor="middle" x="364.04" y="-7.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt</text>
<polyline fill="none" stroke="black" points="387.54,-0.5 387.54,-42.5 "/>
<text text-anchor="middle" x="444.54" y="-24.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">receiver</text>
<text text-anchor="middle" x="444.54" y="-11.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">priority1=128</text>
</g>
<!-- host&#45;&#45;receiver -->
<g id="edge2" class="edge">
<title>host:mgmt2&#45;&#45;receiver:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M58,-15.5C58,-15.5 340.04,-10.5 340.04,-10.5"/>
</g>
<!-- gm&#45;&#45;receiver -->
<g id="edge3" class="edge">
<title>gm:data&#45;&#45;receiver:data</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M369.79,-88.5C368.51,-78.3 366.55,-62.59 365.28,-52.43"/>
<polygon fill="cornflowerblue" stroke="cornflowerblue" stroke-width="2" points="366.33,-89.02 371.04,-98.51 373.27,-88.15 366.33,-89.02"/>
<polygon fill="cornflowerblue" stroke="cornflowerblue" stroke-width="2" points="368.75,-51.99 364.04,-42.5 361.81,-52.86 368.75,-51.99"/>
<text text-anchor="middle" x="299.53" y="-74.26" font-family="DejaVu Serif, Book" font-size="14.00">192.168.100.0/30 &#160;</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

+6
View File
@@ -0,0 +1,6 @@
include::ieee1588.adoc[]
<<<
include::ieee802dot1as.adoc[]
+39
View File
@@ -0,0 +1,39 @@
=== PTP BTCA grandmaster election (IEEE 1588)
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/bmca]
==== Description
Verify that the Best TimeTransmitter Clock Algorithm (BTCA) selects the clock
with the lowest `priority1` as grandmaster, and that a change of `priority1`
at runtime triggers a new election with the correct result.
Two Ordinary Clocks are connected back-to-back. Both announce themselves as
potential grandmasters. In round one, *alpha* holds `priority1=1` and wins
the election; *beta* (`priority1=128`) becomes the time receiver. In round
two, *alpha* is reconfigured to priority1=200 without restarting; the BTCA
re-runs and beta wins, becoming the new grandmaster. The test verifies that
alpha's `parent-ds` `grandmaster-identity` changes to beta's `clock-identity`,
confirming that the re-election is reflected in the operational datastore.
Announce intervals are reduced to 250 ms (`log-announce-interval -2`) and the
announce receipt timeout to 2 intervals (500 ms) to make re-election complete
in roughly one second rather than the default three.
The test is run for both IEEE 1588-2019 (UDP/IPv4, E2E) and IEEE 802.1AS
(Layer 2, P2P) profiles.
==== Topology
image::topology.svg[PTP BTCA grandmaster election (IEEE 1588) topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to DUTs
. Configure both DUTs (ieee1588); alpha has lower priority1
. Verify initial election: alpha is grandmaster, beta is time receiver
. Reconfigure alpha with worse priority1=200
. Verify beta wins re-election (is own grandmaster)
. Verify alpha tracks beta as grandmaster
+1
View File
@@ -0,0 +1 @@
test.py
+39
View File
@@ -0,0 +1,39 @@
=== PTP BTCA grandmaster election (IEEE 802.1AS)
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/bmca]
==== Description
Verify that the Best TimeTransmitter Clock Algorithm (BTCA) selects the clock
with the lowest `priority1` as grandmaster, and that a change of `priority1`
at runtime triggers a new election with the correct result.
Two Ordinary Clocks are connected back-to-back. Both announce themselves as
potential grandmasters. In round one, *alpha* holds `priority1=1` and wins
the election; *beta* (`priority1=128`) becomes the time receiver. In round
two, *alpha* is reconfigured to priority1=200 without restarting; the BTCA
re-runs and beta wins, becoming the new grandmaster. The test verifies that
alpha's `parent-ds` `grandmaster-identity` changes to beta's `clock-identity`,
confirming that the re-election is reflected in the operational datastore.
Announce intervals are reduced to 250 ms (`log-announce-interval -2`) and the
announce receipt timeout to 2 intervals (500 ms) to make re-election complete
in roughly one second rather than the default three.
The test is run for both IEEE 1588-2019 (UDP/IPv4, E2E) and IEEE 802.1AS
(Layer 2, P2P) profiles.
==== Topology
image::topology.svg[PTP BTCA grandmaster election (IEEE 802.1AS) topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to DUTs
. Configure both DUTs (ieee802-dot1as); alpha has lower priority1
. Verify initial election: alpha is grandmaster, beta is time receiver
. Reconfigure alpha with worse priority1=200
. Verify beta wins re-election (is own grandmaster)
. Verify alpha tracks beta as grandmaster
+1
View File
@@ -0,0 +1 @@
test.py
+5
View File
@@ -0,0 +1,5 @@
include::ieee1588.adoc[]
<<<
include::ieee802dot1as.adoc[]
+113
View File
@@ -0,0 +1,113 @@
#!/usr/bin/env python3
"""PTP BTCA grandmaster election
Verify that the Best TimeTransmitter Clock Algorithm (BTCA) selects the clock
with the lowest `priority1` as grandmaster, and that a change of `priority1`
at runtime triggers a new election with the correct result.
Two Ordinary Clocks are connected back-to-back. Both announce themselves as
potential grandmasters. In round one, *alpha* holds `priority1=1` and wins
the election; *beta* (`priority1=128`) becomes the time receiver. In round
two, *alpha* is reconfigured to priority1=200 without restarting; the BTCA
re-runs and beta wins, becoming the new grandmaster. The test verifies that
alpha's `parent-ds` `grandmaster-identity` changes to beta's `clock-identity`,
confirming that the re-election is reflected in the operational datastore.
Announce intervals are reduced to 250 ms (`log-announce-interval -2`) and the
announce receipt timeout to 2 intervals (500 ms) to make re-election complete
in roughly one second rather than the default three.
The test is run for both IEEE 1588-2019 (UDP/IPv4, E2E) and IEEE 802.1AS
(Layer 2, P2P) profiles.
"""
import infamy
import infamy.ptp as ptp
from infamy import until
from infamy.util import parallel
class ArgumentParser(infamy.ArgumentParser):
def __init__(self):
super().__init__()
self.args.add_argument("--profile", default="ieee1588",
choices=["ieee1588", "ieee802-dot1as"])
def configure_oc(iface, priority1, profile, ip=None):
iface_cfg = {"name": iface, "enabled": True}
if profile == "ieee1588":
iface_cfg["ipv4"] = {"address": [{"ip": ip, "prefix-length": 30}]}
port_ds = {
"log-announce-interval": -2,
"announce-receipt-timeout": 2,
"log-sync-interval": -2,
}
if profile == "ieee1588":
port_ds["delay-mechanism"] = "e2e"
return {
"ietf-interfaces": {
"interfaces": {"interface": [iface_cfg]}
},
"ieee1588-ptp-tt": {
"ptp": {
"instances": {
"instance": [{
"instance-index": 0,
"default-ds": {
"instance-type": "oc",
"domain-number": 0,
"priority1": priority1,
"priority2": 128,
"infix-ptp:profile": profile,
"time-receiver-only": False,
},
"ports": {
"port": [{
"port-index": 1,
"underlying-interface": iface,
"port-ds": port_ds,
}]
}
}]
}
}
}
}
with infamy.Test() as test:
with test.step("Set up topology and attach to DUTs"):
arg = ArgumentParser()
env = infamy.Env(args=arg)
profile = env.args.profile
alpha = env.attach("alpha", "mgmt")
beta = env.attach("beta", "mgmt")
_, if_alpha = env.ltop.xlate("alpha", "data")
_, if_beta = env.ltop.xlate("beta", "data")
with test.step(f"Configure both DUTs ({profile}); alpha has lower priority1"):
alpha.put_config_dicts(configure_oc(if_alpha, priority1=1,
profile=profile, ip="192.168.100.1"))
beta.put_config_dicts(configure_oc(if_beta, priority1=128,
profile=profile, ip="192.168.100.2"))
with test.step("Verify initial election: alpha is grandmaster, beta is time receiver"):
parallel(lambda: until(lambda: ptp.is_own_gm(alpha), attempts=60),
lambda: until(lambda: ptp.is_time_receiver(beta), attempts=60))
with test.step("Reconfigure alpha with worse priority1=200"):
alpha.put_config_dicts(configure_oc(if_alpha, priority1=200,
profile=profile, ip="192.168.100.1"))
with test.step("Verify beta wins re-election (is own grandmaster)"):
until(lambda: ptp.is_own_gm(beta), attempts=30)
with test.step("Verify alpha tracks beta as grandmaster"):
until(lambda: ptp.grandmaster_identity(alpha) == ptp.clock_identity(beta),
attempts=30)
test.succeed()
+11
View File
@@ -0,0 +1,11 @@
---
- settings:
test-spec: <case>.adoc
- name: PTP BTCA grandmaster election (IEEE 1588)
case: ieee1588.py
opts: ["--profile", "ieee1588"]
- name: PTP BTCA grandmaster election (IEEE 802.1AS)
case: ieee802dot1as.py
opts: ["--profile", "ieee802-dot1as"]
+33
View File
@@ -0,0 +1,33 @@
graph "ptp-bmca" {
layout="neato";
overlap="false";
esep="+22";
node [shape=record, fontname="DejaVu Sans Mono, Book"];
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
host [
label="{ <mgmt1> mgmt1 | <host> \n\nhost\n\n\n | <mgmt2> mgmt2 }",
pos="0,15!",
requires="controller",
];
alpha [
label="{ <mgmt> mgmt | <data> data } | { alpha }",
pos="2,15.25!",
fontsize=12,
requires="infix",
];
beta [
label="{ <data> data | <mgmt> mgmt } | { beta }",
pos="2,14.75!",
fontsize=12,
requires="infix",
];
host:mgmt1 -- alpha:mgmt [requires="mgmt", color="lightgray"]
host:mgmt2 -- beta:mgmt [requires="mgmt", color="lightgray"]
alpha:data -- beta:data [label="192.168.101.0/30 ", dir="both"]
}
+60
View File
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: ptp&#45;bmca Pages: 1 -->
<svg width="480pt" height="149pt"
viewBox="0.00 0.00 479.54 149.01" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 145.01)">
<title>ptp&#45;bmca</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-145.01 475.54,-145.01 475.54,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="black" points="0,-4 0,-137 58,-137 58,-4 0,-4"/>
<text text-anchor="middle" x="29" y="-121.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt1</text>
<polyline fill="none" stroke="black" points="0,-114 58,-114 "/>
<text text-anchor="middle" x="29" y="-66.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
<polyline fill="none" stroke="black" points="0,-27 58,-27 "/>
<text text-anchor="middle" x="29" y="-11.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt2</text>
</g>
<!-- alpha -->
<g id="node2" class="node">
<title>alpha</title>
<polygon fill="none" stroke="black" points="370.54,-98.51 370.54,-140.51 471.54,-140.51 471.54,-98.51 370.54,-98.51"/>
<text text-anchor="middle" x="394.04" y="-126.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt</text>
<polyline fill="none" stroke="black" points="370.54,-119.51 417.54,-119.51 "/>
<text text-anchor="middle" x="394.04" y="-105.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">data</text>
<polyline fill="none" stroke="black" points="417.54,-98.51 417.54,-140.51 "/>
<text text-anchor="middle" x="444.54" y="-116.41" font-family="DejaVu Sans Mono, Book" font-size="12.00">alpha</text>
</g>
<!-- host&#45;&#45;alpha -->
<g id="edge1" class="edge">
<title>host:mgmt1&#45;&#45;alpha:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M58,-125.5C58,-125.5 370.04,-130.51 370.04,-130.51"/>
</g>
<!-- beta -->
<g id="node3" class="node">
<title>beta</title>
<polygon fill="none" stroke="black" points="374.04,-0.5 374.04,-42.5 468.04,-42.5 468.04,-0.5 374.04,-0.5"/>
<text text-anchor="middle" x="397.54" y="-28.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">data</text>
<polyline fill="none" stroke="black" points="374.04,-21.5 421.04,-21.5 "/>
<text text-anchor="middle" x="397.54" y="-7.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt</text>
<polyline fill="none" stroke="black" points="421.04,-0.5 421.04,-42.5 "/>
<text text-anchor="middle" x="444.54" y="-18.4" font-family="DejaVu Sans Mono, Book" font-size="12.00">beta</text>
</g>
<!-- host&#45;&#45;beta -->
<g id="edge2" class="edge">
<title>host:mgmt2&#45;&#45;beta:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M58,-15.5C58,-15.5 374.04,-10.5 374.04,-10.5"/>
</g>
<!-- alpha&#45;&#45;beta -->
<g id="edge3" class="edge">
<title>alpha:data&#45;&#45;beta:data</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M394.58,-88.5C395.11,-78.43 395.94,-62.98 396.49,-52.81"/>
<polygon fill="cornflowerblue" stroke="cornflowerblue" stroke-width="2" points="391.08,-88.34 394.04,-98.51 398.07,-88.71 391.08,-88.34"/>
<polygon fill="cornflowerblue" stroke="cornflowerblue" stroke-width="2" points="400,-52.67 397.04,-42.5 393.01,-52.3 400,-52.67"/>
<text text-anchor="middle" x="330.03" y="-74.45" font-family="DejaVu Serif, Book" font-size="14.00">192.168.101.0/30 </text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

+6
View File
@@ -0,0 +1,6 @@
include::ieee1588.adoc[]
<<<
include::ieee802dot1as.adoc[]
@@ -0,0 +1,41 @@
=== PTP boundary clock (IEEE 1588)
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/boundary_clock]
==== Description
Verify that a Boundary Clock (BC) correctly receives time on one port and
distributes it on another, and that the downstream time receiver sees exactly
one additional hop (`steps-removed=2`).
Three nodes are connected in a chain: a grandmaster Ordinary Clock (OC,
`priority1=1`), a Boundary Clock (BC, `priority1=64`) with two ports, and a
time-receiver Ordinary Clock (OC, `priority1=128`).
The BC's upstream port (toward the GM) must reach time-receiver state; the
downstream port (toward the time receiver) must reach time-transmitter state.
The time receiver's `steps-removed` counter must equal 2: the BC increments
`steps-removed` to 1 in the ANNOUNCE messages it forwards, and the time
receiver adds 1 more when it stores the value in its `currentDS`. An OC
directly connected to the GM shows 1, so the BC adds exactly one extra hop.
The test is run for both IEEE 1588-2019 (UDP/IPv4, E2E) and IEEE 802.1AS
(Layer 2, P2P) profiles.
==== Topology
image::topology.svg[PTP boundary clock (IEEE 1588) topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to DUTs
. Configure grandmaster (OC, ieee1588, priority1=1) and boundary clock (BC, ieee1588, priority1=64, two ports)
. Wait for BC uplink port to become time-receiver
. Wait for BC dnlink port to become time-transmitter
. Wait for boundary clock offset to converge
. Configure time receiver (OC, ieee1588, priority1=128, client-only)
. Wait for time receiver to reach time-receiver state
. Verify time receiver steps-removed equals 2 (one BC hop)
. Wait for time receiver offset to converge
+1
View File
@@ -0,0 +1 @@
test.py
@@ -0,0 +1,41 @@
=== PTP boundary clock (IEEE 802.1AS)
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/boundary_clock]
==== Description
Verify that a Boundary Clock (BC) correctly receives time on one port and
distributes it on another, and that the downstream time receiver sees exactly
one additional hop (`steps-removed=2`).
Three nodes are connected in a chain: a grandmaster Ordinary Clock (OC,
`priority1=1`), a Boundary Clock (BC, `priority1=64`) with two ports, and a
time-receiver Ordinary Clock (OC, `priority1=128`).
The BC's upstream port (toward the GM) must reach time-receiver state; the
downstream port (toward the time receiver) must reach time-transmitter state.
The time receiver's `steps-removed` counter must equal 2: the BC increments
`steps-removed` to 1 in the ANNOUNCE messages it forwards, and the time
receiver adds 1 more when it stores the value in its `currentDS`. An OC
directly connected to the GM shows 1, so the BC adds exactly one extra hop.
The test is run for both IEEE 1588-2019 (UDP/IPv4, E2E) and IEEE 802.1AS
(Layer 2, P2P) profiles.
==== Topology
image::topology.svg[PTP boundary clock (IEEE 802.1AS) topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to DUTs
. Configure grandmaster (OC, ieee802-dot1as, priority1=1) and boundary clock (BC, ieee802-dot1as, priority1=64, two ports)
. Wait for BC uplink port to become time-receiver
. Wait for BC dnlink port to become time-transmitter
. Wait for boundary clock offset to converge
. Configure time receiver (OC, ieee802-dot1as, priority1=128, client-only)
. Wait for time receiver to reach time-receiver state
. Verify time receiver steps-removed equals 2 (one BC hop)
. Wait for time receiver offset to converge
+1
View File
@@ -0,0 +1 @@
test.py
+5
View File
@@ -0,0 +1,5 @@
include::ieee1588.adoc[]
<<<
include::ieee802dot1as.adoc[]
+179
View File
@@ -0,0 +1,179 @@
#!/usr/bin/env python3
"""PTP boundary clock
Verify that a Boundary Clock (BC) correctly receives time on one port and
distributes it on another, and that the downstream time receiver sees exactly
one additional hop (`steps-removed=2`).
Three nodes are connected in a chain: a grandmaster Ordinary Clock (OC,
`priority1=1`), a Boundary Clock (BC, `priority1=64`) with two ports, and a
time-receiver Ordinary Clock (OC, `priority1=128`).
The BC's upstream port (toward the GM) must reach time-receiver state; the
downstream port (toward the time receiver) must reach time-transmitter state.
The time receiver's `steps-removed` counter must equal 2: the BC increments
`steps-removed` to 1 in the ANNOUNCE messages it forwards, and the time
receiver adds 1 more when it stores the value in its `currentDS`. An OC
directly connected to the GM shows 1, so the BC adds exactly one extra hop.
The test is run for both IEEE 1588-2019 (UDP/IPv4, E2E) and IEEE 802.1AS
(Layer 2, P2P) profiles.
"""
import infamy
import infamy.ptp as ptp
from infamy import until
class ArgumentParser(infamy.ArgumentParser):
def __init__(self):
super().__init__()
self.args.add_argument("--profile", default="ieee1588",
choices=["ieee1588", "ieee802-dot1as"])
self.args.add_argument("--threshold-ns", type=int, default=None)
def configure_oc(iface, priority1, profile, client_only=False, ip=None):
iface_cfg = {"name": iface, "enabled": True}
if profile == "ieee1588":
iface_cfg["ipv4"] = {"address": [{"ip": ip, "prefix-length": 30}]}
port_ds = {
"log-announce-interval": -2,
"announce-receipt-timeout": 2,
"log-sync-interval": -2,
}
if profile == "ieee1588":
port_ds["delay-mechanism"] = "e2e"
return {
"ietf-interfaces": {
"interfaces": {"interface": [iface_cfg]}
},
"ieee1588-ptp-tt": {
"ptp": {
"instances": {
"instance": [{
"instance-index": 0,
"default-ds": {
"instance-type": "oc",
"domain-number": 0,
"priority1": priority1,
"priority2": 128,
"infix-ptp:profile": profile,
"time-receiver-only": client_only,
},
"ports": {
"port": [{
"port-index": 1,
"underlying-interface": iface,
"port-ds": port_ds,
}]
}
}]
}
}
}
}
def configure_bc(uplink_iface, dnlink_iface, profile,
uplink_ip=None, dnlink_ip=None, priority1=64):
ifaces = [{"name": uplink_iface, "enabled": True},
{"name": dnlink_iface, "enabled": True}]
if profile == "ieee1588":
ifaces[0]["ipv4"] = {"address": [{"ip": uplink_ip, "prefix-length": 30}]}
ifaces[1]["ipv4"] = {"address": [{"ip": dnlink_ip, "prefix-length": 30}]}
port_ds = {
"log-announce-interval": -2,
"announce-receipt-timeout": 2,
"log-sync-interval": -2,
}
if profile == "ieee1588":
port_ds["delay-mechanism"] = "e2e"
return {
"ietf-interfaces": {
"interfaces": {"interface": ifaces}
},
"ieee1588-ptp-tt": {
"ptp": {
"instances": {
"instance": [{
"instance-index": 0,
"default-ds": {
"instance-type": "bc",
"domain-number": 0,
"priority1": priority1,
"priority2": 128,
"infix-ptp:profile": profile,
},
"ports": {
"port": [
{
"port-index": 1,
"underlying-interface": uplink_iface,
"port-ds": port_ds,
},
{
"port-index": 2,
"underlying-interface": dnlink_iface,
"port-ds": port_ds,
}
]
}
}]
}
}
}
}
with infamy.Test() as test:
with test.step("Set up topology and attach to DUTs"):
arg = ArgumentParser()
env = infamy.Env(args=arg)
profile = env.args.profile
gm = env.attach("gm", "mgmt")
bc = env.attach("bc", "mgmt")
receiver = env.attach("receiver", "mgmt")
_, gm_iface = env.ltop.xlate("gm", "data")
_, bc_uplink = env.ltop.xlate("bc", "uplink")
_, bc_dnlink = env.ltop.xlate("bc", "dnlink")
_, recv_iface = env.ltop.xlate("receiver", "data")
threshold_ns = env.args.threshold_ns or ptp.default_threshold(env, "receiver", hops=2)
with test.step(f"Configure grandmaster (OC, {profile}, priority1=1) and boundary clock (BC, {profile}, priority1=64, two ports)"):
gm.put_config_dicts(configure_oc(gm_iface, priority1=1,
profile=profile, ip="192.168.100.1"))
bc.put_config_dicts(configure_bc(bc_uplink, bc_dnlink, profile=profile,
uplink_ip="192.168.100.2",
dnlink_ip="192.168.101.1"))
with test.step("Wait for BC uplink port to become time-receiver"):
until(lambda: ptp.is_time_receiver(bc, port_idx=1), attempts=60)
with test.step("Wait for BC dnlink port to become time-transmitter"):
until(lambda: ptp.is_time_transmitter(bc, port_idx=2), attempts=60)
with test.step("Wait for boundary clock offset to converge"):
bc_threshold_ns = env.args.threshold_ns or ptp.default_threshold(env, "bc", hops=1)
until(lambda: ptp.has_converged(bc, bc_threshold_ns), attempts=120)
with test.step(f"Configure time receiver (OC, {profile}, priority1=128, client-only)"):
receiver.put_config_dicts(configure_oc(recv_iface, priority1=128,
profile=profile, client_only=True,
ip="192.168.101.2"))
with test.step("Wait for time receiver to reach time-receiver state"):
until(lambda: ptp.is_time_receiver(receiver), attempts=60)
with test.step("Verify time receiver steps-removed equals 2 (one BC hop)"):
until(lambda: ptp.steps_removed(receiver) == 2, attempts=30)
with test.step("Wait for time receiver offset to converge"):
until(lambda: ptp.has_converged(receiver, threshold_ns), attempts=120)
test.succeed()
+11
View File
@@ -0,0 +1,11 @@
---
- settings:
test-spec: <case>.adoc
- name: PTP boundary clock (IEEE 1588)
case: ieee1588.py
opts: ["--profile", "ieee1588"]
- name: PTP boundary clock (IEEE 802.1AS)
case: ieee802dot1as.py
opts: ["--profile", "ieee802-dot1as"]
+42
View File
@@ -0,0 +1,42 @@
graph "ptp-boundary-clock" {
layout="neato";
overlap="false";
esep="+22";
node [shape=record, fontname="DejaVu Sans Mono, Book"];
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
host [
label="{ <mgmt1> mgmt1 | <host> \n\nhost\n\n | <mgmt2> mgmt2 | <mgmt3> mgmt3 }",
pos="0,15!",
requires="controller",
];
gm [
label="{ <mgmt> mgmt | <data> data } | { gm\npriority1=1 }",
pos="2,15.5!",
fontsize=12,
requires="infix",
];
bc [
label="{ <uplink> uplink | <mgmt> mgmt | <dnlink> dnlink } | { bc\npriority1=64 }",
pos="2,15!",
fontsize=12,
requires="infix",
];
receiver [
label="{ <data> data | <mgmt> mgmt } | { receiver\npriority1=128 }",
pos="2,14.5!",
fontsize=12,
requires="infix",
];
host:mgmt1 -- gm:mgmt [requires="mgmt", color="lightgray"]
host:mgmt2 -- bc:mgmt [requires="mgmt", color="lightgray"]
host:mgmt3 -- receiver:mgmt [requires="mgmt", color="lightgray"]
gm:data -- bc:uplink [label="192.168.102.0/30 ", dir="both"]
bc:dnlink -- receiver:data [label="192.168.103.0/30 ", dir="both"]
}
+90
View File
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: ptp&#45;boundary&#45;clock Pages: 1 -->
<svg width="556pt" height="268pt"
viewBox="0.00 0.00 555.54 268.02" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 264.02)">
<title>ptp&#45;boundary&#45;clock</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-264.02 551.54,-264.02 551.54,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="black" points="0,-60.01 0,-200.01 58,-200.01 58,-60.01 0,-60.01"/>
<text text-anchor="middle" x="29" y="-184.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt1</text>
<polyline fill="none" stroke="black" points="0,-177.01 58,-177.01 "/>
<text text-anchor="middle" x="29" y="-129.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
<polyline fill="none" stroke="black" points="0,-106.01 58,-106.01 "/>
<text text-anchor="middle" x="29" y="-90.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt2</text>
<polyline fill="none" stroke="black" points="0,-83.01 58,-83.01 "/>
<text text-anchor="middle" x="29" y="-67.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt3</text>
</g>
<!-- gm -->
<g id="node2" class="node">
<title>gm</title>
<polygon fill="none" stroke="black" points="390.04,-217.52 390.04,-259.52 536.04,-259.52 536.04,-217.52 390.04,-217.52"/>
<text text-anchor="middle" x="413.54" y="-245.92" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt</text>
<polyline fill="none" stroke="black" points="390.04,-238.52 437.04,-238.52 "/>
<text text-anchor="middle" x="413.54" y="-224.92" font-family="DejaVu Sans Mono, Book" font-size="12.00">data</text>
<polyline fill="none" stroke="black" points="437.04,-217.52 437.04,-259.52 "/>
<text text-anchor="middle" x="486.54" y="-241.92" font-family="DejaVu Sans Mono, Book" font-size="12.00">gm</text>
<text text-anchor="middle" x="486.54" y="-228.92" font-family="DejaVu Sans Mono, Book" font-size="12.00">priority1=1</text>
</g>
<!-- host&#45;&#45;gm -->
<g id="edge1" class="edge">
<title>host:mgmt1&#45;&#45;gm:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M58,-189.01C58,-189.01 390.04,-249.52 390.04,-249.52"/>
</g>
<!-- bc -->
<g id="node3" class="node">
<title>bc</title>
<polygon fill="none" stroke="black" points="378.54,-98.51 378.54,-161.51 547.54,-161.51 547.54,-98.51 378.54,-98.51"/>
<text text-anchor="middle" x="409.54" y="-147.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">uplink</text>
<polyline fill="none" stroke="black" points="378.54,-140.51 440.54,-140.51 "/>
<text text-anchor="middle" x="409.54" y="-126.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt</text>
<polyline fill="none" stroke="black" points="378.54,-119.51 440.54,-119.51 "/>
<text text-anchor="middle" x="409.54" y="-105.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">dnlink</text>
<polyline fill="none" stroke="black" points="440.54,-98.51 440.54,-161.51 "/>
<text text-anchor="middle" x="494.04" y="-133.41" font-family="DejaVu Sans Mono, Book" font-size="12.00">bc</text>
<text text-anchor="middle" x="494.04" y="-120.41" font-family="DejaVu Sans Mono, Book" font-size="12.00">priority1=64</text>
</g>
<!-- host&#45;&#45;bc -->
<g id="edge2" class="edge">
<title>host:mgmt2&#45;&#45;bc:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M58,-94.01C58,-94.01 378.04,-130.01 378.04,-130.01"/>
</g>
<!-- receiver -->
<g id="node4" class="node">
<title>receiver</title>
<polygon fill="none" stroke="black" points="382.54,-0.5 382.54,-42.5 543.54,-42.5 543.54,-0.5 382.54,-0.5"/>
<text text-anchor="middle" x="406.04" y="-28.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">data</text>
<polyline fill="none" stroke="black" points="382.54,-21.5 429.54,-21.5 "/>
<text text-anchor="middle" x="406.04" y="-7.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt</text>
<polyline fill="none" stroke="black" points="429.54,-0.5 429.54,-42.5 "/>
<text text-anchor="middle" x="486.54" y="-24.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">receiver</text>
<text text-anchor="middle" x="486.54" y="-11.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">priority1=128</text>
</g>
<!-- host&#45;&#45;receiver -->
<g id="edge3" class="edge">
<title>host:mgmt3&#45;&#45;receiver:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M58,-71.01C58,-71.01 382.04,-10.5 382.04,-10.5"/>
</g>
<!-- gm&#45;&#45;bc -->
<g id="edge4" class="edge">
<title>gm:data&#45;&#45;bc:uplink</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M412.31,-207.34C411.59,-197.32 410.49,-182.12 409.77,-172.13"/>
<polygon fill="cornflowerblue" stroke="cornflowerblue" stroke-width="2" points="408.83,-207.8 413.04,-217.52 415.82,-207.3 408.83,-207.8"/>
<polygon fill="cornflowerblue" stroke="cornflowerblue" stroke-width="2" points="413.25,-171.73 409.04,-162.01 406.27,-172.24 413.25,-171.73"/>
<text text-anchor="middle" x="345.54" y="-193.53" font-family="DejaVu Serif, Book" font-size="14.00">192.168.102.0/30 </text>
</g>
<!-- bc&#45;&#45;receiver -->
<g id="edge5" class="edge">
<title>bc:dnlink&#45;&#45;receiver:data</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M408.49,-87.83C407.95,-77.81 407.13,-62.61 406.59,-52.62"/>
<polygon fill="cornflowerblue" stroke="cornflowerblue" stroke-width="2" points="405.01,-88.21 409.04,-98.01 412,-87.84 405.01,-88.21"/>
<polygon fill="cornflowerblue" stroke="cornflowerblue" stroke-width="2" points="410.08,-52.3 406.04,-42.5 403.09,-52.67 410.08,-52.3"/>
<text text-anchor="middle" x="342.04" y="-74.02" font-family="DejaVu Serif, Book" font-size="14.00">192.168.103.0/30 </text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

+1
View File
@@ -0,0 +1 @@
test.adoc
+32
View File
@@ -0,0 +1,32 @@
=== PTP port fault recovery
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/port_recovery]
==== Description
Verify that the PTP port state machine correctly detects a link fault
and recovers to time-receiver state once the link is restored.
Two Ordinary Clocks are connected back-to-back. Once the time receiver
has converged, the grandmaster's data interface is disabled. The time
receiver must leave time-receiver state within a short timeout. When
the interface is re-enabled, the time receiver must return to
time-receiver state and its offset must converge again to within the
configured threshold.
==== Topology
image::topology.svg[PTP port fault recovery topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to DUTs
. Configure grandmaster (priority1=1) and time receiver (client-only)
. Wait for initial convergence
. Disable grandmaster data interface to trigger fault
. Verify time receiver leaves time-receiver state
. Re-enable grandmaster data interface
. Wait for time receiver to return to time-receiver state after recovery
. Wait for offset to re-converge
+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
"""PTP port fault recovery
Verify that the PTP port state machine correctly detects a link fault
and recovers to time-receiver state once the link is restored.
Two Ordinary Clocks are connected back-to-back. Once the time receiver
has converged, the grandmaster's data interface is disabled. The time
receiver must leave time-receiver state within a short timeout. When
the interface is re-enabled, the time receiver must return to
time-receiver state and its offset must converge again to within the
configured threshold.
"""
import infamy
import infamy.ptp as ptp
from infamy import until
def configure_oc(iface, ip, priority1, client_only, dm="e2e"):
return {
"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": iface,
"enabled": True,
"ipv4": {
"address": [{"ip": ip, "prefix-length": 30}]
}
}]
}
},
"ieee1588-ptp-tt": {
"ptp": {
"instances": {
"instance": [{
"instance-index": 0,
"default-ds": {
"instance-type": "oc",
"domain-number": 0,
"priority1": priority1,
"priority2": 128,
"infix-ptp:profile": "ieee1588",
"time-receiver-only": client_only,
},
"ports": {
"port": [{
"port-index": 1,
"underlying-interface": iface,
"port-ds": {
"delay-mechanism": dm,
"log-announce-interval": -2,
"announce-receipt-timeout": 2,
"log-sync-interval": -2,
}
}]
}
}]
}
}
}
}
def set_iface_enabled(target, iface, enabled):
target.put_config_dict("ietf-interfaces", {
"interfaces": {
"interface": [{
"name": iface,
"enabled": enabled,
}]
}
})
class ArgumentParser(infamy.ArgumentParser):
def __init__(self):
super().__init__()
self.args.add_argument("--threshold-ns", type=int, default=None)
with infamy.Test() as test:
with test.step("Set up topology and attach to DUTs"):
arg = ArgumentParser()
env = infamy.Env(args=arg)
gm = env.attach("gm", "mgmt")
receiver = env.attach("receiver", "mgmt")
_, gm_iface = env.ltop.xlate("gm", "data")
_, receiver_iface = env.ltop.xlate("receiver", "data")
threshold_ns = env.args.threshold_ns or ptp.default_threshold(env, "receiver")
with test.step("Configure grandmaster (priority1=1) and time receiver (client-only)"):
gm.put_config_dicts(configure_oc(gm_iface, "192.168.100.1",
priority1=1, client_only=False))
receiver.put_config_dicts(configure_oc(receiver_iface, "192.168.100.2",
priority1=128, client_only=True))
with test.step("Wait for initial convergence"):
until(lambda: ptp.is_time_receiver(receiver) and ptp.has_converged(receiver, threshold_ns),
attempts=120)
with test.step("Disable grandmaster data interface to trigger fault"):
set_iface_enabled(gm, gm_iface, False)
with test.step("Verify time receiver leaves time-receiver state"):
until(lambda: not ptp.is_time_receiver(receiver), attempts=30)
with test.step("Re-enable grandmaster data interface"):
set_iface_enabled(gm, gm_iface, True)
with test.step("Wait for time receiver to return to time-receiver state after recovery"):
until(lambda: ptp.is_time_receiver(receiver), attempts=120)
with test.step("Wait for offset to re-converge"):
until(lambda: ptp.has_converged(receiver, threshold_ns), attempts=120)
test.succeed()
+33
View File
@@ -0,0 +1,33 @@
graph "ptp-port-recovery" {
layout="neato";
overlap="false";
esep="+22";
node [shape=record, fontname="DejaVu Sans Mono, Book"];
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
host [
label="{ <mgmt1> mgmt1 | <host> \n\nhost\n\n\n | <mgmt2> mgmt2 }",
pos="0,15!",
requires="controller",
];
gm [
label="{ <mgmt> mgmt | <data> data } | { gm\npriority1=1 }",
pos="2,15.25!",
fontsize=12,
requires="infix",
];
receiver [
label="{ <data> data | <mgmt> mgmt } | { receiver\npriority1=128 }",
pos="2,14.75!",
fontsize=12,
requires="infix",
];
host:mgmt1 -- gm:mgmt [requires="mgmt", color="lightgray"]
host:mgmt2 -- receiver:mgmt [requires="mgmt", color="lightgray"]
gm:data -- receiver:data [label="\n\n192.168.106.0/30 ", dir="both"]
}
+62
View File
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: ptp&#45;port&#45;recovery Pages: 1 -->
<svg width="510pt" height="149pt"
viewBox="0.00 0.00 509.54 149.01" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 145.01)">
<title>ptp&#45;port&#45;recovery</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-145.01 505.54,-145.01 505.54,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="black" points="0,-4 0,-137 58,-137 58,-4 0,-4"/>
<text text-anchor="middle" x="29" y="-121.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt1</text>
<polyline fill="none" stroke="black" points="0,-114 58,-114 "/>
<text text-anchor="middle" x="29" y="-66.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
<polyline fill="none" stroke="black" points="0,-27 58,-27 "/>
<text text-anchor="middle" x="29" y="-11.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt2</text>
</g>
<!-- gm -->
<g id="node2" class="node">
<title>gm</title>
<polygon fill="none" stroke="black" points="348.04,-98.51 348.04,-140.51 494.04,-140.51 494.04,-98.51 348.04,-98.51"/>
<text text-anchor="middle" x="371.54" y="-126.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt</text>
<polyline fill="none" stroke="black" points="348.04,-119.51 395.04,-119.51 "/>
<text text-anchor="middle" x="371.54" y="-105.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">data</text>
<polyline fill="none" stroke="black" points="395.04,-98.51 395.04,-140.51 "/>
<text text-anchor="middle" x="444.54" y="-122.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">gm</text>
<text text-anchor="middle" x="444.54" y="-109.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">priority1=1</text>
</g>
<!-- host&#45;&#45;gm -->
<g id="edge1" class="edge">
<title>host:mgmt1&#45;&#45;gm:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M58,-125.5C58,-125.5 348.04,-130.51 348.04,-130.51"/>
</g>
<!-- receiver -->
<g id="node3" class="node">
<title>receiver</title>
<polygon fill="none" stroke="black" points="340.54,-0.5 340.54,-42.5 501.54,-42.5 501.54,-0.5 340.54,-0.5"/>
<text text-anchor="middle" x="364.04" y="-28.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">data</text>
<polyline fill="none" stroke="black" points="340.54,-21.5 387.54,-21.5 "/>
<text text-anchor="middle" x="364.04" y="-7.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt</text>
<polyline fill="none" stroke="black" points="387.54,-0.5 387.54,-42.5 "/>
<text text-anchor="middle" x="444.54" y="-24.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">receiver</text>
<text text-anchor="middle" x="444.54" y="-11.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">priority1=128</text>
</g>
<!-- host&#45;&#45;receiver -->
<g id="edge2" class="edge">
<title>host:mgmt2&#45;&#45;receiver:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M58,-15.5C58,-15.5 340.04,-10.5 340.04,-10.5"/>
</g>
<!-- gm&#45;&#45;receiver -->
<g id="edge3" class="edge">
<title>gm:data&#45;&#45;receiver:data</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M369.79,-88.5C368.51,-78.3 366.55,-62.59 365.28,-52.43"/>
<polygon fill="cornflowerblue" stroke="cornflowerblue" stroke-width="2" points="366.33,-89.02 371.04,-98.51 373.27,-88.15 366.33,-89.02"/>
<polygon fill="cornflowerblue" stroke="cornflowerblue" stroke-width="2" points="368.75,-51.99 364.04,-42.5 361.81,-52.86 368.75,-51.99"/>
<text text-anchor="middle" x="299.53" y="-74.26" font-family="DejaVu Serif, Book" font-size="14.00">192.168.106.0/30 &#160;</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

+1
View File
@@ -0,0 +1 @@
test.adoc
+44
View File
@@ -0,0 +1,44 @@
=== PTP servo step-threshold
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/servo]
==== Description
Verify that configuring a non-zero `step-threshold` allows the clock servo
to correct a large time offset by stepping rather than slewing.
Two Ordinary Clocks are connected back-to-back using the IEEE 1588 profile.
After initial convergence the receiver is reconfigured with
`step-threshold=1.0 s` and ptp4l restarts. Because the offset at restart
is near zero, `first_step_threshold` (ptp4l's per-startup step gate) does
not trigger, so the restart itself is convergence-neutral.
Once the receiver has re-locked, the grandmaster clock is stepped forward by
10 seconds using phc_ctl (hardware PHC) or the system clock (software
timestamping). The 10-second offset exceeds the 1-second step-threshold, so
the servo steps the clock immediately and the receiver converges within a
few seconds.
Note: a negative test (verify that offset=10 s does *not* converge without
step-threshold) is not included here because it is unreliable across
platforms. On physical hardware the kernel caps clock frequency adjustment
at ~500 ppm, making a 10-second slew take ~5.5 hours; on virtual clocks
(QEMU) no such limit applies and the servo can slew the offset away in
seconds. Full negative coverage requires exposing first_step_threshold and
max_frequency in the YANG model — see TODO.org.
==== Topology
image::topology.svg[PTP servo step-threshold topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to DUTs
. Configure grandmaster (OC, IEEE 1588, priority1=1) and time receiver
. Wait for grandmaster and time receiver ports to reach active states
. Wait for initial convergence
. Reconfigure receiver with step-threshold=1.0 s
. Inject {STEP_SEC}-second offset on grandmaster clock
. Verify receiver converges by stepping (step-threshold=1.0 s)
+154
View File
@@ -0,0 +1,154 @@
#!/usr/bin/env python3
"""PTP servo step-threshold
Verify that configuring a non-zero `step-threshold` allows the clock servo
to correct a large time offset by stepping rather than slewing.
Two Ordinary Clocks are connected back-to-back using the IEEE 1588 profile.
After initial convergence the receiver is reconfigured with
`step-threshold=1.0 s` and ptp4l restarts. Because the offset at restart
is near zero, `first_step_threshold` (ptp4l's per-startup step gate) does
not trigger, so the restart itself is convergence-neutral.
Once the receiver has re-locked, the grandmaster clock is stepped forward by
10 seconds using phc_ctl (hardware PHC) or the system clock (software
timestamping). The 10-second offset exceeds the 1-second step-threshold, so
the servo steps the clock immediately and the receiver converges within a
few seconds.
Note: a negative test (verify that offset=10 s does *not* converge without
step-threshold) is not included here because it is unreliable across
platforms. On physical hardware the kernel caps clock frequency adjustment
at ~500 ppm, making a 10-second slew take ~5.5 hours; on virtual clocks
(QEMU) no such limit applies and the servo can slew the offset away in
seconds. Full negative coverage requires exposing first_step_threshold and
max_frequency in the YANG model — see TODO.org.
"""
import infamy
import infamy.ptp as ptp
from infamy import until
from infamy.util import parallel
STEP_SEC = 10
class ArgumentParser(infamy.ArgumentParser):
def __init__(self):
super().__init__()
self.args.add_argument("--threshold-ns", type=int, default=None)
def configure_oc(iface, priority1, client, ip, step_threshold=None):
config = {
"ieee1588-ptp-tt": {
"ptp": {
"instances": {
"instance": [{
"instance-index": 0,
"default-ds": {
"instance-type": "oc",
"domain-number": 0,
"priority1": priority1,
"priority2": 128,
"infix-ptp:profile": "ieee1588",
"time-receiver-only": client,
},
"ports": {
"port": [{
"port-index": 1,
"underlying-interface": iface,
}]
}
}]
}
}
}
}
iface_cfg = {"name": iface, "enabled": True,
"ipv4": {"address": [{"ip": ip, "prefix-length": 30}]}}
config["ietf-interfaces"] = {"interfaces": {"interface": [iface_cfg]}}
port_ds = {
"log-announce-interval": -2,
"announce-receipt-timeout": 2,
"log-sync-interval": -2,
"delay-mechanism": "e2e",
}
inst = config["ieee1588-ptp-tt"]["ptp"]["instances"]["instance"][0]
inst["ports"]["port"][0]["port-ds"] = port_ds
if step_threshold is not None:
inst["infix-ptp:servo"] = {"step-threshold": str(step_threshold)}
return config
def step_clock(ssh, iface, seconds):
"""Step the PTP clock on the node owning iface forward by seconds.
Uses phc_ctl on the hardware PHC when available (sysfs discovery);
falls back to date(1) for software-timestamping nodes where ptp4l
disciplines CLOCK_REALTIME directly.
"""
rc = ssh.runsh(f"ls /sys/class/net/{iface}/device/ptp/ 2>/dev/null")
phc = rc.stdout.strip().split()[0] if rc.returncode == 0 and rc.stdout.strip() else None
if phc:
ssh.runsh(f"phc_ctl /dev/{phc} adj {float(seconds)}")
else:
ssh.runsh(f"date -s @$(( $(date +%s) + {seconds} ))")
with infamy.Test() as test:
with test.step("Set up topology and attach to DUTs"):
arg = ArgumentParser()
env = infamy.Env(args=arg)
gm = env.attach("gm", "mgmt")
receiver = env.attach("receiver", "mgmt")
_, gm_iface = env.ltop.xlate("gm", "data")
_, receiver_iface = env.ltop.xlate("receiver", "data")
threshold_ns = env.args.threshold_ns or ptp.default_threshold(env, "receiver")
with test.step("Configure grandmaster (OC, IEEE 1588, priority1=1) and time receiver"):
gm.put_config_dicts(configure_oc(gm_iface, priority1=1,
client=False, ip="192.168.100.1"))
receiver.put_config_dicts(configure_oc(receiver_iface, priority1=128,
client=True, ip="192.168.100.2"))
with test.step("Wait for grandmaster and time receiver ports to reach active states"):
def gm_ready():
if not ptp.is_time_transmitter(gm):
# print(f"{ptp.port_state_dbg(gm)}")
return False
return True
def receiver_ready():
if not ptp.is_time_receiver(receiver):
# print(f"{ptp.port_state_dbg(receiver)}")
return False
return True
parallel(lambda: until(gm_ready, attempts=60),
lambda: until(receiver_ready, attempts=60))
with test.step("Wait for initial convergence"):
until(lambda: ptp.has_converged(receiver, threshold_ns), attempts=120)
with test.step("Reconfigure receiver with step-threshold=1.0 s"):
# ptp4l restarts while the offset is near zero so first_step_threshold
# does not trigger; the restart itself is convergence-neutral.
receiver.put_config_dicts(configure_oc(receiver_iface, priority1=128,
client=True, ip="192.168.100.2",
step_threshold=1.0))
until(lambda: ptp.has_converged(receiver, threshold_ns), attempts=60)
with test.step(f"Inject {STEP_SEC}-second offset on grandmaster clock"):
gmssh = env.attach("gm", "mgmt", "ssh")
step_clock(gmssh, gm_iface, STEP_SEC)
with test.step("Verify receiver converges by stepping (step-threshold=1.0 s)"):
until(lambda: ptp.has_converged(receiver, threshold_ns), attempts=60)
test.succeed()
+33
View File
@@ -0,0 +1,33 @@
graph "ptp-servo" {
layout="neato";
overlap="false";
esep="+22";
node [shape=record, fontname="DejaVu Sans Mono, Book"];
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
host [
label="{ <mgmt1> mgmt1 | <host> \n\nhost\n\n\n | <mgmt2> mgmt2 }",
pos="0,15!",
requires="controller",
];
gm [
label="{ <mgmt> mgmt | <data> data } | { gm\npriority1=1 }",
pos="2,15.25!",
fontsize=12,
requires="infix",
];
receiver [
label="{ <data> data | <mgmt> mgmt } | { receiver\npriority1=128 }",
pos="2,14.75!",
fontsize=12,
requires="infix",
];
host:mgmt1 -- gm:mgmt [requires="mgmt", color="lightgray"]
host:mgmt2 -- receiver:mgmt [requires="mgmt", color="lightgray"]
gm:data -- receiver:data [label="\n\n192.168.100.0/30 ", dir="both"]
}
+62
View File
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: ptp&#45;servo Pages: 1 -->
<svg width="510pt" height="149pt"
viewBox="0.00 0.00 509.54 149.01" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 145.01)">
<title>ptp&#45;servo</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-145.01 505.54,-145.01 505.54,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="black" points="0,-4 0,-137 58,-137 58,-4 0,-4"/>
<text text-anchor="middle" x="29" y="-121.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt1</text>
<polyline fill="none" stroke="black" points="0,-114 58,-114 "/>
<text text-anchor="middle" x="29" y="-66.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
<polyline fill="none" stroke="black" points="0,-27 58,-27 "/>
<text text-anchor="middle" x="29" y="-11.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt2</text>
</g>
<!-- gm -->
<g id="node2" class="node">
<title>gm</title>
<polygon fill="none" stroke="black" points="348.04,-98.51 348.04,-140.51 494.04,-140.51 494.04,-98.51 348.04,-98.51"/>
<text text-anchor="middle" x="371.54" y="-126.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt</text>
<polyline fill="none" stroke="black" points="348.04,-119.51 395.04,-119.51 "/>
<text text-anchor="middle" x="371.54" y="-105.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">data</text>
<polyline fill="none" stroke="black" points="395.04,-98.51 395.04,-140.51 "/>
<text text-anchor="middle" x="444.54" y="-122.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">gm</text>
<text text-anchor="middle" x="444.54" y="-109.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">priority1=1</text>
</g>
<!-- host&#45;&#45;gm -->
<g id="edge1" class="edge">
<title>host:mgmt1&#45;&#45;gm:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M58,-125.5C58,-125.5 348.04,-130.51 348.04,-130.51"/>
</g>
<!-- receiver -->
<g id="node3" class="node">
<title>receiver</title>
<polygon fill="none" stroke="black" points="340.54,-0.5 340.54,-42.5 501.54,-42.5 501.54,-0.5 340.54,-0.5"/>
<text text-anchor="middle" x="364.04" y="-28.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">data</text>
<polyline fill="none" stroke="black" points="340.54,-21.5 387.54,-21.5 "/>
<text text-anchor="middle" x="364.04" y="-7.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt</text>
<polyline fill="none" stroke="black" points="387.54,-0.5 387.54,-42.5 "/>
<text text-anchor="middle" x="444.54" y="-24.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">receiver</text>
<text text-anchor="middle" x="444.54" y="-11.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">priority1=128</text>
</g>
<!-- host&#45;&#45;receiver -->
<g id="edge2" class="edge">
<title>host:mgmt2&#45;&#45;receiver:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M58,-15.5C58,-15.5 340.04,-10.5 340.04,-10.5"/>
</g>
<!-- gm&#45;&#45;receiver -->
<g id="edge3" class="edge">
<title>gm:data&#45;&#45;receiver:data</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M369.79,-88.5C368.51,-78.3 366.55,-62.59 365.28,-52.43"/>
<polygon fill="cornflowerblue" stroke="cornflowerblue" stroke-width="2" points="366.33,-89.02 371.04,-98.51 373.27,-88.15 366.33,-89.02"/>
<polygon fill="cornflowerblue" stroke="cornflowerblue" stroke-width="2" points="368.75,-51.99 364.04,-42.5 361.81,-52.86 368.75,-51.99"/>
<text text-anchor="middle" x="299.53" y="-74.26" font-family="DejaVu Serif, Book" font-size="14.00">192.168.100.0/30 &#160;</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

@@ -0,0 +1,10 @@
include::e2e.adoc[]
<<<
include::p2p.adoc[]
<<<
include::ieee802dot1as.adoc[]
+43
View File
@@ -0,0 +1,43 @@
=== PTP transparent clock (E2E)
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/transparent_clock]
==== Description
Verify that an E2E or P2P Transparent Clock (TC) passes timing transparently
through a hardware switch without adding a boundary-clock hop, and that the
downstream time receiver converges to the grandmaster's time.
Three nodes are connected in a chain: a grandmaster Ordinary Clock
(`priority1=1`), a Transparent Clock, and a time-receiver Ordinary Clock
(`priority1=128`).
The TC updates the correction field in each Sync and Delay_Req message to
account for its own residence time. Because a TC is transparent, the time
receiver's `steps-removed` counter must equal 1 — unlike a Boundary Clock,
which would give 2. A TC passes ANNOUNCE messages unchanged (`stepsRemoved=0`
from the GM), and the time receiver adds 1 when it stores the value in
`currentDS`, giving a total of 1. A BC increments `stepsRemoved` to 1 before
forwarding, and the receiver adds 1 more, giving 2. The time receiver's offset must converge within the configured threshold
(default is tighter when the topology provides hardware timestamping links).
The delay mechanism (E2E or P2P) is controlled by the test suite for
IEEE 1588 runs. When the profile is IEEE 802.1AS the delay mechanism is
always P2P (mandated by the standard) and Layer 2 transport is used.
==== Topology
image::topology.svg[PTP transparent clock (E2E) topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to DUTs
. Configure grandmaster (OC, priority1=1, {dm})
. Configure transparent clock ({dm}-tc, {profile})
. Configure time receiver (OC, priority1=128, client-only)
. Wait for grandmaster port to become time-transmitter
. Wait for time receiver to reach time-receiver state
. Verify time receiver steps-removed equals 1
. Wait for time receiver offset to converge
+1
View File
@@ -0,0 +1 @@
test.py
@@ -0,0 +1,43 @@
=== PTP transparent clock (IEEE 802.1AS)
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/transparent_clock]
==== Description
Verify that an E2E or P2P Transparent Clock (TC) passes timing transparently
through a hardware switch without adding a boundary-clock hop, and that the
downstream time receiver converges to the grandmaster's time.
Three nodes are connected in a chain: a grandmaster Ordinary Clock
(`priority1=1`), a Transparent Clock, and a time-receiver Ordinary Clock
(`priority1=128`).
The TC updates the correction field in each Sync and Delay_Req message to
account for its own residence time. Because a TC is transparent, the time
receiver's `steps-removed` counter must equal 1 — unlike a Boundary Clock,
which would give 2. A TC passes ANNOUNCE messages unchanged (`stepsRemoved=0`
from the GM), and the time receiver adds 1 when it stores the value in
`currentDS`, giving a total of 1. A BC increments `stepsRemoved` to 1 before
forwarding, and the receiver adds 1 more, giving 2. The time receiver's offset must converge within the configured threshold
(default is tighter when the topology provides hardware timestamping links).
The delay mechanism (E2E or P2P) is controlled by the test suite for
IEEE 1588 runs. When the profile is IEEE 802.1AS the delay mechanism is
always P2P (mandated by the standard) and Layer 2 transport is used.
==== Topology
image::topology.svg[PTP transparent clock (IEEE 802.1AS) topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to DUTs
. Configure grandmaster (OC, priority1=1, {dm})
. Configure transparent clock ({dm}-tc, ieee802-dot1as)
. Configure time receiver (OC, priority1=128, client-only)
. Wait for grandmaster port to become time-transmitter
. Wait for time receiver to reach time-receiver state
. Verify time receiver steps-removed equals 1
. Wait for time receiver offset to converge
+1
View File
@@ -0,0 +1 @@
test.py
+43
View File
@@ -0,0 +1,43 @@
=== PTP transparent clock (P2P)
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/transparent_clock]
==== Description
Verify that an E2E or P2P Transparent Clock (TC) passes timing transparently
through a hardware switch without adding a boundary-clock hop, and that the
downstream time receiver converges to the grandmaster's time.
Three nodes are connected in a chain: a grandmaster Ordinary Clock
(`priority1=1`), a Transparent Clock, and a time-receiver Ordinary Clock
(`priority1=128`).
The TC updates the correction field in each Sync and Delay_Req message to
account for its own residence time. Because a TC is transparent, the time
receiver's `steps-removed` counter must equal 1 — unlike a Boundary Clock,
which would give 2. A TC passes ANNOUNCE messages unchanged (`stepsRemoved=0`
from the GM), and the time receiver adds 1 when it stores the value in
`currentDS`, giving a total of 1. A BC increments `stepsRemoved` to 1 before
forwarding, and the receiver adds 1 more, giving 2. The time receiver's offset must converge within the configured threshold
(default is tighter when the topology provides hardware timestamping links).
The delay mechanism (E2E or P2P) is controlled by the test suite for
IEEE 1588 runs. When the profile is IEEE 802.1AS the delay mechanism is
always P2P (mandated by the standard) and Layer 2 transport is used.
==== Topology
image::topology.svg[PTP transparent clock (P2P) topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to DUTs
. Configure grandmaster (OC, priority1=1, {dm})
. Configure transparent clock ({dm}-tc, {profile})
. Configure time receiver (OC, priority1=128, client-only)
. Wait for grandmaster port to become time-transmitter
. Wait for time receiver to reach time-receiver state
. Verify time receiver steps-removed equals 1
. Wait for time receiver offset to converge
+1
View File
@@ -0,0 +1 @@
test.py
+46
View File
@@ -0,0 +1,46 @@
=== PTP transparent clock (P2P)
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/transparent_clock]
==== Description
Verify that an E2E or P2P Transparent Clock (TC) passes timing
transparently through a hardware switch without adding a boundary-clock
hop, and that the downstream time receiver converges to the
grandmaster's time.
Three nodes are connected in a chain: a grandmaster Ordinary Clock
(priority1=1), a Transparent Clock with hardware timestamping, and a
time-receiver Ordinary Clock (priority1=128).
The TC updates the correction field in each Sync and Delay_Req message
to account for its own residence time. Because a TC is transparent,
the time receiver's steps-removed counter must equal 1 — unlike a
Boundary Clock, which would give 2. A TC passes ANNOUNCE messages
unchanged (stepsRemoved=0 from the GM), and the time receiver adds 1
when it stores the value in currentDS, giving a total of 1. A BC
increments stepsRemoved to 1 before forwarding, and the receiver adds
1 more, giving 2. The time receiver's offset must converge within the
tight threshold appropriate for hardware timestamping.
The delay mechanism (E2E or P2P) is injected via the --delay-mechanism
argument from the test suite YAML, allowing both variants to run from
the same test script. The TC node requires hardware PTP timestamping
support (capability: ptp-hwts).
==== Topology
image::topology.svg[PTP transparent clock (P2P) topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to DUTs
. Configure grandmaster (OC, priority1=1, {dm})
. Configure transparent clock ({dm.upper()}-TC)
. Configure time receiver (OC, priority1=128, client-only)
. Wait for grandmaster port to become time-transmitter
. Wait for time receiver to reach time-receiver state
. Verify time receiver steps-removed equals 1 (TC adds no boundary-clock hop)
. Wait for time receiver offset to converge
+175
View File
@@ -0,0 +1,175 @@
#!/usr/bin/env python3
"""PTP transparent clock
Verify that an E2E or P2P Transparent Clock (TC) passes timing transparently
through a hardware switch without adding a boundary-clock hop, and that the
downstream time receiver converges to the grandmaster's time.
Three nodes are connected in a chain: a grandmaster Ordinary Clock
(`priority1=1`), a Transparent Clock, and a time-receiver Ordinary Clock
(`priority1=128`).
The TC updates the correction field in each Sync and Delay_Req message to
account for its own residence time. Because a TC is transparent, the time
receiver's `steps-removed` counter must equal 1 — unlike a Boundary Clock,
which would give 2. A TC passes ANNOUNCE messages unchanged (`stepsRemoved=0`
from the GM), and the time receiver adds 1 when it stores the value in
`currentDS`, giving a total of 1. A BC increments `stepsRemoved` to 1 before
forwarding, and the receiver adds 1 more, giving 2. The time receiver's offset must converge within the configured threshold
(default is tighter when the topology provides hardware timestamping links).
The delay mechanism (E2E or P2P) is controlled by the test suite for
IEEE 1588 runs. When the profile is IEEE 802.1AS the delay mechanism is
always P2P (mandated by the standard) and Layer 2 transport is used.
"""
import infamy
import infamy.ptp as ptp
from infamy import until
class ArgumentParser(infamy.ArgumentParser):
def __init__(self):
super().__init__()
self.args.add_argument("--profile", default="ieee1588",
choices=["ieee1588", "ieee802-dot1as"])
self.args.add_argument("--delay-mechanism", default="e2e",
choices=["e2e", "p2p"])
self.args.add_argument("--threshold-ns", type=int, default=None)
def configure_oc(iface, priority1, profile, client_only=False, ip=None, dm="e2e"):
iface_cfg = {"name": iface, "enabled": True}
if profile == "ieee1588":
iface_cfg["ipv4"] = {"address": [{"ip": ip, "prefix-length": 30}]}
port_ds = {
"log-announce-interval": -2,
"announce-receipt-timeout": 2,
"log-sync-interval": -2,
}
if profile == "ieee1588":
port_ds["delay-mechanism"] = dm
return {
"ietf-interfaces": {
"interfaces": {"interface": [iface_cfg]}
},
"ieee1588-ptp-tt": {
"ptp": {
"instances": {
"instance": [{
"instance-index": 0,
"default-ds": {
"instance-type": "oc",
"domain-number": 0,
"priority1": priority1,
"priority2": 128,
"infix-ptp:profile": profile,
"time-receiver-only": client_only,
},
"ports": {
"port": [{
"port-index": 1,
"underlying-interface": iface,
"port-ds": port_ds,
}]
}
}]
}
}
}
}
def configure_tc(uplink_iface, dnlink_iface, profile, dm="e2e",
uplink_ip=None, dnlink_ip=None):
if profile == "ieee802-dot1as":
instance_type = "p2p-tc"
else:
instance_type = "p2p-tc" if dm == "p2p" else "e2e-tc"
ifaces = [{"name": uplink_iface, "enabled": True},
{"name": dnlink_iface, "enabled": True}]
if profile == "ieee1588":
ifaces[0]["ipv4"] = {"address": [{"ip": uplink_ip, "prefix-length": 30}]}
ifaces[1]["ipv4"] = {"address": [{"ip": dnlink_ip, "prefix-length": 30}]}
return {
"ietf-interfaces": {
"interfaces": {"interface": ifaces}
},
"ieee1588-ptp-tt": {
"ptp": {
"instances": {
"instance": [{
"instance-index": 0,
"default-ds": {
"instance-type": instance_type,
"domain-number": 0,
"infix-ptp:profile": profile,
},
"ports": {
"port": [
{
"port-index": 1,
"underlying-interface": uplink_iface,
"port-ds": {"log-sync-interval": -2},
},
{
"port-index": 2,
"underlying-interface": dnlink_iface,
"port-ds": {"log-sync-interval": -2},
}
]
}
}]
}
}
}
}
with infamy.Test() as test:
with test.step("Set up topology and attach to DUTs"):
arg = ArgumentParser()
env = infamy.Env(args=arg)
profile = env.args.profile
dm = "p2p" if profile == "ieee802-dot1as" else env.args.delay_mechanism
gm = env.attach("gm", "mgmt")
tc = env.attach("tc", "mgmt")
receiver = env.attach("receiver", "mgmt")
gm_iface = gm["data"]
tc_uplink = tc["uplink"]
tc_dnlink = tc["dnlink"]
receiver_iface = receiver["data"]
threshold_ns = env.args.threshold_ns or ptp.default_threshold(env, "tc")
with test.step(f"Configure grandmaster (OC, priority1=1, {dm})"):
gm.put_config_dicts(configure_oc(gm_iface, priority1=1,
profile=profile, ip="192.168.100.1", dm=dm))
with test.step(f"Configure transparent clock ({dm}-tc, {profile})"):
tc.put_config_dicts(configure_tc(tc_uplink, tc_dnlink, profile=profile, dm=dm,
uplink_ip="192.168.100.2",
dnlink_ip="192.168.101.1"))
with test.step("Configure time receiver (OC, priority1=128, client-only)"):
receiver.put_config_dicts(configure_oc(receiver_iface, priority1=128,
profile=profile, client_only=True,
ip="192.168.101.2", dm=dm))
with test.step("Wait for grandmaster port to become time-transmitter"):
until(lambda: ptp.is_time_transmitter(gm), attempts=60)
with test.step("Wait for time receiver to reach time-receiver state"):
until(lambda: ptp.is_time_receiver(receiver), attempts=60)
with test.step("Verify time receiver steps-removed equals 1"):
until(lambda: ptp.steps_removed(receiver) == 1, attempts=60)
with test.step("Wait for time receiver offset to converge"):
until(lambda: ptp.has_converged(receiver, threshold_ns), attempts=180)
test.succeed()
+15
View File
@@ -0,0 +1,15 @@
---
- settings:
test-spec: <case>.adoc
- name: PTP transparent clock (E2E)
case: e2e.py
opts: ["--delay-mechanism", "e2e"]
- name: PTP transparent clock (P2P)
case: p2p.py
opts: ["--delay-mechanism", "p2p"]
- name: PTP transparent clock (IEEE 802.1AS)
case: ieee802dot1as.py
opts: ["--profile", "ieee802-dot1as"]
@@ -0,0 +1,42 @@
graph "ptp-transparent-clock" {
layout="neato";
overlap="false";
esep="+22";
node [shape=record, fontname="DejaVu Sans Mono, Book"];
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
host [
label="{ <mgmt1> mgmt1 | <host> \n\nhost\n\n | <mgmt2> mgmt2 | <mgmt3> mgmt3 }",
pos="0,15!",
requires="controller",
];
gm [
label="{ <mgmt> mgmt | <data> data } | { gm\npriority1=1 }",
pos="2,15.5!",
fontsize=12,
requires="infix",
];
tc [
label="{ <uplink> uplink | <mgmt> mgmt | <dnlink> dnlink } | { tc\nE2E-TC or P2P-TC }",
pos="2,15!",
fontsize=12,
requires="infix",
];
receiver [
label="{ <data> data | <mgmt> mgmt } | { receiver\npriority1=128 }",
pos="2,14.5!",
fontsize=12,
requires="infix",
];
host:mgmt1 -- gm:mgmt [requires="mgmt", color="lightgray"]
host:mgmt2 -- tc:mgmt [requires="mgmt", color="lightgray"]
host:mgmt3 -- receiver:mgmt [requires="mgmt", color="lightgray"]
gm:data -- tc:uplink [label="192.168.104.0/30 ", dir="both"]
tc:dnlink -- receiver:data [label="192.168.105.0/30 ", dir="both"]
}
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: ptp&#45;transparent&#45;clock Pages: 1 -->
<svg width="571pt" height="268pt"
viewBox="0.00 0.00 570.54 268.02" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 264.02)">
<title>ptp&#45;transparent&#45;clock</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-264.02 566.54,-264.02 566.54,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="black" points="0,-60.01 0,-200.01 58,-200.01 58,-60.01 0,-60.01"/>
<text text-anchor="middle" x="29" y="-184.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt1</text>
<polyline fill="none" stroke="black" points="0,-177.01 58,-177.01 "/>
<text text-anchor="middle" x="29" y="-129.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
<polyline fill="none" stroke="black" points="0,-106.01 58,-106.01 "/>
<text text-anchor="middle" x="29" y="-90.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt2</text>
<polyline fill="none" stroke="black" points="0,-83.01 58,-83.01 "/>
<text text-anchor="middle" x="29" y="-67.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt3</text>
</g>
<!-- gm -->
<g id="node2" class="node">
<title>gm</title>
<polygon fill="none" stroke="black" points="390.04,-217.52 390.04,-259.52 536.04,-259.52 536.04,-217.52 390.04,-217.52"/>
<text text-anchor="middle" x="413.54" y="-245.92" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt</text>
<polyline fill="none" stroke="black" points="390.04,-238.52 437.04,-238.52 "/>
<text text-anchor="middle" x="413.54" y="-224.92" font-family="DejaVu Sans Mono, Book" font-size="12.00">data</text>
<polyline fill="none" stroke="black" points="437.04,-217.52 437.04,-259.52 "/>
<text text-anchor="middle" x="486.54" y="-241.92" font-family="DejaVu Sans Mono, Book" font-size="12.00">gm</text>
<text text-anchor="middle" x="486.54" y="-228.92" font-family="DejaVu Sans Mono, Book" font-size="12.00">priority1=1</text>
</g>
<!-- host&#45;&#45;gm -->
<g id="edge1" class="edge">
<title>host:mgmt1&#45;&#45;gm:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M58,-189.01C58,-189.01 390.04,-249.52 390.04,-249.52"/>
</g>
<!-- tc -->
<g id="node3" class="node">
<title>tc</title>
<polygon fill="none" stroke="black" points="363.54,-98.51 363.54,-161.51 562.54,-161.51 562.54,-98.51 363.54,-98.51"/>
<text text-anchor="middle" x="394.54" y="-147.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">uplink</text>
<polyline fill="none" stroke="black" points="363.54,-140.51 425.54,-140.51 "/>
<text text-anchor="middle" x="394.54" y="-126.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt</text>
<polyline fill="none" stroke="black" points="363.54,-119.51 425.54,-119.51 "/>
<text text-anchor="middle" x="394.54" y="-105.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">dnlink</text>
<polyline fill="none" stroke="black" points="425.54,-98.51 425.54,-161.51 "/>
<text text-anchor="middle" x="494.04" y="-133.41" font-family="DejaVu Sans Mono, Book" font-size="12.00">tc</text>
<text text-anchor="middle" x="494.04" y="-120.41" font-family="DejaVu Sans Mono, Book" font-size="12.00">E2E&#45;TC or P2P&#45;TC</text>
</g>
<!-- host&#45;&#45;tc -->
<g id="edge2" class="edge">
<title>host:mgmt2&#45;&#45;tc:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M58,-94.01C58,-94.01 363.04,-130.01 363.04,-130.01"/>
</g>
<!-- receiver -->
<g id="node4" class="node">
<title>receiver</title>
<polygon fill="none" stroke="black" points="382.54,-0.5 382.54,-42.5 543.54,-42.5 543.54,-0.5 382.54,-0.5"/>
<text text-anchor="middle" x="406.04" y="-28.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">data</text>
<polyline fill="none" stroke="black" points="382.54,-21.5 429.54,-21.5 "/>
<text text-anchor="middle" x="406.04" y="-7.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt</text>
<polyline fill="none" stroke="black" points="429.54,-0.5 429.54,-42.5 "/>
<text text-anchor="middle" x="486.54" y="-24.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">receiver</text>
<text text-anchor="middle" x="486.54" y="-11.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">priority1=128</text>
</g>
<!-- host&#45;&#45;receiver -->
<g id="edge3" class="edge">
<title>host:mgmt3&#45;&#45;receiver:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M58,-71.01C58,-71.01 382.04,-10.5 382.04,-10.5"/>
</g>
<!-- gm&#45;&#45;tc -->
<g id="edge4" class="edge">
<title>gm:data&#45;&#45;tc:uplink</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M409.73,-207.85C406.25,-197.66 400.79,-181.72 397.32,-171.57"/>
<polygon fill="cornflowerblue" stroke="cornflowerblue" stroke-width="2" points="406.49,-209.19 413.04,-217.52 413.12,-206.93 406.49,-209.19"/>
<polygon fill="cornflowerblue" stroke="cornflowerblue" stroke-width="2" points="400.59,-170.34 394.04,-162.01 393.97,-172.61 400.59,-170.34"/>
<text text-anchor="middle" x="338.02" y="-193.51" font-family="DejaVu Serif, Book" font-size="14.00">192.168.104.0/30 </text>
</g>
<!-- tc&#45;&#45;receiver -->
<g id="edge5" class="edge">
<title>tc:dnlink&#45;&#45;receiver:data</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M396.19,-88.09C398.37,-77.98 401.74,-62.41 403.92,-52.34"/>
<polygon fill="cornflowerblue" stroke="cornflowerblue" stroke-width="2" points="392.74,-87.5 394.04,-98.01 399.58,-88.98 392.74,-87.5"/>
<polygon fill="cornflowerblue" stroke="cornflowerblue" stroke-width="2" points="407.35,-53.01 406.04,-42.5 400.51,-51.53 407.35,-53.01"/>
<text text-anchor="middle" x="334.55" y="-74.01" font-family="DejaVu Serif, Book" font-size="14.00">192.168.105.0/30 </text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

@@ -1,4 +1,4 @@
=== OSPF Default route advertise
=== OSPF Default Route Advertise
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/routing/ospf_default_route_advertise]
@@ -33,7 +33,7 @@ unless _always_ is set for _default-route-advertising_.
==== Topology
image::topology.svg[OSPF Default route advertise topology, align=center, scaledwidth=75%]
image::topology.svg[OSPF Default Route Advertise topology, align=center, scaledwidth=75%]
==== Sequence

Some files were not shown because too many files have changed in this diff Show More