test: add some basic ptp tests

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2026-04-24 14:29:58 +02:00
parent 3426c2527b
commit 95bd795347
122 changed files with 2483 additions and 132 deletions
+6
View File
@@ -0,0 +1,6 @@
include::ieee1588.adoc[]
<<<
include::ieee802dot1as.adoc[]
+28
View File
@@ -0,0 +1,28 @@
=== PTP basic (IEEE 1588)
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/basic]
==== Description
Verify basic PTP operation end-to-end: clock configuration, port state
transitions, and clock servo convergence.
Two Ordinary Clocks are connected back-to-back. The grandmaster is
configured with `priority1=1` so it always wins the BTCA election; the
time receiver is configured with `time-receiver-only` so it never
attempts to become grandmaster. The test is run once per supported
profile, covering both IEEE 1588-2019 (UDP/IPv4, E2E) and IEEE 802.1AS
(Layer 2, P2P).
==== Topology
image::topology.svg[PTP basic (IEEE 1588) topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to DUTs
. Configure grandmaster (OC, ieee1588, priority1=1) and time receiver (ieee1588, priority1=128, client-only)
. Wait for grandmaster and time receiver ports to reach active states
. Wait for time receiver offset to converge
+1
View File
@@ -0,0 +1 @@
test.py
+28
View File
@@ -0,0 +1,28 @@
=== PTP basic (IEEE 802.1AS)
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/basic]
==== Description
Verify basic PTP operation end-to-end: clock configuration, port state
transitions, and clock servo convergence.
Two Ordinary Clocks are connected back-to-back. The grandmaster is
configured with `priority1=1` so it always wins the BTCA election; the
time receiver is configured with `time-receiver-only` so it never
attempts to become grandmaster. The test is run once per supported
profile, covering both IEEE 1588-2019 (UDP/IPv4, E2E) and IEEE 802.1AS
(Layer 2, P2P).
==== Topology
image::topology.svg[PTP basic (IEEE 802.1AS) topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to DUTs
. Configure grandmaster (OC, ieee802-dot1as, priority1=1) and time receiver (ieee802-dot1as, priority1=128, client-only)
. Wait for grandmaster and time receiver ports to reach active states
. Wait for time receiver offset to converge
+1
View File
@@ -0,0 +1 @@
test.py
+29
View File
@@ -0,0 +1,29 @@
=== PTP basic
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/ptp/basic]
==== Description
Verify basic PTP operation end-to-end: clock configuration, port state
transitions, and clock servo convergence.
Two Ordinary Clocks are connected back-to-back. The grandmaster is
configured with `priority1=1` so it always wins the BTCA election; the
time receiver is configured with `time-receiver-only` so it never
attempts to become grandmaster. Software timestamping is used, making
the test suitable for virtual machines as well as real hardware.
This is the smoke test: if it passes, the full PTP stack is working.
==== Topology
image::topology.svg[PTP basic topology, align=center, scaledwidth=75%]
==== Sequence
. Set up topology and attach to DUTs
. Configure grandmaster (priority1=1) and time receiver (priority1=128, client-only)
. Wait for grandmaster and time receiver ports to reach active states
. Wait for time receiver offset to converge
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""PTP basic
Verify basic PTP operation end-to-end: clock configuration, port state
transitions, and clock servo convergence.
Two Ordinary Clocks are connected back-to-back. The grandmaster is
configured with `priority1=1` so it always wins the BTCA election; the
time receiver is configured with `time-receiver-only` so it never
attempts to become grandmaster. The test is run once per supported
profile, covering both IEEE 1588-2019 (UDP/IPv4, E2E) and IEEE 802.1AS
(Layer 2, P2P).
"""
import infamy
import infamy.ptp as ptp
from infamy import until
from infamy.util import parallel
class ArgumentParser(infamy.ArgumentParser):
def __init__(self):
super().__init__()
self.args.add_argument("--profile", default="ieee1588",
choices=["ieee1588", "ieee802-dot1as"])
self.args.add_argument("--threshold-ns", type=int, default=None)
def configure_oc(iface, priority1, client_only, profile, ip=None):
config = {
"ieee1588-ptp-tt": {
"ptp": {
"instances": {
"instance": [{
"instance-index": 0,
"default-ds": {
"instance-type": "oc",
"domain-number": 0,
"priority1": priority1,
"priority2": 128,
"infix-ptp:profile": profile,
"time-receiver-only": client_only,
},
"ports": {
"port": [{
"port-index": 1,
"underlying-interface": iface,
}]
}
}]
}
}
}
}
# Always enable the underlying interface — ptp4l needs it up regardless
# of profile. IEEE 1588 also needs an IPv4 address for UDP transport.
iface_cfg = {"name": iface, "enabled": True}
if profile == "ieee1588":
iface_cfg["ipv4"] = {"address": [{"ip": ip, "prefix-length": 30}]}
config["ietf-interfaces"] = {
"interfaces": {"interface": [iface_cfg]}
}
# Fast timers: 250 ms announce/sync intervals speed up port state transitions
# and convergence compared to the 1 s defaults.
port_ds = {
"log-announce-interval": -2,
"announce-receipt-timeout": 2,
"log-sync-interval": -2,
}
if profile == "ieee1588":
port_ds["delay-mechanism"] = "e2e"
config["ieee1588-ptp-tt"]["ptp"]["instances"]["instance"][0] \
["ports"]["port"][0]["port-ds"] = port_ds
return config
with infamy.Test() as test:
with test.step("Set up topology and attach to DUTs"):
arg = ArgumentParser()
env = infamy.Env(args=arg)
profile = env.args.profile
gm = env.attach("gm", "mgmt")
receiver = env.attach("receiver", "mgmt")
_, gm_iface = env.ltop.xlate("gm", "data")
_, receiver_iface = env.ltop.xlate("receiver", "data")
threshold_ns = env.args.threshold_ns or ptp.default_threshold(env, "receiver")
with test.step(f"Configure grandmaster (OC, {profile}, priority1=1) and time receiver ({profile}, priority1=128, client-only)"):
gm.put_config_dicts(configure_oc(gm_iface, priority1=1,
client_only=False, profile=profile,
ip="192.168.100.1"))
receiver.put_config_dicts(configure_oc(receiver_iface, priority1=128,
client_only=True, profile=profile,
ip="192.168.100.2"))
with test.step("Wait for grandmaster and time receiver ports to reach active states"):
parallel(lambda: until(lambda: ptp.is_time_transmitter(gm), attempts=60),
lambda: until(lambda: ptp.is_time_receiver(receiver), attempts=60))
with test.step("Wait for time receiver offset to converge"):
until(lambda: ptp.has_converged(receiver, threshold_ns), attempts=120)
test.succeed()
+11
View File
@@ -0,0 +1,11 @@
---
- settings:
test-spec: <case>.adoc
- name: PTP basic (IEEE 1588)
case: ieee1588.py
opts: ["--profile", "ieee1588"]
- name: PTP basic (IEEE 802.1AS)
case: ieee802dot1as.py
opts: ["--profile", "ieee802-dot1as"]
+33
View File
@@ -0,0 +1,33 @@
graph "ptp-basic" {
layout="neato";
overlap="false";
esep="+22";
node [shape=record, fontname="DejaVu Sans Mono, Book"];
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
host [
label="{ <mgmt1> mgmt1 | <host> \n\nhost\n\n\n | <mgmt2> mgmt2 }",
pos="0,15!",
requires="controller",
];
gm [
label="{ <mgmt> mgmt | <data> data } | { gm\npriority1=1 }",
pos="2,15.25!",
fontsize=12,
requires="infix",
];
receiver [
label="{ <data> data | <mgmt> mgmt } | { receiver\npriority1=128 }",
pos="2,14.75!",
fontsize=12,
requires="infix",
];
host:mgmt1 -- gm:mgmt [requires="mgmt", color="lightgray"]
host:mgmt2 -- receiver:mgmt [requires="mgmt", color="lightgray"]
gm:data -- receiver:data [label="\n\n192.168.100.0/30 ", dir="both"]
}
+62
View File
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: ptp&#45;basic Pages: 1 -->
<svg width="510pt" height="149pt"
viewBox="0.00 0.00 509.54 149.01" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 145.01)">
<title>ptp&#45;basic</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-145.01 505.54,-145.01 505.54,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="black" points="0,-4 0,-137 58,-137 58,-4 0,-4"/>
<text text-anchor="middle" x="29" y="-121.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt1</text>
<polyline fill="none" stroke="black" points="0,-114 58,-114 "/>
<text text-anchor="middle" x="29" y="-66.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
<polyline fill="none" stroke="black" points="0,-27 58,-27 "/>
<text text-anchor="middle" x="29" y="-11.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt2</text>
</g>
<!-- gm -->
<g id="node2" class="node">
<title>gm</title>
<polygon fill="none" stroke="black" points="348.04,-98.51 348.04,-140.51 494.04,-140.51 494.04,-98.51 348.04,-98.51"/>
<text text-anchor="middle" x="371.54" y="-126.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt</text>
<polyline fill="none" stroke="black" points="348.04,-119.51 395.04,-119.51 "/>
<text text-anchor="middle" x="371.54" y="-105.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">data</text>
<polyline fill="none" stroke="black" points="395.04,-98.51 395.04,-140.51 "/>
<text text-anchor="middle" x="444.54" y="-122.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">gm</text>
<text text-anchor="middle" x="444.54" y="-109.91" font-family="DejaVu Sans Mono, Book" font-size="12.00">priority1=1</text>
</g>
<!-- host&#45;&#45;gm -->
<g id="edge1" class="edge">
<title>host:mgmt1&#45;&#45;gm:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M58,-125.5C58,-125.5 348.04,-130.51 348.04,-130.51"/>
</g>
<!-- receiver -->
<g id="node3" class="node">
<title>receiver</title>
<polygon fill="none" stroke="black" points="340.54,-0.5 340.54,-42.5 501.54,-42.5 501.54,-0.5 340.54,-0.5"/>
<text text-anchor="middle" x="364.04" y="-28.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">data</text>
<polyline fill="none" stroke="black" points="340.54,-21.5 387.54,-21.5 "/>
<text text-anchor="middle" x="364.04" y="-7.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">mgmt</text>
<polyline fill="none" stroke="black" points="387.54,-0.5 387.54,-42.5 "/>
<text text-anchor="middle" x="444.54" y="-24.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">receiver</text>
<text text-anchor="middle" x="444.54" y="-11.9" font-family="DejaVu Sans Mono, Book" font-size="12.00">priority1=128</text>
</g>
<!-- host&#45;&#45;receiver -->
<g id="edge2" class="edge">
<title>host:mgmt2&#45;&#45;receiver:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M58,-15.5C58,-15.5 340.04,-10.5 340.04,-10.5"/>
</g>
<!-- gm&#45;&#45;receiver -->
<g id="edge3" class="edge">
<title>gm:data&#45;&#45;receiver:data</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M369.79,-88.5C368.51,-78.3 366.55,-62.59 365.28,-52.43"/>
<polygon fill="cornflowerblue" stroke="cornflowerblue" stroke-width="2" points="366.33,-89.02 371.04,-98.51 373.27,-88.15 366.33,-89.02"/>
<polygon fill="cornflowerblue" stroke="cornflowerblue" stroke-width="2" points="368.75,-51.99 364.04,-42.5 361.81,-52.86 368.75,-51.99"/>
<text text-anchor="middle" x="299.53" y="-74.26" font-family="DejaVu Serif, Book" font-size="14.00">192.168.100.0/30 &#160;</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB