Skip to content

Commit 8032783

Browse files
committed
Add BMv2 e2e integration tests for PRE entity reads
The BMv2 end-to-end coverage in 1199952 verified the table-driven read APIs against a real simple_switch_grpc instance; this commit lifts the same pattern to the packet replication engine read APIs shipped by b893a53 (multicast group) and f709b5a (clone session). The new test class follows the lifecycle BMv2TestSupport established: a single @BeforeAll spawn, @afterall teardown, and a parallel raw P4Runtime stub used solely for seeding device state, since jp4 1.5 has no public Write API for multicast groups or clone sessions and controllers normally install them through the wire RPC. Local verification against simple_switch_grpc 1.15.1 — the BMv2 build the project's CI Docker image and developer hosts both run — found that this BMv2 fully implements both MulticastGroupEntry and CloneSessionEntry Read RPCs. That is the opposite of the RegisterEntry case 1199952 documented, where the same BMv2 returns UNIMPLEMENTED and the register integration test skips via Assumptions.assumeTrue. The two new PRE tests retain the same defensive Assumptions.assumeTrue blocks even though BMv2 1.15.1 does not trip them, so an older BMv2 or an alternate spec-compliant target that does refuse the read with UNIMPLEMENTED will skip rather than fail the suite. The packet replication engine is program-agnostic — multicast groups and clone sessions are addressed by controller-assigned numeric ids only and carry no P4-program declaration. As a consequence the existing counters_meters_registers_groups.p4 fixture serves these tests as-is: it satisfies the pipeline-bound gate that jp4 enforces on every read entry point, and that is the only fixture-side requirement PRE reads have. No new P4, JSON, or P4Info artefacts are introduced. Two happy-path tests cover the wire-level round-trip, one per entity family. readMulticastGroupAfterInsertReturnsTheGroup seeds a one-replica group through the raw stub and asserts the multicast-group id, the replica's canonical-bytestring port, and the per-replica instance round-trip intact. readCloneSessionAfterInsertReturnsTheSession seeds a one-replica session with class-of-service 0 and packetLengthBytes 0 (the "no truncation" case the spec defines) and asserts the same shape. The unit tests in P4SwitchReadMulticastGroupTest and P4SwitchReadCloneSessionTest already cover the matrix dimensions (three-way port_kind handling, backup_replicas wiring, metadata round-trip, server-side id filter assembly, client-side where filter, and the clone-session truncation-boundary semantics); the e2e tests deliberately focus on the BMv2 wire round-trip rather than duplicating unit coverage. The test count moves from 396 to 398. SemVer-irrelevant: test-only commit; no production source changes, no public API surface changes, no fixture changes. The library code and the existing test suite are byte-identical to f709b5a.
1 parent f709b5a commit 8032783

1 file changed

Lines changed: 200 additions & 0 deletions

