blob: c7e21cbdd0d407b0796085d9113ca6a657e7745d [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 com.google.protobuf.ByteString;
20import com.google.protobuf.TextFormat;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080021import io.grpc.Status;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080022import io.grpc.stub.StreamObserver;
23import org.onosproject.net.pi.model.PiPipeconf;
24import org.onosproject.p4runtime.api.P4RuntimePipelineConfigClient;
25import org.onosproject.p4runtime.ctl.utils.PipeconfHelper;
26import org.slf4j.Logger;
27import p4.config.v1.P4InfoOuterClass;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080028import p4.v1.P4RuntimeOuterClass.ForwardingPipelineConfig;
29import p4.v1.P4RuntimeOuterClass.GetForwardingPipelineConfigRequest;
30import p4.v1.P4RuntimeOuterClass.GetForwardingPipelineConfigResponse;
31import p4.v1.P4RuntimeOuterClass.SetForwardingPipelineConfigRequest;
32import p4.v1.P4RuntimeOuterClass.SetForwardingPipelineConfigResponse;
33
34import java.nio.ByteBuffer;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080035import java.util.Objects;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080036import java.util.concurrent.CompletableFuture;
37
38import static com.google.common.base.Preconditions.checkNotNull;
39import static java.util.concurrent.CompletableFuture.completedFuture;
40import static org.onosproject.p4runtime.ctl.client.P4RuntimeClientImpl.LONG_TIMEOUT_SECONDS;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080041import static org.onosproject.p4runtime.ctl.client.P4RuntimeClientImpl.SHORT_TIMEOUT_SECONDS;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080042import static org.slf4j.LoggerFactory.getLogger;
43import static p4.v1.P4RuntimeOuterClass.GetForwardingPipelineConfigRequest.ResponseType.COOKIE_ONLY;
44import static p4.v1.P4RuntimeOuterClass.SetForwardingPipelineConfigRequest.Action.VERIFY_AND_COMMIT;
45
46/**
47 * Implementation of P4RuntimePipelineConfigClient. Handles pipeline
48 * config-related RPCs.
49 */
50final class PipelineConfigClientImpl implements P4RuntimePipelineConfigClient {
51
52 private static final Logger log = getLogger(PipelineConfigClientImpl.class);
53
54 private static final SetForwardingPipelineConfigResponse DEFAULT_SET_RESPONSE =
55 SetForwardingPipelineConfigResponse.getDefaultInstance();
56
57 private final P4RuntimeClientImpl client;
58
59 PipelineConfigClientImpl(P4RuntimeClientImpl client) {
60 this.client = client;
61 }
62
63 @Override
64 public CompletableFuture<Boolean> setPipelineConfig(
Carmelo Casconec2be50a2019-04-10 00:15:39 -070065 long p4DeviceId, PiPipeconf pipeconf, ByteBuffer deviceData) {
Carmelo Cascone4c289b72019-01-22 15:30:45 -080066
Carmelo Casconec2be50a2019-04-10 00:15:39 -070067 if (!client.isSessionOpen(p4DeviceId)) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -080068 log.warn("Dropping set pipeline config request for {}, session is CLOSED",
69 client.deviceId());
70 return completedFuture(false);
71 }
72
Carmelo Cascone4c289b72019-01-22 15:30:45 -080073 log.info("Setting pipeline config for {} to {}...",
74 client.deviceId(), pipeconf.id());
75
76 checkNotNull(deviceData, "deviceData cannot be null");
77
78 final ForwardingPipelineConfig pipelineConfigMsg =
79 buildForwardingPipelineConfigMsg(pipeconf, deviceData);
80 if (pipelineConfigMsg == null) {
81 // Error logged in buildForwardingPipelineConfigMsg()
82 return completedFuture(false);
83 }
84
85 final SetForwardingPipelineConfigRequest requestMsg =
86 SetForwardingPipelineConfigRequest
87 .newBuilder()
Carmelo Casconec2be50a2019-04-10 00:15:39 -070088 .setDeviceId(p4DeviceId)
89 .setElectionId(client.lastUsedElectionId(p4DeviceId))
Carmelo Cascone4c289b72019-01-22 15:30:45 -080090 .setAction(VERIFY_AND_COMMIT)
91 .setConfig(pipelineConfigMsg)
92 .build();
93
94 final CompletableFuture<Boolean> future = new CompletableFuture<>();
95 final StreamObserver<SetForwardingPipelineConfigResponse> responseObserver =
96 new StreamObserver<SetForwardingPipelineConfigResponse>() {
97 @Override
98 public void onNext(SetForwardingPipelineConfigResponse value) {
99 if (!DEFAULT_SET_RESPONSE.equals(value)) {
100 log.warn("Received invalid SetForwardingPipelineConfigResponse " +
101 " from {} [{}]",
102 client.deviceId(),
103 TextFormat.shortDebugString(value));
104 future.complete(false);
105 }
106 // All good, pipeline is set.
107 future.complete(true);
108 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800109
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800110 @Override
111 public void onError(Throwable t) {
112 client.handleRpcError(t, "SET-pipeline-config");
113 future.complete(false);
114 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800115
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800116 @Override
117 public void onCompleted() {
118 // Ignore, unary call.
119 }
120 };
121
122 client.execRpc(
123 s -> s.setForwardingPipelineConfig(requestMsg, responseObserver),
124 LONG_TIMEOUT_SECONDS);
125
126 return future;
127 }
128
129 private ForwardingPipelineConfig buildForwardingPipelineConfigMsg(
130 PiPipeconf pipeconf, ByteBuffer deviceData) {
131
132 final P4InfoOuterClass.P4Info p4Info = PipeconfHelper.getP4Info(pipeconf);
133 if (p4Info == null) {
134 // Problem logged by PipeconfHelper.
135 return null;
136 }
137 final ForwardingPipelineConfig.Cookie cookieMsg =
138 ForwardingPipelineConfig.Cookie
139 .newBuilder()
140 .setCookie(pipeconf.fingerprint())
141 .build();
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800142 return ForwardingPipelineConfig
143 .newBuilder()
144 .setP4Info(p4Info)
Yi Tsengb81121f2019-05-07 16:51:05 -0700145 .setP4DeviceConfig(deviceData != null
146 ? ByteString.copyFrom(deviceData)
147 : ByteString.EMPTY)
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800148 .setCookie(cookieMsg)
149 .build();
150 }
151
152
153 @Override
154 public CompletableFuture<Boolean> isPipelineConfigSet(
Carmelo Casconeadb89052019-04-17 20:02:33 -0700155 long p4DeviceId, PiPipeconf pipeconf) {
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700156 return getPipelineCookieFromServer(p4DeviceId)
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800157 .thenApply(cfgFromDevice -> comparePipelineConfig(
Carmelo Casconeadb89052019-04-17 20:02:33 -0700158 pipeconf, cfgFromDevice));
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800159 }
160
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800161 @Override
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700162 public CompletableFuture<Boolean> isAnyPipelineConfigSet(long p4DeviceId) {
163 return getPipelineCookieFromServer(p4DeviceId).thenApply(Objects::nonNull);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800164 }
165
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800166 private boolean comparePipelineConfig(
Carmelo Casconeadb89052019-04-17 20:02:33 -0700167 PiPipeconf pipeconf, ForwardingPipelineConfig cfgFromDevice) {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800168 if (cfgFromDevice == null) {
Charles Chan53c361e2021-01-09 08:05:57 +0000169 log.debug("Failed to comparePipelineConfig. cfgFromDevice is null");
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800170 return false;
171 }
172
173 final ForwardingPipelineConfig expectedCfg = buildForwardingPipelineConfigMsg(
Carmelo Casconeadb89052019-04-17 20:02:33 -0700174 pipeconf, null);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800175 if (expectedCfg == null) {
Charles Chan53c361e2021-01-09 08:05:57 +0000176 // Problem logged by buildForwardingPipelineConfigMsg
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800177 return false;
178 }
179
180 if (cfgFromDevice.hasCookie()) {
Charles Chan53c361e2021-01-09 08:05:57 +0000181 log.debug("Cookie from device = {}", cfgFromDevice.getCookie().getCookie());
182 log.debug("Pipeconf fingerprint = {}", pipeconf.fingerprint());
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800183 return cfgFromDevice.getCookie().getCookie() == pipeconf.fingerprint();
184 }
185
186 // No cookie.
187 log.warn("{} returned GetForwardingPipelineConfigResponse " +
188 "with 'cookie' field unset. " +
Carmelo Casconeadb89052019-04-17 20:02:33 -0700189 "Will try by comparing 'p4_info'...",
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800190 client.deviceId());
191
Carmelo Casconeadb89052019-04-17 20:02:33 -0700192 return cfgFromDevice.hasP4Info() && expectedCfg.hasP4Info() &&
193 cfgFromDevice.getP4Info().equals(expectedCfg.getP4Info());
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800194 }
195
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700196 private CompletableFuture<ForwardingPipelineConfig> getPipelineCookieFromServer(
197 long p4DeviceId) {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800198 final GetForwardingPipelineConfigRequest request =
199 GetForwardingPipelineConfigRequest
200 .newBuilder()
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700201 .setDeviceId(p4DeviceId)
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800202 .setResponseType(COOKIE_ONLY)
203 .build();
204 final CompletableFuture<ForwardingPipelineConfig> future = new CompletableFuture<>();
205 final StreamObserver<GetForwardingPipelineConfigResponse> responseObserver =
206 new StreamObserver<GetForwardingPipelineConfigResponse>() {
207 @Override
208 public void onNext(GetForwardingPipelineConfigResponse value) {
209 if (value.hasConfig()) {
210 future.complete(value.getConfig());
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800211 if (!value.getConfig().getP4DeviceConfig().isEmpty()) {
212 log.warn("{} returned GetForwardingPipelineConfigResponse " +
213 "with p4_device_config field set " +
214 "({} bytes), but we requested COOKIE_ONLY",
215 client.deviceId(),
216 value.getConfig().getP4DeviceConfig().size());
217 }
218 if (value.getConfig().hasP4Info()) {
219 log.warn("{} returned GetForwardingPipelineConfigResponse " +
220 "with p4_info field set " +
221 "({} bytes), but we requested COOKIE_ONLY",
222 client.deviceId(),
223 value.getConfig().getP4Info().getSerializedSize());
224 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800225 } else {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800226 future.complete(null);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800227 log.warn("{} returned {} with 'config' field unset",
228 client.deviceId(), value.getClass().getSimpleName());
229 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800230 }
231
232 @Override
233 public void onError(Throwable t) {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800234 future.complete(null);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800235 if (Status.fromThrowable(t).getCode() ==
236 Status.Code.FAILED_PRECONDITION) {
237 // FAILED_PRECONDITION means that a pipeline
238 // config was not set in the first place, don't
239 // bother logging.
240 return;
241 }
242 client.handleRpcError(t, "GET-pipeline-config");
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800243 }
244
245 @Override
246 public void onCompleted() {
247 // Ignore, unary call.
248 }
249 };
250 // Use long timeout as the device might return the full P4 blob
251 // (e.g. server does not support cookie), over a slow network.
252 client.execRpc(
253 s -> s.getForwardingPipelineConfig(request, responseObserver),
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800254 SHORT_TIMEOUT_SECONDS);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800255 return future;
256 }
257}