blob: c023f266420405418ed6e929a4c4236cc6987406 [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;
21import io.grpc.stub.StreamObserver;
22import org.onosproject.net.pi.model.PiPipeconf;
23import org.onosproject.p4runtime.api.P4RuntimePipelineConfigClient;
24import org.onosproject.p4runtime.ctl.utils.PipeconfHelper;
25import org.slf4j.Logger;
26import p4.config.v1.P4InfoOuterClass;
27import p4.tmp.P4Config;
28import 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;
35import java.util.concurrent.CompletableFuture;
36
37import static com.google.common.base.Preconditions.checkNotNull;
38import static java.util.concurrent.CompletableFuture.completedFuture;
39import static org.onosproject.p4runtime.ctl.client.P4RuntimeClientImpl.LONG_TIMEOUT_SECONDS;
40import static org.slf4j.LoggerFactory.getLogger;
41import static p4.v1.P4RuntimeOuterClass.GetForwardingPipelineConfigRequest.ResponseType.COOKIE_ONLY;
42import static p4.v1.P4RuntimeOuterClass.SetForwardingPipelineConfigRequest.Action.VERIFY_AND_COMMIT;
43
44/**
45 * Implementation of P4RuntimePipelineConfigClient. Handles pipeline
46 * config-related RPCs.
47 */
48final class PipelineConfigClientImpl implements P4RuntimePipelineConfigClient {
49
50 private static final Logger log = getLogger(PipelineConfigClientImpl.class);
51
52 private static final SetForwardingPipelineConfigResponse DEFAULT_SET_RESPONSE =
53 SetForwardingPipelineConfigResponse.getDefaultInstance();
54
55 private final P4RuntimeClientImpl client;
56
57 PipelineConfigClientImpl(P4RuntimeClientImpl client) {
58 this.client = client;
59 }
60
61 @Override
62 public CompletableFuture<Boolean> setPipelineConfig(
63 PiPipeconf pipeconf, ByteBuffer deviceData) {
64
65 log.info("Setting pipeline config for {} to {}...",
66 client.deviceId(), pipeconf.id());
67
68 checkNotNull(deviceData, "deviceData cannot be null");
69
70 final ForwardingPipelineConfig pipelineConfigMsg =
71 buildForwardingPipelineConfigMsg(pipeconf, deviceData);
72 if (pipelineConfigMsg == null) {
73 // Error logged in buildForwardingPipelineConfigMsg()
74 return completedFuture(false);
75 }
76
77 final SetForwardingPipelineConfigRequest requestMsg =
78 SetForwardingPipelineConfigRequest
79 .newBuilder()
80 .setDeviceId(client.p4DeviceId())
81 .setElectionId(client.lastUsedElectionId())
82 .setAction(VERIFY_AND_COMMIT)
83 .setConfig(pipelineConfigMsg)
84 .build();
85
86 final CompletableFuture<Boolean> future = new CompletableFuture<>();
87 final StreamObserver<SetForwardingPipelineConfigResponse> responseObserver =
88 new StreamObserver<SetForwardingPipelineConfigResponse>() {
89 @Override
90 public void onNext(SetForwardingPipelineConfigResponse value) {
91 if (!DEFAULT_SET_RESPONSE.equals(value)) {
92 log.warn("Received invalid SetForwardingPipelineConfigResponse " +
93 " from {} [{}]",
94 client.deviceId(),
95 TextFormat.shortDebugString(value));
96 future.complete(false);
97 }
98 // All good, pipeline is set.
99 future.complete(true);
100 }
101 @Override
102 public void onError(Throwable t) {
103 client.handleRpcError(t, "SET-pipeline-config");
104 future.complete(false);
105 }
106 @Override
107 public void onCompleted() {
108 // Ignore, unary call.
109 }
110 };
111
112 client.execRpc(
113 s -> s.setForwardingPipelineConfig(requestMsg, responseObserver),
114 LONG_TIMEOUT_SECONDS);
115
116 return future;
117 }
118
119 private ForwardingPipelineConfig buildForwardingPipelineConfigMsg(
120 PiPipeconf pipeconf, ByteBuffer deviceData) {
121
122 final P4InfoOuterClass.P4Info p4Info = PipeconfHelper.getP4Info(pipeconf);
123 if (p4Info == null) {
124 // Problem logged by PipeconfHelper.
125 return null;
126 }
127 final ForwardingPipelineConfig.Cookie cookieMsg =
128 ForwardingPipelineConfig.Cookie
129 .newBuilder()
130 .setCookie(pipeconf.fingerprint())
131 .build();
132 // FIXME: This is specific to PI P4Runtime implementation and should be
133 // moved to driver.
134 final P4Config.P4DeviceConfig p4DeviceConfigMsg = P4Config.P4DeviceConfig
135 .newBuilder()
136 .setExtras(P4Config.P4DeviceConfig.Extras.getDefaultInstance())
137 .setReassign(true)
138 .setDeviceData(ByteString.copyFrom(deviceData))
139 .build();
140 return ForwardingPipelineConfig
141 .newBuilder()
142 .setP4Info(p4Info)
143 .setP4DeviceConfig(p4DeviceConfigMsg.toByteString())
144 .setCookie(cookieMsg)
145 .build();
146 }
147
148
149 @Override
150 public CompletableFuture<Boolean> isPipelineConfigSet(
151 PiPipeconf pipeconf, ByteBuffer expectedDeviceData) {
152 return getPipelineCookieFromServer()
153 .thenApply(cfgFromDevice -> comparePipelineConfig(
154 pipeconf, expectedDeviceData, cfgFromDevice));
155 }
156
157 private boolean comparePipelineConfig(
158 PiPipeconf pipeconf, ByteBuffer expectedDeviceData,
159 ForwardingPipelineConfig cfgFromDevice) {
160 if (cfgFromDevice == null) {
161 return false;
162 }
163
164 final ForwardingPipelineConfig expectedCfg = buildForwardingPipelineConfigMsg(
165 pipeconf, expectedDeviceData);
166 if (expectedCfg == null) {
167 return false;
168 }
169
170 if (cfgFromDevice.hasCookie()) {
171 return cfgFromDevice.getCookie().getCookie() == pipeconf.fingerprint();
172 }
173
174 // No cookie.
175 log.warn("{} returned GetForwardingPipelineConfigResponse " +
176 "with 'cookie' field unset. " +
177 "Will try by comparing 'device_data' and 'p4_info'...",
178 client.deviceId());
179
180 if (cfgFromDevice.getP4DeviceConfig().isEmpty()
181 && !expectedCfg.getP4DeviceConfig().isEmpty()) {
182 // Don't bother with a warn or error since we don't really allow
183 // updating the P4 blob to a different one without changing the
184 // P4Info. I.e, comparing just the P4Info should be enough for us.
185 log.debug("{} returned GetForwardingPipelineConfigResponse " +
186 "with empty 'p4_device_config' field, " +
187 "equality will be based only on P4Info",
188 client.deviceId());
189 return cfgFromDevice.getP4Info().equals(expectedCfg.getP4Info());
190 }
191
192 return cfgFromDevice.getP4DeviceConfig()
193 .equals(expectedCfg.getP4DeviceConfig())
194 && cfgFromDevice.getP4Info()
195 .equals(expectedCfg.getP4Info());
196 }
197
198 private CompletableFuture<ForwardingPipelineConfig> getPipelineCookieFromServer() {
199 final GetForwardingPipelineConfigRequest request =
200 GetForwardingPipelineConfigRequest
201 .newBuilder()
202 .setDeviceId(client.p4DeviceId())
203 .setResponseType(COOKIE_ONLY)
204 .build();
205 final CompletableFuture<ForwardingPipelineConfig> future = new CompletableFuture<>();
206 final StreamObserver<GetForwardingPipelineConfigResponse> responseObserver =
207 new StreamObserver<GetForwardingPipelineConfigResponse>() {
208 @Override
209 public void onNext(GetForwardingPipelineConfigResponse value) {
210 if (value.hasConfig()) {
211 future.complete(value.getConfig());
212 } else {
213 log.warn("{} returned {} with 'config' field unset",
214 client.deviceId(), value.getClass().getSimpleName());
215 }
216 future.complete(null);
217 }
218
219 @Override
220 public void onError(Throwable t) {
221 client.handleRpcError(t, "GET-pipeline-config");
222 future.complete(null);
223 }
224
225 @Override
226 public void onCompleted() {
227 // Ignore, unary call.
228 }
229 };
230 // Use long timeout as the device might return the full P4 blob
231 // (e.g. server does not support cookie), over a slow network.
232 client.execRpc(
233 s -> s.getForwardingPipelineConfig(request, responseObserver),
234 LONG_TIMEOUT_SECONDS);
235 return future;
236 }
237}