File tree

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package io.github.zhh2001.jp4.integration;
2+
3+
import com.google.protobuf.ByteString;
4+
import io.github.zhh2001.jp4.P4Switch;
5+
import io.github.zhh2001.jp4.entity.CloneSessionEntry;
6+
import io.github.zhh2001.jp4.entity.MulticastGroupEntry;
7+
import io.github.zhh2001.jp4.entity.Replica;
8+
import io.github.zhh2001.jp4.error.P4OperationException;
9+
import io.github.zhh2001.jp4.pipeline.DeviceConfig;
10+
import io.github.zhh2001.jp4.pipeline.P4Info;
11+
import io.github.zhh2001.jp4.testsupport.BMv2TestSupport;
12+
import io.grpc.ManagedChannel;
13+
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
14+
import org.junit.jupiter.api.AfterAll;
15+
import org.junit.jupiter.api.Assumptions;
16+
import org.junit.jupiter.api.BeforeAll;
17+
import org.junit.jupiter.api.Test;
18+
import p4.v1.P4RuntimeGrpc;
19+
import p4.v1.P4RuntimeOuterClass.Entity;
20+
import p4.v1.P4RuntimeOuterClass.PacketReplicationEngineEntry;
21+
import p4.v1.P4RuntimeOuterClass.Uint128;
22+
import p4.v1.P4RuntimeOuterClass.Update;
23+
import p4.v1.P4RuntimeOuterClass.WriteRequest;
24+
25+
import java.nio.file.Path;
26+
import java.util.List;
27+
28+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
29+
import static org.junit.jupiter.api.Assertions.assertEquals;
30+
import static org.junit.jupiter.api.Assertions.assertNotNull;
31+
32+
/**
33+
* End-to-end integration coverage for the packet replication engine read
34+
* APIs added in {@code b893a53} (multicast group) and {@code f709b5a}
35+
* (clone session). The PRE family is program-agnostic, so this class
36+
* binds the existing {@code counters_meters_registers_groups.p4}
37+
* pipeline solely to satisfy the pipeline-bound gate; the multicast
38+
* groups and clone sessions are installed at runtime via a parallel
39+
* P4Runtime stub Write (jp4 v1.5 has no public write API for either).
40+
*/
41+
class PacketReplicationEngineIntegrationTest {
42+
43+
private static BMv2TestSupport bmv2;
44+
private static P4Switch sw;
45+
private static ManagedChannel rawChannel;
46+
private static P4RuntimeGrpc.P4RuntimeBlockingStub rawStub;
47+
private static final long ELECTION_ID = 1L;
48+
49+
@BeforeAll
50+
static void start() throws Exception {
51+
BMv2TestSupport.checkEnvironment();
52+
bmv2 = new BMv2TestSupport("PacketReplicationEngineIntegrationTest").start();
53+
P4Info p4info = P4Info.fromFile(
54+
Path.of("src/test/resources/p4/counters_meters_registers_groups.p4info.txtpb"));
55+
DeviceConfig dc = DeviceConfig.Bmv2.fromFile(
56+
Path.of("src/test/resources/p4/counters_meters_registers_groups.json"));
57+
sw = P4Switch.connectAsPrimary(bmv2.grpcAddress()).bindPipeline(p4info, dc);
58+
59+
rawChannel = NettyChannelBuilder.forTarget(bmv2.grpcAddress())
60+
.usePlaintext()
61+
.build();
62+
rawStub = P4RuntimeGrpc.newBlockingStub(rawChannel);
63+
}
64+
65+
@AfterAll
66+
static void stop() {
67+
if (sw != null) sw.close();
68+
if (rawChannel != null) rawChannel.shutdownNow();
69+
if (bmv2 != null) bmv2.close();
70+
}
71+
72+
@Test
73+
void readMulticastGroupAfterInsertReturnsTheGroup() {
74+
int groupId = 50;
75+
byte[] portBytes = new byte[]{0x05}; // canonical-bytestring per P4Runtime 1.3+ encoding
76+
try {
77+
seedMulticastGroup(groupId, portBytes, /*instance*/ 1);
78+
} catch (RuntimeException e) {
79+
if (e.getMessage() != null && e.getMessage().contains("UNIMPLEMENTED")) {
80+
Assumptions.assumeTrue(false,
81+
"BMv2 does not implement MulticastGroupEntry Write: " + e.getMessage());
82+
}
83+
throw e;
84+
}
85+
86+
List<MulticastGroupEntry> groups;
87+
try {
88+
groups = sw.readMulticastGroup().all();
89+
} catch (P4OperationException e) {
90+
if (e.getMessage() != null && e.getMessage().contains("UNIMPLEMENTED")) {
91+
Assumptions.assumeTrue(false,
92+
"BMv2 does not implement MulticastGroupEntry Read: " + e.getMessage());
93+
}
94+
throw e;
95+
}
96+
97+
assertEquals(1, groups.size(),
98+
"exactly one multicast group should be present after the INSERT");
99+
MulticastGroupEntry g = groups.get(0);
100+
assertEquals(groupId, g.multicastGroupId());
101+
assertEquals(1, g.replicas().size());
102+
Replica r = g.replicas().get(0);
103+
assertNotNull(r.port(), "replica port should round-trip as non-null after wire write");
104+
assertArrayEquals(portBytes, r.port().toByteArray());
105+
assertEquals(1, r.instance());
106+
}
107+
108+
@Test
109+
void readCloneSessionAfterInsertReturnsTheSession() {
110+
int sessionId = 60;
111+
byte[] portBytes = new byte[]{0x07}; // canonical-bytestring
112+
try {
113+
seedCloneSession(sessionId, portBytes, /*instance*/ 2, /*classOfService*/ 0,
114+
/*packetLengthBytes*/ 0);
115+
} catch (RuntimeException e) {
116+
if (e.getMessage() != null && e.getMessage().contains("UNIMPLEMENTED")) {
117+
Assumptions.assumeTrue(false,
118+
"BMv2 does not implement CloneSessionEntry Write: " + e.getMessage());
119+
}
120+
throw e;
121+
}
122+
123+
List<CloneSessionEntry> sessions;
124+
try {
125+
sessions = sw.readCloneSession().all();
126+
} catch (P4OperationException e) {
127+
if (e.getMessage() != null && e.getMessage().contains("UNIMPLEMENTED")) {
128+
Assumptions.assumeTrue(false,
129+
"BMv2 does not implement CloneSessionEntry Read: " + e.getMessage());
130+
}
131+
throw e;
132+
}
133+
134+
assertEquals(1, sessions.size(),
135+
"exactly one clone session should be present after the INSERT");
136+
CloneSessionEntry s = sessions.get(0);
137+
assertEquals(sessionId, s.sessionId());
138+
assertEquals(1, s.replicas().size());
139+
Replica r = s.replicas().get(0);
140+
assertNotNull(r.port(), "replica port should round-trip as non-null after wire write");
141+
assertArrayEquals(portBytes, r.port().toByteArray());
142+
assertEquals(2, r.instance());
143+
}
144+
145+
// ---------- raw-stub seeding helpers --------------------------------
146+
147+
private static void seedMulticastGroup(int groupId, byte[] portBytes, int instance) {
148+
var replica = p4.v1.P4RuntimeOuterClass.Replica.newBuilder()
149+
.setPort(ByteString.copyFrom(portBytes))
150+
.setInstance(instance)
151+
.build();
152+
var group = p4.v1.P4RuntimeOuterClass.MulticastGroupEntry.newBuilder()
153+
.setMulticastGroupId(groupId)
154+
.addReplicas(replica)
155+
.build();
156+
Entity entity = Entity.newBuilder()
157+
.setPacketReplicationEngineEntry(
158+
PacketReplicationEngineEntry.newBuilder()
159+
.setMulticastGroupEntry(group)
160+
.build())
161+
.build();
162+
sendInsert(entity);
163+
}
164+
165+
private static void seedCloneSession(int sessionId, byte[] portBytes, int instance,
166+
int classOfService, int packetLengthBytes) {
167+
var replica = p4.v1.P4RuntimeOuterClass.Replica.newBuilder()
168+
.setPort(ByteString.copyFrom(portBytes))
169+
.setInstance(instance)
170+
.build();
171+
var session = p4.v1.P4RuntimeOuterClass.CloneSessionEntry.newBuilder()
172+
.setSessionId(sessionId)
173+
.addReplicas(replica)
174+
.setClassOfService(classOfService)
175+
.setPacketLengthBytes(packetLengthBytes)
176+
.build();
177+
Entity entity = Entity.newBuilder()
178+
.setPacketReplicationEngineEntry(
179+
PacketReplicationEngineEntry.newBuilder()
180+
.setCloneSessionEntry(session)
181+
.build())
182+
.build();
183+
sendInsert(entity);
184+
}
185+
186+
private static void sendInsert(Entity entity) {
187+
WriteRequest req = WriteRequest.newBuilder()
188+
.setDeviceId(0L)
189+
.setElectionId(Uint128.newBuilder()
190+
.setHigh(0L)
191+
.setLow(ELECTION_ID)
192+
.build())
193+
.addUpdates(Update.newBuilder()
194+
.setType(Update.Type.INSERT)
195+
.setEntity(entity)
196+
.build())
197+
.build();
198+
rawStub.write(req);
199+
}
200+
}

0 commit comments

Comments
 (0)