blob: b0b6c57489c886437158f031844c6708f2d4b999 [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;
28import p4.tmp.P4Config;
29import p4.v1.P4RuntimeOuterClass.ForwardingPipelineConfig;
30import p4.v1.P4RuntimeOuterClass.GetForwardingPipelineConfigRequest;
31import p4.v1.P4RuntimeOuterClass.GetForwardingPipelineConfigResponse;
32import p4.v1.P4RuntimeOuterClass.SetForwardingPipelineConfigRequest;
33import p4.v1.P4RuntimeOuterClass.SetForwardingPipelineConfigResponse;
34
35import java.nio.ByteBuffer;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080036import java.util.Objects;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080037import java.util.concurrent.CompletableFuture;
38
39import static com.google.common.base.Preconditions.checkNotNull;
40import static java.util.concurrent.CompletableFuture.completedFuture;
41import static org.onosproject.p4runtime.ctl.client.P4RuntimeClientImpl.LONG_TIMEOUT_SECONDS;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080042import static org.onosproject.p4runtime.ctl.client.P4RuntimeClientImpl.SHORT_TIMEOUT_SECONDS;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080043import static org.slf4j.LoggerFactory.getLogger;
44import static p4.v1.P4RuntimeOuterClass.GetForwardingPipelineConfigRequest.ResponseType.COOKIE_ONLY;
45import static p4.v1.P4RuntimeOuterClass.SetForwardingPipelineConfigRequest.Action.VERIFY_AND_COMMIT;
46
47/**
48 * Implementation of P4RuntimePipelineConfigClient. Handles pipeline
49 * config-related RPCs.
50 */
51final class PipelineConfigClientImpl implements P4RuntimePipelineConfigClient {
52
53 private static final Logger log = getLogger(PipelineConfigClientImpl.class);
54
55 private static final SetForwardingPipelineConfigResponse DEFAULT_SET_RESPONSE =
56 SetForwardingPipelineConfigResponse.getDefaultInstance();
57
58 private final P4RuntimeClientImpl client;
59
60 PipelineConfigClientImpl(P4RuntimeClientImpl client) {
61 this.client = client;
62 }
63
64 @Override
65 public CompletableFuture<Boolean> setPipelineConfig(
66 PiPipeconf pipeconf, ByteBuffer deviceData) {
67
Carmelo Cascone3977ea42019-02-28 13:43:42 -080068 if (!client.isSessionOpen()) {
69 log.warn("Dropping set pipeline config request for {}, session is CLOSED",
70 client.deviceId());
71 return completedFuture(false);
72 }
73
Carmelo Cascone4c289b72019-01-22 15:30:45 -080074 log.info("Setting pipeline config for {} to {}...",
75 client.deviceId(), pipeconf.id());
76
77 checkNotNull(deviceData, "deviceData cannot be null");
78
79 final ForwardingPipelineConfig pipelineConfigMsg =
80 buildForwardingPipelineConfigMsg(pipeconf, deviceData);
81 if (pipelineConfigMsg == null) {
82 // Error logged in buildForwardingPipelineConfigMsg()
83 return completedFuture(false);
84 }
85
86 final SetForwardingPipelineConfigRequest requestMsg =
87 SetForwardingPipelineConfigRequest
88 .newBuilder()
89 .setDeviceId(client.p4DeviceId())
90 .setElectionId(client.lastUsedElectionId())
91 .setAction(VERIFY_AND_COMMIT)
92 .setConfig(pipelineConfigMsg)
93 .build();
94
95 final CompletableFuture<Boolean> future = new CompletableFuture<>();
96 final StreamObserver<SetForwardingPipelineConfigResponse> responseObserver =
97 new StreamObserver<SetForwardingPipelineConfigResponse>() {
98 @Override
99 public void onNext(SetForwardingPipelineConfigResponse value) {
100 if (!DEFAULT_SET_RESPONSE.equals(value)) {
101 log.warn("Received invalid SetForwardingPipelineConfigResponse " +
102 " from {} [{}]",
103 client.deviceId(),
104 TextFormat.shortDebugString(value));
105 future.complete(false);
106 }
107 // All good, pipeline is set.
108 future.complete(true);
109 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800110
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800111 @Override
112 public void onError(Throwable t) {
113 client.handleRpcError(t, "SET-pipeline-config");
114 future.complete(false);
115 }
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800116
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800117 @Override
118 public void onCompleted() {
119 // Ignore, unary call.
120 }
121 };
122
123 client.execRpc(
124 s -> s.setForwardingPipelineConfig(requestMsg, responseObserver),
125 LONG_TIMEOUT_SECONDS);
126
127 return future;
128 }
129
130 private ForwardingPipelineConfig buildForwardingPipelineConfigMsg(
131 PiPipeconf pipeconf, ByteBuffer deviceData) {
132
133 final P4InfoOuterClass.P4Info p4Info = PipeconfHelper.getP4Info(pipeconf);
134 if (p4Info == null) {
135 // Problem logged by PipeconfHelper.
136 return null;
137 }
138 final ForwardingPipelineConfig.Cookie cookieMsg =
139 ForwardingPipelineConfig.Cookie
140 .newBuilder()
141 .setCookie(pipeconf.fingerprint())
142 .build();
143 // FIXME: This is specific to PI P4Runtime implementation and should be
144 // moved to driver.
145 final P4Config.P4DeviceConfig p4DeviceConfigMsg = P4Config.P4DeviceConfig
146 .newBuilder()
147 .setExtras(P4Config.P4DeviceConfig.Extras.getDefaultInstance())
148 .setReassign(true)
149 .setDeviceData(ByteString.copyFrom(deviceData))
150 .build();
151 return ForwardingPipelineConfig
152 .newBuilder()
153 .setP4Info(p4Info)
154 .setP4DeviceConfig(p4DeviceConfigMsg.toByteString())
155 .setCookie(cookieMsg)
156 .build();
157 }
158
159
160 @Override
161 public CompletableFuture<Boolean> isPipelineConfigSet(
162 PiPipeconf pipeconf, ByteBuffer expectedDeviceData) {
163 return getPipelineCookieFromServer()
164 .thenApply(cfgFromDevice -> comparePipelineConfig(
165 pipeconf, expectedDeviceData, cfgFromDevice));
166 }
167
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800168 @Override
169 public CompletableFuture<Boolean> isAnyPipelineConfigSet() {
170 return getPipelineCookieFromServer().thenApply(Objects::nonNull);
171 }
172
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800173 private boolean comparePipelineConfig(
174 PiPipeconf pipeconf, ByteBuffer expectedDeviceData,
175 ForwardingPipelineConfig cfgFromDevice) {
176 if (cfgFromDevice == null) {
177 return false;
178 }
179
180 final ForwardingPipelineConfig expectedCfg = buildForwardingPipelineConfigMsg(
181 pipeconf, expectedDeviceData);
182 if (expectedCfg == null) {
183 return false;
184 }
185
186 if (cfgFromDevice.hasCookie()) {
187 return cfgFromDevice.getCookie().getCookie() == pipeconf.fingerprint();
188 }
189
190 // No cookie.
191 log.warn("{} returned GetForwardingPipelineConfigResponse " +
192 "with 'cookie' field unset. " +
193 "Will try by comparing 'device_data' and 'p4_info'...",
194 client.deviceId());
195
196 if (cfgFromDevice.getP4DeviceConfig().isEmpty()
197 && !expectedCfg.getP4DeviceConfig().isEmpty()) {
198 // Don't bother with a warn or error since we don't really allow
199 // updating the P4 blob to a different one without changing the
200 // P4Info. I.e, comparing just the P4Info should be enough for us.
201 log.debug("{} returned GetForwardingPipelineConfigResponse " +
202 "with empty 'p4_device_config' field, " +
203 "equality will be based only on P4Info",
204 client.deviceId());
205 return cfgFromDevice.getP4Info().equals(expectedCfg.getP4Info());
206 }
207
208 return cfgFromDevice.getP4DeviceConfig()
209 .equals(expectedCfg.getP4DeviceConfig())
210 && cfgFromDevice.getP4Info()
211 .equals(expectedCfg.getP4Info());
212 }
213
214 private CompletableFuture<ForwardingPipelineConfig> getPipelineCookieFromServer() {
215 final GetForwardingPipelineConfigRequest request =
216 GetForwardingPipelineConfigRequest
217 .newBuilder()
218 .setDeviceId(client.p4DeviceId())
219 .setResponseType(COOKIE_ONLY)
220 .build();
221 final CompletableFuture<ForwardingPipelineConfig> future = new CompletableFuture<>();
222 final StreamObserver<GetForwardingPipelineConfigResponse> responseObserver =
223 new StreamObserver<GetForwardingPipelineConfigResponse>() {
224 @Override
225 public void onNext(GetForwardingPipelineConfigResponse value) {
226 if (value.hasConfig()) {
227 future.complete(value.getConfig());
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800228 if (!value.getConfig().getP4DeviceConfig().isEmpty()) {
229 log.warn("{} returned GetForwardingPipelineConfigResponse " +
230 "with p4_device_config field set " +
231 "({} bytes), but we requested COOKIE_ONLY",
232 client.deviceId(),
233 value.getConfig().getP4DeviceConfig().size());
234 }
235 if (value.getConfig().hasP4Info()) {
236 log.warn("{} returned GetForwardingPipelineConfigResponse " +
237 "with p4_info field set " +
238 "({} bytes), but we requested COOKIE_ONLY",
239 client.deviceId(),
240 value.getConfig().getP4Info().getSerializedSize());
241 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800242 } else {
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800243 future.complete(null);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800244 log.warn("{} returned {} with 'config' field unset",
245 client.deviceId(), value.getClass().getSimpleName());
246 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800247 }
248
249 @Override
250 public void onError(Throwable t) {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800251 future.complete(null);
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800252 if (Status.fromThrowable(t).getCode() ==
253 Status.Code.FAILED_PRECONDITION) {
254 // FAILED_PRECONDITION means that a pipeline
255 // config was not set in the first place, don't
256 // bother logging.
257 return;
258 }
259 client.handleRpcError(t, "GET-pipeline-config");
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800260 }
261
262 @Override
263 public void onCompleted() {
264 // Ignore, unary call.
265 }
266 };
267 // Use long timeout as the device might return the full P4 blob
268 // (e.g. server does not support cookie), over a slow network.
269 client.execRpc(
270 s -> s.getForwardingPipelineConfig(request, responseObserver),
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800271 SHORT_TIMEOUT_SECONDS);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800272 return future;
273 }
274}