blob: 5e29d62bcb0da1632d3062b2670be0be63a39218 [file] [log] [blame]
Carmelo Cascone4c289b72019-01-22 15:30:45 -08001/*
2 * Copyright 2019-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.p4runtime.ctl.client;
18
Carmelo Casconec2be50a2019-04-10 00:15:39 -070019import com.google.common.collect.Maps;
20import io.grpc.ConnectivityState;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080021import io.grpc.ManagedChannel;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080022import io.grpc.Status;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080023import io.grpc.StatusRuntimeException;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080024import io.grpc.stub.StreamObserver;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080025import org.onosproject.grpc.ctl.AbstractGrpcClient;
26import org.onosproject.net.DeviceId;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080027import org.onosproject.net.device.DeviceAgentEvent;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080028import org.onosproject.net.pi.model.PiPipeconf;
29import org.onosproject.net.pi.runtime.PiPacketOperation;
30import org.onosproject.net.pi.service.PiPipeconfService;
31import org.onosproject.p4runtime.api.P4RuntimeClient;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080032import org.onosproject.p4runtime.ctl.controller.MasterElectionIdStore;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080033import org.onosproject.p4runtime.ctl.controller.P4RuntimeControllerImpl;
34import p4.v1.P4RuntimeGrpc;
35import p4.v1.P4RuntimeOuterClass;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080036import p4.v1.P4RuntimeOuterClass.GetForwardingPipelineConfigRequest;
37import p4.v1.P4RuntimeOuterClass.GetForwardingPipelineConfigResponse;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080038
Carmelo Cascone3977ea42019-02-28 13:43:42 -080039import java.math.BigInteger;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080040import java.nio.ByteBuffer;
41import java.util.concurrent.CompletableFuture;
Carmelo Casconec2be50a2019-04-10 00:15:39 -070042import java.util.concurrent.ConcurrentMap;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080043import java.util.concurrent.TimeUnit;
44import java.util.function.Consumer;
45
46import static com.google.common.base.Preconditions.checkNotNull;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080047import static p4.v1.P4RuntimeOuterClass.GetForwardingPipelineConfigRequest.ResponseType.COOKIE_ONLY;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080048
49/**
50 * Implementation of P4RuntimeClient.
51 */
52public final class P4RuntimeClientImpl
53 extends AbstractGrpcClient implements P4RuntimeClient {
54
Carmelo Casconec2be50a2019-04-10 00:15:39 -070055 private static final long DEFAULT_P4_DEVICE_ID = 1;
56
Carmelo Cascone4c289b72019-01-22 15:30:45 -080057 // TODO: consider making timeouts configurable per-device via netcfg
pierventre270cf6d2022-02-22 15:43:46 -080058 // We have measured that some devices can take up to 15s to push a pipeline
59 // which can block potentially other READ done against the target.
Carmelo Cascone4c289b72019-01-22 15:30:45 -080060 /**
61 * Timeout in seconds for short/fast RPCs.
62 */
pierventre270cf6d2022-02-22 15:43:46 -080063 static final int SHORT_TIMEOUT_SECONDS = 15;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080064 /**
65 * Timeout in seconds for RPCs that involve transfer of potentially large
66 * amount of data. This shoulld be long enough to allow for network delay
67 * (e.g. to transfer large pipeline binaries over slow network).
68 */
69 static final int LONG_TIMEOUT_SECONDS = 60;
70
Carmelo Cascone4c289b72019-01-22 15:30:45 -080071 private final P4RuntimeControllerImpl controller;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080072 private final PipelineConfigClientImpl pipelineConfigClient;
Carmelo Casconec2be50a2019-04-10 00:15:39 -070073 private final PiPipeconfService pipeconfService;
74 private final MasterElectionIdStore masterElectionIdStore;
75 private final ConcurrentMap<Long, StreamClientImpl> streamClients = Maps.newConcurrentMap();
Carmelo Cascone4c289b72019-01-22 15:30:45 -080076
77 /**
78 * Instantiates a new client with the given arguments.
79 *
Carmelo Casconec2be50a2019-04-10 00:15:39 -070080 * @param deviceId device ID
Carmelo Cascone3977ea42019-02-28 13:43:42 -080081 * @param channel gRPC managed channel
Carmelo Casconec2be50a2019-04-10 00:15:39 -070082 * @param controller P4Runtime controller instance
Carmelo Cascone3977ea42019-02-28 13:43:42 -080083 * @param pipeconfService pipeconf service instance
84 * @param masterElectionIdStore master election ID store
Carmelo Cascone4c289b72019-01-22 15:30:45 -080085 */
Carmelo Casconec2be50a2019-04-10 00:15:39 -070086 public P4RuntimeClientImpl(DeviceId deviceId,
Carmelo Cascone4c289b72019-01-22 15:30:45 -080087 ManagedChannel channel,
88 P4RuntimeControllerImpl controller,
Carmelo Cascone3977ea42019-02-28 13:43:42 -080089 PiPipeconfService pipeconfService,
90 MasterElectionIdStore masterElectionIdStore) {
Carmelo Casconec2be50a2019-04-10 00:15:39 -070091 super(deviceId, channel, true, controller);
Carmelo Cascone4c289b72019-01-22 15:30:45 -080092 checkNotNull(channel);
93 checkNotNull(controller);
94 checkNotNull(pipeconfService);
Carmelo Cascone3977ea42019-02-28 13:43:42 -080095 checkNotNull(masterElectionIdStore);
Carmelo Cascone4c289b72019-01-22 15:30:45 -080096
Carmelo Cascone4c289b72019-01-22 15:30:45 -080097 this.controller = controller;
Carmelo Casconec2be50a2019-04-10 00:15:39 -070098 this.pipeconfService = pipeconfService;
99 this.masterElectionIdStore = masterElectionIdStore;
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800100 this.pipelineConfigClient = new PipelineConfigClientImpl(this);
101 }
102
103 @Override
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800104 public void shutdown() {
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700105 streamClients.forEach((p4DeviceId, streamClient) ->
106 streamClient.closeSession(p4DeviceId));
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800107 super.shutdown();
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800108 }
109
110 @Override
111 public CompletableFuture<Boolean> setPipelineConfig(
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700112 long p4DeviceId, PiPipeconf pipeconf, ByteBuffer deviceData) {
113 return pipelineConfigClient.setPipelineConfig(p4DeviceId, pipeconf, deviceData);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800114 }
115
116 @Override
117 public CompletableFuture<Boolean> isPipelineConfigSet(
Carmelo Casconeadb89052019-04-17 20:02:33 -0700118 long p4DeviceId, PiPipeconf pipeconf) {
119 return pipelineConfigClient.isPipelineConfigSet(p4DeviceId, pipeconf);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800120 }
121
122 @Override
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700123 public CompletableFuture<Boolean> isAnyPipelineConfigSet(long p4DeviceId) {
124 return pipelineConfigClient.isAnyPipelineConfigSet(p4DeviceId);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800125 }
126
127 @Override
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700128 public ReadRequest read(long p4DeviceId, PiPipeconf pipeconf) {
129 return new ReadRequestImpl(this, p4DeviceId, pipeconf);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800130 }
131
132 @Override
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700133 public boolean isSessionOpen(long p4DeviceId) {
134 return streamClients.containsKey(p4DeviceId) &&
135 streamClients.get(p4DeviceId).isSessionOpen(p4DeviceId);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800136 }
137
138 @Override
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700139 public void closeSession(long p4DeviceId) {
140 if (streamClients.containsKey(p4DeviceId)) {
141 streamClients.get(p4DeviceId).closeSession(p4DeviceId);
142 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800143 }
144
145 @Override
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700146 public void setMastership(long p4DeviceId, boolean master, BigInteger newElectionId) {
147 streamClients.putIfAbsent(p4DeviceId, new StreamClientImpl(
148 pipeconfService, masterElectionIdStore, this, p4DeviceId, controller));
149 streamClients.get(p4DeviceId).setMastership(p4DeviceId, master, newElectionId);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800150 }
151
152 @Override
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700153 public boolean isMaster(long p4DeviceId) {
154 return streamClients.containsKey(p4DeviceId) &&
155 streamClients.get(p4DeviceId).isMaster(p4DeviceId);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800156 }
157
158 @Override
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700159 public void packetOut(long p4DeviceId, PiPacketOperation packet, PiPipeconf pipeconf) {
160 if (streamClients.containsKey(p4DeviceId)) {
161 streamClients.get(p4DeviceId).packetOut(p4DeviceId, packet, pipeconf);
162 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800163 }
164
165 @Override
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700166 public WriteRequest write(long p4DeviceId, PiPipeconf pipeconf) {
167 return new WriteRequestImpl(this, p4DeviceId, pipeconf);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800168 }
169
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800170 @Override
171 public CompletableFuture<Boolean> probeService() {
172 final CompletableFuture<Boolean> future = new CompletableFuture<>();
173 final StreamObserver<GetForwardingPipelineConfigResponse> responseObserver =
174 new StreamObserver<GetForwardingPipelineConfigResponse>() {
175 @Override
176 public void onNext(GetForwardingPipelineConfigResponse value) {
177 future.complete(true);
178 }
179
180 @Override
181 public void onError(Throwable t) {
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700182 log.debug("", t);
183 // FIXME: The P4Runtime spec is not explicit about error
184 // codes when a pipeline config is not set, which would
185 // be useful here as it's an indication that the
186 // service is available. As a workaround, we simply
187 // check the channel state.
188 future.complete(ConnectivityState.READY.equals(
189 channel.getState(false)));
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800190 }
191
192 @Override
193 public void onCompleted() {
194 // Ignore, unary call.
195 }
196 };
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700197 // Get any p4DeviceId under the control of this client or a default one.
198 final long p4DeviceId = streamClients.isEmpty() ? DEFAULT_P4_DEVICE_ID
199 : streamClients.keySet().iterator().next();
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800200 // Use long timeout as the device might return the full P4 blob
201 // (e.g. server does not support cookie), over a slow network.
202 execRpc(s -> s.getForwardingPipelineConfig(
203 GetForwardingPipelineConfigRequest.newBuilder()
204 .setDeviceId(p4DeviceId)
205 .setResponseType(COOKIE_ONLY)
206 .build(), responseObserver),
207 SHORT_TIMEOUT_SECONDS);
208 return future;
209 }
210
Carmelo Casconeab5d41e2019-03-06 18:02:34 -0800211 @Override
212 protected void handleRpcError(Throwable throwable, String opDescription) {
213 if (throwable instanceof StatusRuntimeException) {
214 checkGrpcException((StatusRuntimeException) throwable);
215 }
216 super.handleRpcError(throwable, opDescription);
217 }
218
219 private void checkGrpcException(StatusRuntimeException sre) {
220 if (sre.getStatus().getCode() == Status.Code.PERMISSION_DENIED) {
221 // Notify upper layers that this node is not master.
222 controller.postEvent(new DeviceAgentEvent(
223 DeviceAgentEvent.Type.NOT_MASTER, deviceId));
224 }
225 }
226
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800227 /**
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800228 * Returns the ONOS device ID associated with this client.
229 *
230 * @return ONOS device ID
231 */
232 DeviceId deviceId() {
233 return this.deviceId;
234 }
235
236 /**
237 * Returns the election ID last used in a MasterArbitrationUpdate message
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700238 * sent by the client to the server for the given P4Runtime-internal device
239 * ID. No guarantees are given that this is the current election ID
240 * associated to the session, nor that the server has acknowledged this
241 * value as valid.
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800242 *
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700243 * @param p4DeviceId P4Runtime-internal device ID
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800244 * @return election ID uint128 protobuf message
245 */
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700246 P4RuntimeOuterClass.Uint128 lastUsedElectionId(long p4DeviceId) {
247 if (streamClients.containsKey(p4DeviceId)) {
248 return streamClients.get(p4DeviceId).lastUsedElectionId();
249 } else {
250 return P4RuntimeOuterClass.Uint128.getDefaultInstance();
251 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800252 }
253
254 /**
255 * Forces execution of an RPC in a cancellable context with the given
256 * timeout (in seconds).
257 *
258 * @param stubConsumer P4Runtime stub consumer
259 * @param timeout timeout in seconds
260 */
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800261 void execRpc(Consumer<P4RuntimeGrpc.P4RuntimeStub> stubConsumer,
262 int timeout) {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800263 if (log.isTraceEnabled()) {
264 log.trace("Executing RPC with timeout {} seconds (context deadline {})...",
265 timeout, context().getDeadline());
266 }
267 runInCancellableContext(() -> stubConsumer.accept(
268 P4RuntimeGrpc.newStub(channel)
269 .withDeadlineAfter(timeout, TimeUnit.SECONDS)));
270 }
271
272 /**
273 * Forces execution of an RPC in a cancellable context with no timeout.
274 *
275 * @param stubConsumer P4Runtime stub consumer
276 */
277 void execRpcNoTimeout(Consumer<P4RuntimeGrpc.P4RuntimeStub> stubConsumer) {
278 if (log.isTraceEnabled()) {
279 log.trace("Executing RPC with no timeout (context deadline {})...",
280 context().getDeadline());
281 }
282 runInCancellableContext(() -> stubConsumer.accept(
283 P4RuntimeGrpc.newStub(channel)));
284 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800285}