mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
test: add some basic ptp tests
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
@@ -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
@@ -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");
|
||||
|
||||
@@ -39,6 +39,9 @@
|
||||
- name: "NTP Server"
|
||||
suite: ntp/all.yaml
|
||||
|
||||
- name: "PTP"
|
||||
suite: ptp/all.yaml
|
||||
|
||||
- name: "Routing"
|
||||
suite: routing/all.yaml
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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[]
|
||||
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
include::ieee1588.adoc[]
|
||||
|
||||
<<<
|
||||
|
||||
include::ieee802dot1as.adoc[]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
test.py
|
||||
@@ -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
@@ -0,0 +1 @@
|
||||
test.py
|
||||
@@ -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
|
||||
|
||||
|
||||
Executable
+107
@@ -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()
|
||||
@@ -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"]
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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-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-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--gm -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmt1--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--receiver -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:mgmt2--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--receiver -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>gm:data--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  </text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,6 @@
|
||||
include::ieee1588.adoc[]
|
||||
|
||||
<<<
|
||||
|
||||
include::ieee802dot1as.adoc[]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
test.py
|
||||
@@ -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
@@ -0,0 +1 @@
|
||||
test.py
|
||||
@@ -0,0 +1,5 @@
|
||||
include::ieee1588.adoc[]
|
||||
|
||||
<<<
|
||||
|
||||
include::ieee802dot1as.adoc[]
|
||||
Executable
+113
@@ -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()
|
||||
@@ -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"]
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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-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-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--alpha -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmt1--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--beta -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:mgmt2--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--beta -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>alpha:data--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 |
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
test.py
|
||||
@@ -0,0 +1,5 @@
|
||||
include::ieee1588.adoc[]
|
||||
|
||||
<<<
|
||||
|
||||
include::ieee802dot1as.adoc[]
|
||||
Executable
+179
@@ -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()
|
||||
@@ -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"]
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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-boundary-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-boundary-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--gm -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmt1--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--bc -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:mgmt2--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--receiver -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>host:mgmt3--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--bc -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>gm:data--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--receiver -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>bc:dnlink--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
@@ -0,0 +1 @@
|
||||
test.adoc
|
||||
@@ -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
|
||||
|
||||
|
||||
Executable
+118
@@ -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()
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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-port-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-port-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--gm -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmt1--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--receiver -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:mgmt2--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--receiver -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>gm:data--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  </text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
Symlink
+1
@@ -0,0 +1 @@
|
||||
test.adoc
|
||||
@@ -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)
|
||||
|
||||
|
||||
Executable
+154
@@ -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()
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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-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-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--gm -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmt1--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--receiver -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:mgmt2--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--receiver -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>gm:data--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  </text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,10 @@
|
||||
include::e2e.adoc[]
|
||||
|
||||
<<<
|
||||
|
||||
include::p2p.adoc[]
|
||||
|
||||
<<<
|
||||
|
||||
include::ieee802dot1as.adoc[]
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
test.py
|
||||
@@ -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
@@ -0,0 +1 @@
|
||||
test.py
|
||||
@@ -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
|
||||
|
||||
|
||||
Executable
+175
@@ -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()
|
||||
@@ -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-transparent-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-transparent-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--gm -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmt1--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-TC or P2P-TC</text>
|
||||
</g>
|
||||
<!-- host--tc -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:mgmt2--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--receiver -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>host:mgmt3--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--tc -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>gm:data--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--receiver -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>tc:dnlink--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
Reference in New Issue
Block a user