blob: d06e3fc5542c04152435d2ff5b8a5d01d5995ecf [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
19import io.grpc.ManagedChannel;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080020import io.grpc.Status;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080021import io.grpc.StatusRuntimeException;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080022import io.grpc.stub.StreamObserver;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080023import org.onosproject.grpc.ctl.AbstractGrpcClient;
24import org.onosproject.net.DeviceId;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080025import org.onosproject.net.device.DeviceAgentEvent;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080026import org.onosproject.net.pi.model.PiPipeconf;
27import org.onosproject.net.pi.runtime.PiPacketOperation;
28import org.onosproject.net.pi.service.PiPipeconfService;
29import org.onosproject.p4runtime.api.P4RuntimeClient;
30import org.onosproject.p4runtime.api.P4RuntimeClientKey;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080031import org.onosproject.p4runtime.ctl.controller.MasterElectionIdStore;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080032import org.onosproject.p4runtime.ctl.controller.P4RuntimeControllerImpl;
33import p4.v1.P4RuntimeGrpc;
34import p4.v1.P4RuntimeOuterClass;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080035import p4.v1.P4RuntimeOuterClass.GetForwardingPipelineConfigRequest;
36import p4.v1.P4RuntimeOuterClass.GetForwardingPipelineConfigResponse;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080037
Carmelo Cascone3977ea42019-02-28 13:43:42 -080038import java.math.BigInteger;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080039import java.nio.ByteBuffer;
40import java.util.concurrent.CompletableFuture;
41import java.util.concurrent.TimeUnit;
42import java.util.function.Consumer;
43
44import static com.google.common.base.Preconditions.checkNotNull;
45import static java.lang.String.format;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080046import static p4.v1.P4RuntimeOuterClass.GetForwardingPipelineConfigRequest.ResponseType.COOKIE_ONLY;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080047
48/**
49 * Implementation of P4RuntimeClient.
50 */
51public final class P4RuntimeClientImpl
52 extends AbstractGrpcClient implements P4RuntimeClient {
53
54 // TODO: consider making timeouts configurable per-device via netcfg
55 /**
56 * Timeout in seconds for short/fast RPCs.
57 */
58 static final int SHORT_TIMEOUT_SECONDS = 10;
59 /**
60 * Timeout in seconds for RPCs that involve transfer of potentially large
61 * amount of data. This shoulld be long enough to allow for network delay
62 * (e.g. to transfer large pipeline binaries over slow network).
63 */
64 static final int LONG_TIMEOUT_SECONDS = 60;
65
66 private final long p4DeviceId;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080067 private final P4RuntimeControllerImpl controller;
68 private final StreamClientImpl streamClient;
69 private final PipelineConfigClientImpl pipelineConfigClient;
70
71 /**
72 * Instantiates a new client with the given arguments.
73 *
Carmelo Cascone3977ea42019-02-28 13:43:42 -080074 * @param clientKey client key
75 * @param channel gRPC managed channel
76 * @param controller P$Runtime controller instance
77 * @param pipeconfService pipeconf service instance
78 * @param masterElectionIdStore master election ID store
Carmelo Cascone4c289b72019-01-22 15:30:45 -080079 */
80 public P4RuntimeClientImpl(P4RuntimeClientKey clientKey,
81 ManagedChannel channel,
82 P4RuntimeControllerImpl controller,
Carmelo Cascone3977ea42019-02-28 13:43:42 -080083 PiPipeconfService pipeconfService,
84 MasterElectionIdStore masterElectionIdStore) {
85 super(clientKey, channel, true, controller);
Carmelo Cascone4c289b72019-01-22 15:30:45 -080086 checkNotNull(channel);
87 checkNotNull(controller);
88 checkNotNull(pipeconfService);
Carmelo Cascone3977ea42019-02-28 13:43:42 -080089 checkNotNull(masterElectionIdStore);
Carmelo Cascone4c289b72019-01-22 15:30:45 -080090
91 this.p4DeviceId = clientKey.p4DeviceId();
Carmelo Cascone4c289b72019-01-22 15:30:45 -080092 this.controller = controller;
93 this.streamClient = new StreamClientImpl(
Carmelo Cascone3977ea42019-02-28 13:43:42 -080094 pipeconfService, masterElectionIdStore, this, controller);
Carmelo Cascone4c289b72019-01-22 15:30:45 -080095 this.pipelineConfigClient = new PipelineConfigClientImpl(this);
96 }
97
98 @Override
99 protected Void doShutdown() {
100 streamClient.closeSession();
101 return super.doShutdown();
102 }
103
104 @Override
105 public CompletableFuture<Boolean> setPipelineConfig(
106 PiPipeconf pipeconf, ByteBuffer deviceData) {
107 return pipelineConfigClient.setPipelineConfig(pipeconf, deviceData);
108 }
109
110 @Override
111 public CompletableFuture<Boolean> isPipelineConfigSet(
112 PiPipeconf pipeconf, ByteBuffer deviceData) {
113 return pipelineConfigClient.isPipelineConfigSet(pipeconf, deviceData);
114 }
115
116 @Override
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800117 public CompletableFuture<Boolean> isAnyPipelineConfigSet() {
118 return pipelineConfigClient.isAnyPipelineConfigSet();
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800119 }
120
121 @Override
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800122 public ReadRequest read(PiPipeconf pipeconf) {
123 return new ReadRequestImpl(this, pipeconf);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800124 }
125
126 @Override
127 public boolean isSessionOpen() {
128 return streamClient.isSessionOpen();
129 }
130
131 @Override
132 public void closeSession() {
133 streamClient.closeSession();
134 }
135
136 @Override
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800137 public void setMastership(boolean master, BigInteger newElectionId) {
138 streamClient.setMastership(master, newElectionId);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800139 }
140
141 @Override
142 public boolean isMaster() {
143 return streamClient.isMaster();
144 }
145
146 @Override
147 public void packetOut(PiPacketOperation packet, PiPipeconf pipeconf) {
148 streamClient.packetOut(packet, pipeconf);
149 }
150
151 @Override
152 public WriteRequest write(PiPipeconf pipeconf) {
153 return new WriteRequestImpl(this, pipeconf);
154 }
155
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800156 @Override
157 public CompletableFuture<Boolean> probeService() {
158 final CompletableFuture<Boolean> future = new CompletableFuture<>();
159 final StreamObserver<GetForwardingPipelineConfigResponse> responseObserver =
160 new StreamObserver<GetForwardingPipelineConfigResponse>() {
161 @Override
162 public void onNext(GetForwardingPipelineConfigResponse value) {
163 future.complete(true);
164 }
165
166 @Override
167 public void onError(Throwable t) {
168 if (Status.fromThrowable(t).getCode() ==
169 Status.Code.FAILED_PRECONDITION) {
170 // Pipeline not set but service is available.
171 future.complete(true);
172 } else {
173 log.debug("", t);
174 }
175 future.complete(false);
176 }
177
178 @Override
179 public void onCompleted() {
180 // Ignore, unary call.
181 }
182 };
183 // Use long timeout as the device might return the full P4 blob
184 // (e.g. server does not support cookie), over a slow network.
185 execRpc(s -> s.getForwardingPipelineConfig(
186 GetForwardingPipelineConfigRequest.newBuilder()
187 .setDeviceId(p4DeviceId)
188 .setResponseType(COOKIE_ONLY)
189 .build(), responseObserver),
190 SHORT_TIMEOUT_SECONDS);
191 return future;
192 }
193
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800194 /**
195 * Returns the P4Runtime-internal device ID associated with this client.
196 *
197 * @return P4Runtime-internal device ID
198 */
199 long p4DeviceId() {
200 return this.p4DeviceId;
201 }
202
203 /**
204 * Returns the ONOS device ID associated with this client.
205 *
206 * @return ONOS device ID
207 */
208 DeviceId deviceId() {
209 return this.deviceId;
210 }
211
212 /**
213 * Returns the election ID last used in a MasterArbitrationUpdate message
214 * sent by the client to the server. No guarantees are given that this is
215 * the current election ID associated to the session, nor that the server
216 * has acknowledged this value as valid.
217 *
218 * @return election ID uint128 protobuf message
219 */
220 P4RuntimeOuterClass.Uint128 lastUsedElectionId() {
221 return streamClient.lastUsedElectionId();
222 }
223
224 /**
225 * Forces execution of an RPC in a cancellable context with the given
226 * timeout (in seconds).
227 *
228 * @param stubConsumer P4Runtime stub consumer
229 * @param timeout timeout in seconds
230 */
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800231 void execRpc(Consumer<P4RuntimeGrpc.P4RuntimeStub> stubConsumer,
232 int timeout) {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800233 if (log.isTraceEnabled()) {
234 log.trace("Executing RPC with timeout {} seconds (context deadline {})...",
235 timeout, context().getDeadline());
236 }
237 runInCancellableContext(() -> stubConsumer.accept(
238 P4RuntimeGrpc.newStub(channel)
239 .withDeadlineAfter(timeout, TimeUnit.SECONDS)));
240 }
241
242 /**
243 * Forces execution of an RPC in a cancellable context with no timeout.
244 *
245 * @param stubConsumer P4Runtime stub consumer
246 */
247 void execRpcNoTimeout(Consumer<P4RuntimeGrpc.P4RuntimeStub> stubConsumer) {
248 if (log.isTraceEnabled()) {
249 log.trace("Executing RPC with no timeout (context deadline {})...",
250 context().getDeadline());
251 }
252 runInCancellableContext(() -> stubConsumer.accept(
253 P4RuntimeGrpc.newStub(channel)));
254 }
255
256 /**
257 * Logs the error and checks it for any condition that might be of interest
258 * for the controller.
259 *
260 * @param throwable throwable
261 * @param opDescription operation description for logging
262 */
263 void handleRpcError(Throwable throwable, String opDescription) {
264 if (throwable instanceof StatusRuntimeException) {
265 final StatusRuntimeException sre = (StatusRuntimeException) throwable;
266 checkGrpcException(sre);
267 final String logMsg;
268 if (sre.getCause() == null) {
269 logMsg = sre.getMessage();
270 } else {
271 logMsg = format("%s (%s)", sre.getMessage(), sre.getCause().toString());
272 }
273 log.warn("Error while performing {} on {}: {}",
274 opDescription, deviceId, logMsg);
275 log.debug("", throwable);
276 return;
277 }
278 log.error(format("Exception while performing %s on %s",
279 opDescription, deviceId), throwable);
280 }
281
282 private void checkGrpcException(StatusRuntimeException sre) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800283 if (sre.getStatus().getCode() == Status.Code.PERMISSION_DENIED) {
284 // Notify upper layers that this node is not master.
285 controller.postEvent(new DeviceAgentEvent(
286 DeviceAgentEvent.Type.NOT_MASTER, deviceId));
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800287 }
288 }
289}