blob: 54d284dbc5dfa9b5db7f1aa654bdf6a3f3f3043b [file] [log] [blame]
Carmelo Casconef7aa3f92017-07-06 23:56:50 -04001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Carmelo Casconef7aa3f92017-07-06 23:56:50 -04003 *
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;
18
Carmelo Cascone87b9b392017-10-02 18:33:20 +020019import com.google.common.collect.HashMultimap;
Carmelo Cascone8d99b172017-07-18 17:26:31 -040020import com.google.common.collect.ImmutableMap;
Yi Tseng82512da2017-08-16 19:46:36 -070021import com.google.common.collect.Lists;
Yi Tseng82512da2017-08-16 19:46:36 -070022import com.google.common.collect.Multimap;
Carmelo Cascone50d195f2018-09-11 13:26:38 -070023import com.google.common.collect.Sets;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040024import com.google.protobuf.ByteString;
Yi Tsenge67e1412018-01-31 17:35:20 -080025import com.google.protobuf.InvalidProtocolBufferException;
Carmelo Cascone59f57de2017-07-11 19:55:09 -040026import io.grpc.Context;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040027import io.grpc.ManagedChannel;
Carmelo Cascone943c6642018-09-11 13:01:03 -070028import io.grpc.Metadata;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040029import io.grpc.Status;
30import io.grpc.StatusRuntimeException;
Carmelo Cascone943c6642018-09-11 13:01:03 -070031import io.grpc.protobuf.lite.ProtoLiteUtils;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -070032import io.grpc.stub.ClientCallStreamObserver;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040033import io.grpc.stub.StreamObserver;
Andrea Campanella288b2732017-07-28 14:16:16 +020034import org.onlab.osgi.DefaultServiceDirectory;
Carmelo Casconee5b28722018-06-22 17:28:28 +020035import org.onlab.util.SharedExecutors;
Yi Tseng82512da2017-08-16 19:46:36 -070036import org.onlab.util.Tools;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040037import org.onosproject.net.DeviceId;
Carmelo Cascone87892e22017-11-13 16:01:29 -080038import org.onosproject.net.pi.model.PiActionProfileId;
39import org.onosproject.net.pi.model.PiCounterId;
Carmelo Cascone81929aa2018-04-07 01:38:55 -070040import org.onosproject.net.pi.model.PiMeterId;
Andrea Campanella432f7182017-07-14 18:43:27 +020041import org.onosproject.net.pi.model.PiPipeconf;
Carmelo Cascone87892e22017-11-13 16:01:29 -080042import org.onosproject.net.pi.model.PiTableId;
Carmelo Cascone87b9b392017-10-02 18:33:20 +020043import org.onosproject.net.pi.runtime.PiActionGroup;
44import org.onosproject.net.pi.runtime.PiActionGroupMember;
Carmelo Casconee44592f2018-09-12 02:24:47 -070045import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
Carmelo Casconeb045ddc2017-09-01 01:26:35 +020046import org.onosproject.net.pi.runtime.PiCounterCellData;
47import org.onosproject.net.pi.runtime.PiCounterCellId;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090048import org.onosproject.net.pi.runtime.PiMeterCellConfig;
49import org.onosproject.net.pi.runtime.PiMeterCellId;
Carmelo Cascone58136812018-07-19 03:40:16 +020050import org.onosproject.net.pi.runtime.PiMulticastGroupEntry;
Andrea Campanella432f7182017-07-14 18:43:27 +020051import org.onosproject.net.pi.runtime.PiPacketOperation;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040052import org.onosproject.net.pi.runtime.PiTableEntry;
Yi Tsenge67e1412018-01-31 17:35:20 -080053import org.onosproject.net.pi.service.PiPipeconfService;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040054import org.onosproject.p4runtime.api.P4RuntimeClient;
55import org.onosproject.p4runtime.api.P4RuntimeEvent;
56import org.slf4j.Logger;
Carmelo Casconee5b28722018-06-22 17:28:28 +020057import p4.config.v1.P4InfoOuterClass.P4Info;
58import p4.tmp.P4Config;
Carmelo Cascone6af4e172018-06-15 16:01:30 +020059import p4.v1.P4RuntimeGrpc;
60import p4.v1.P4RuntimeOuterClass;
61import p4.v1.P4RuntimeOuterClass.ActionProfileGroup;
62import p4.v1.P4RuntimeOuterClass.ActionProfileMember;
63import p4.v1.P4RuntimeOuterClass.Entity;
64import p4.v1.P4RuntimeOuterClass.ForwardingPipelineConfig;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -070065import p4.v1.P4RuntimeOuterClass.GetForwardingPipelineConfigRequest;
66import p4.v1.P4RuntimeOuterClass.GetForwardingPipelineConfigResponse;
Carmelo Cascone6af4e172018-06-15 16:01:30 +020067import p4.v1.P4RuntimeOuterClass.MasterArbitrationUpdate;
Carmelo Cascone58136812018-07-19 03:40:16 +020068import p4.v1.P4RuntimeOuterClass.MulticastGroupEntry;
69import p4.v1.P4RuntimeOuterClass.PacketReplicationEngineEntry;
Carmelo Cascone6af4e172018-06-15 16:01:30 +020070import p4.v1.P4RuntimeOuterClass.ReadRequest;
71import p4.v1.P4RuntimeOuterClass.ReadResponse;
72import p4.v1.P4RuntimeOuterClass.SetForwardingPipelineConfigRequest;
73import p4.v1.P4RuntimeOuterClass.StreamMessageRequest;
74import p4.v1.P4RuntimeOuterClass.StreamMessageResponse;
75import p4.v1.P4RuntimeOuterClass.TableEntry;
76import p4.v1.P4RuntimeOuterClass.Uint128;
77import p4.v1.P4RuntimeOuterClass.Update;
78import p4.v1.P4RuntimeOuterClass.WriteRequest;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040079
Carmelo Casconee5b28722018-06-22 17:28:28 +020080import java.math.BigInteger;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -070081import java.net.ConnectException;
Carmelo Casconed61fdb32017-10-30 10:09:57 -070082import java.nio.ByteBuffer;
Carmelo Cascone8d99b172017-07-18 17:26:31 -040083import java.util.Collections;
84import java.util.Iterator;
85import java.util.List;
86import java.util.Map;
Carmelo Cascone87b9b392017-10-02 18:33:20 +020087import java.util.Objects;
Carmelo Casconeb045ddc2017-09-01 01:26:35 +020088import java.util.Set;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040089import java.util.concurrent.CompletableFuture;
Carmelo Cascone8d99b172017-07-18 17:26:31 -040090import java.util.concurrent.Executor;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040091import java.util.concurrent.ExecutorService;
Carmelo Cascone8d99b172017-07-18 17:26:31 -040092import java.util.concurrent.Executors;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040093import java.util.concurrent.TimeUnit;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -070094import java.util.concurrent.atomic.AtomicBoolean;
Carmelo Cascone8d99b172017-07-18 17:26:31 -040095import java.util.concurrent.locks.Lock;
96import java.util.concurrent.locks.ReentrantLock;
97import java.util.function.Supplier;
98import java.util.stream.Collectors;
99import java.util.stream.StreamSupport;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400100
Carmelo Casconed61fdb32017-10-30 10:09:57 -0700101import static com.google.common.base.Preconditions.checkNotNull;
Carmelo Cascone5bc7e102018-02-18 18:27:55 -0800102import static java.lang.String.format;
Carmelo Casconee44592f2018-09-12 02:24:47 -0700103import static java.util.Collections.singletonList;
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400104import static org.onlab.util.Tools.groupedThreads;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400105import static org.slf4j.LoggerFactory.getLogger;
Carmelo Cascone6af4e172018-06-15 16:01:30 +0200106import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.ACTION_PROFILE_GROUP;
107import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.ACTION_PROFILE_MEMBER;
Carmelo Cascone58136812018-07-19 03:40:16 +0200108import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.PACKET_REPLICATION_ENGINE_ENTRY;
Carmelo Cascone6af4e172018-06-15 16:01:30 +0200109import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.TABLE_ENTRY;
Carmelo Casconee5b28722018-06-22 17:28:28 +0200110import static p4.v1.P4RuntimeOuterClass.PacketIn;
Carmelo Cascone6af4e172018-06-15 16:01:30 +0200111import static p4.v1.P4RuntimeOuterClass.PacketOut;
Carmelo Cascone58136812018-07-19 03:40:16 +0200112import static p4.v1.P4RuntimeOuterClass.PacketReplicationEngineEntry.TypeCase.MULTICAST_GROUP_ENTRY;
Carmelo Cascone6af4e172018-06-15 16:01:30 +0200113import static p4.v1.P4RuntimeOuterClass.SetForwardingPipelineConfigRequest.Action.VERIFY_AND_COMMIT;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400114
115/**
116 * Implementation of a P4Runtime client.
117 */
Carmelo Casconee5b28722018-06-22 17:28:28 +0200118final class P4RuntimeClientImpl implements P4RuntimeClient {
119
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200120 // Timeout in seconds to obtain the request lock.
121 private static final int LOCK_TIMEOUT = 60;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400122
Carmelo Cascone943c6642018-09-11 13:01:03 -0700123 private static final Metadata.Key<com.google.rpc.Status> STATUS_DETAILS_KEY =
124 Metadata.Key.of("grpc-status-details-bin",
125 ProtoLiteUtils.metadataMarshaller(
126 com.google.rpc.Status.getDefaultInstance()));
127
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400128 private static final Map<WriteOperationType, Update.Type> UPDATE_TYPES = ImmutableMap.of(
129 WriteOperationType.UNSPECIFIED, Update.Type.UNSPECIFIED,
130 WriteOperationType.INSERT, Update.Type.INSERT,
131 WriteOperationType.MODIFY, Update.Type.MODIFY,
132 WriteOperationType.DELETE, Update.Type.DELETE
133 );
134
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400135 private final Logger log = getLogger(getClass());
136
Carmelo Casconee5b28722018-06-22 17:28:28 +0200137 private final Lock requestLock = new ReentrantLock();
138 private final Context.CancellableContext cancellableContext =
139 Context.current().withCancellation();
140
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400141 private final DeviceId deviceId;
Carmelo Casconef423bec2017-08-30 01:56:25 +0200142 private final long p4DeviceId;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400143 private final P4RuntimeControllerImpl controller;
144 private final P4RuntimeGrpc.P4RuntimeBlockingStub blockingStub;
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400145 private final ExecutorService executorService;
146 private final Executor contextExecutor;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700147 private StreamChannelManager streamChannelManager;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400148
Carmelo Casconee5b28722018-06-22 17:28:28 +0200149 // Used by this client for write requests.
150 private Uint128 clientElectionId = Uint128.newBuilder().setLow(1).build();
Yi Tseng3e7f1452017-10-20 10:31:53 -0700151
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700152 private final AtomicBoolean isClientMaster = new AtomicBoolean(false);
153
Yi Tseng82512da2017-08-16 19:46:36 -0700154 /**
155 * Default constructor.
156 *
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200157 * @param deviceId the ONOS device id
Yi Tseng82512da2017-08-16 19:46:36 -0700158 * @param p4DeviceId the P4 device id
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200159 * @param channel gRPC channel
Yi Tseng82512da2017-08-16 19:46:36 -0700160 * @param controller runtime client controller
161 */
Carmelo Casconef423bec2017-08-30 01:56:25 +0200162 P4RuntimeClientImpl(DeviceId deviceId, long p4DeviceId, ManagedChannel channel,
163 P4RuntimeControllerImpl controller) {
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400164 this.deviceId = deviceId;
165 this.p4DeviceId = p4DeviceId;
166 this.controller = controller;
Carmelo Casconea966c342017-07-30 01:56:30 -0400167 this.executorService = Executors.newFixedThreadPool(15, groupedThreads(
Carmelo Casconee5b28722018-06-22 17:28:28 +0200168 "onos-p4runtime-client-" + deviceId.toString(), "%d"));
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400169 this.contextExecutor = this.cancellableContext.fixedContextExecutor(executorService);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200170 //TODO Investigate use of stub deadlines instead of timeout in supplyInContext
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +0200171 this.blockingStub = P4RuntimeGrpc.newBlockingStub(channel);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700172 this.streamChannelManager = new StreamChannelManager(channel);
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400173 }
174
175 /**
Carmelo Cascone58136812018-07-19 03:40:16 +0200176 * Submits a task for async execution via the given executor. All tasks
177 * submitted with this method will be executed sequentially.
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400178 */
Carmelo Casconee5b28722018-06-22 17:28:28 +0200179 private <U> CompletableFuture<U> supplyWithExecutor(
180 Supplier<U> supplier, String opDescription, Executor executor) {
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400181 return CompletableFuture.supplyAsync(() -> {
182 // TODO: explore a more relaxed locking strategy.
Carmelo Casconee5b28722018-06-22 17:28:28 +0200183 try {
184 if (!requestLock.tryLock(LOCK_TIMEOUT, TimeUnit.SECONDS)) {
185 log.error("LOCK TIMEOUT! This is likely a deadlock, "
186 + "please debug (executing {})",
187 opDescription);
188 throw new IllegalThreadStateException("Lock timeout");
189 }
190 } catch (InterruptedException e) {
191 log.warn("Thread interrupted while waiting for lock (executing {})",
192 opDescription);
Ray Milkeydbd38212018-07-02 09:18:09 -0700193 throw new IllegalStateException(e);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200194 }
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400195 try {
196 return supplier.get();
Yi Tsenge67e1412018-01-31 17:35:20 -0800197 } catch (StatusRuntimeException ex) {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200198 log.warn("Unable to execute {} on {}: {}",
199 opDescription, deviceId, ex.toString());
Yi Tsenge67e1412018-01-31 17:35:20 -0800200 throw ex;
Carmelo Casconea966c342017-07-30 01:56:30 -0400201 } catch (Throwable ex) {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200202 log.error("Exception in client of {}, executing {}",
203 deviceId, opDescription, ex);
Carmelo Casconea966c342017-07-30 01:56:30 -0400204 throw ex;
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400205 } finally {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200206 requestLock.unlock();
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400207 }
Carmelo Casconee5b28722018-06-22 17:28:28 +0200208 }, executor);
209 }
210
211 /**
212 * Equivalent of supplyWithExecutor using the gRPC context executor of this
213 * client, such that if the context is cancelled (e.g. client shutdown) the
214 * RPC is automatically cancelled.
215 */
216 private <U> CompletableFuture<U> supplyInContext(
217 Supplier<U> supplier, String opDescription) {
218 return supplyWithExecutor(supplier, opDescription, contextExecutor);
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400219 }
220
221 @Override
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700222 public CompletableFuture<Boolean> startStreamChannel() {
Carmelo Cascone85d72ef2018-08-23 19:06:29 -0700223 return supplyInContext(() -> sendMasterArbitrationUpdate(false),
Carmelo Casconee5b28722018-06-22 17:28:28 +0200224 "start-initStreamChannel");
225 }
226
227 @Override
228 public CompletableFuture<Void> shutdown() {
229 return supplyWithExecutor(this::doShutdown, "shutdown",
230 SharedExecutors.getPoolThreadExecutor());
231 }
232
233 @Override
234 public CompletableFuture<Boolean> becomeMaster() {
Carmelo Cascone85d72ef2018-08-23 19:06:29 -0700235 return supplyInContext(() -> sendMasterArbitrationUpdate(true),
Carmelo Casconee5b28722018-06-22 17:28:28 +0200236 "becomeMaster");
Carmelo Cascone59f57de2017-07-11 19:55:09 -0400237 }
238
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400239 @Override
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700240 public boolean isMaster() {
241 return streamChannelManager.isOpen() && isClientMaster.get();
242 }
243
244 @Override
245 public boolean isStreamChannelOpen() {
246 return streamChannelManager.isOpen();
247 }
248
249 @Override
Carmelo Casconed61fdb32017-10-30 10:09:57 -0700250 public CompletableFuture<Boolean> setPipelineConfig(PiPipeconf pipeconf, ByteBuffer deviceData) {
251 return supplyInContext(() -> doSetPipelineConfig(pipeconf, deviceData), "setPipelineConfig");
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400252 }
253
254 @Override
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700255 public boolean isPipelineConfigSet(PiPipeconf pipeconf, ByteBuffer deviceData) {
256 return doIsPipelineConfigSet(pipeconf, deviceData);
257 }
258
259 @Override
Carmelo Casconee44592f2018-09-12 02:24:47 -0700260 public CompletableFuture<Boolean> writeTableEntries(List<PiTableEntry> piTableEntries,
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400261 WriteOperationType opType, PiPipeconf pipeconf) {
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +0200262 return supplyInContext(() -> doWriteTableEntries(piTableEntries, opType, pipeconf),
263 "writeTableEntries-" + opType.name());
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400264 }
265
266 @Override
Carmelo Casconee44592f2018-09-12 02:24:47 -0700267 public CompletableFuture<List<PiTableEntry>> dumpTables(
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700268 Set<PiTableId> piTableIds, boolean defaultEntries, PiPipeconf pipeconf) {
269 return supplyInContext(() -> doDumpTables(piTableIds, defaultEntries, pipeconf),
270 "dumpTables-" + piTableIds.hashCode());
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400271 }
272
273 @Override
Carmelo Casconee44592f2018-09-12 02:24:47 -0700274 public CompletableFuture<List<PiTableEntry>> dumpAllTables(PiPipeconf pipeconf) {
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700275 return supplyInContext(() -> doDumpTables(null, false, pipeconf), "dumpAllTables");
Carmelo Casconee5b28722018-06-22 17:28:28 +0200276 }
277
278 @Override
Andrea Campanella432f7182017-07-14 18:43:27 +0200279 public CompletableFuture<Boolean> packetOut(PiPacketOperation packet, PiPipeconf pipeconf) {
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +0200280 return supplyInContext(() -> doPacketOut(packet, pipeconf), "packetOut");
Andrea Campanella432f7182017-07-14 18:43:27 +0200281 }
282
Carmelo Casconeb045ddc2017-09-01 01:26:35 +0200283 @Override
Carmelo Casconee44592f2018-09-12 02:24:47 -0700284 public CompletableFuture<List<PiCounterCellData>> readCounterCells(Set<PiCounterCellId> cellIds,
285 PiPipeconf pipeconf) {
286 return supplyInContext(() -> doReadCounterCells(Lists.newArrayList(cellIds), pipeconf),
Carmelo Casconeb045ddc2017-09-01 01:26:35 +0200287 "readCounterCells-" + cellIds.hashCode());
288 }
289
290 @Override
Carmelo Casconee44592f2018-09-12 02:24:47 -0700291 public CompletableFuture<List<PiCounterCellData>> readAllCounterCells(Set<PiCounterId> counterIds,
292 PiPipeconf pipeconf) {
293 return supplyInContext(() -> doReadAllCounterCells(Lists.newArrayList(counterIds), pipeconf),
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700294 "readAllCounterCells-" + counterIds.hashCode());
Carmelo Casconeb045ddc2017-09-01 01:26:35 +0200295 }
296
Yi Tseng82512da2017-08-16 19:46:36 -0700297 @Override
Carmelo Casconee44592f2018-09-12 02:24:47 -0700298 public CompletableFuture<Boolean> writeActionGroupMembers(List<PiActionGroupMember> members,
Yi Tseng82512da2017-08-16 19:46:36 -0700299 WriteOperationType opType,
300 PiPipeconf pipeconf) {
Carmelo Casconee44592f2018-09-12 02:24:47 -0700301 return supplyInContext(() -> doWriteActionGroupMembers(members, opType, pipeconf),
Yi Tseng82512da2017-08-16 19:46:36 -0700302 "writeActionGroupMembers-" + opType.name());
303 }
304
Yi Tseng8d355132018-04-13 01:40:48 +0800305
Yi Tseng82512da2017-08-16 19:46:36 -0700306 @Override
307 public CompletableFuture<Boolean> writeActionGroup(PiActionGroup group,
308 WriteOperationType opType,
309 PiPipeconf pipeconf) {
310 return supplyInContext(() -> doWriteActionGroup(group, opType, pipeconf),
311 "writeActionGroup-" + opType.name());
312 }
313
314 @Override
Carmelo Casconee44592f2018-09-12 02:24:47 -0700315 public CompletableFuture<List<PiActionGroup>> dumpGroups(PiActionProfileId actionProfileId,
316 PiPipeconf pipeconf) {
Yi Tseng82512da2017-08-16 19:46:36 -0700317 return supplyInContext(() -> doDumpGroups(actionProfileId, pipeconf),
318 "dumpGroups-" + actionProfileId.id());
319 }
320
Yi Tseng3e7f1452017-10-20 10:31:53 -0700321 @Override
Carmelo Casconee44592f2018-09-12 02:24:47 -0700322 public CompletableFuture<List<PiActionGroupMemberId>> dumpActionProfileMemberIds(
323 PiActionProfileId actionProfileId, PiPipeconf pipeconf) {
324 return supplyInContext(() -> doDumpActionProfileMemberIds(actionProfileId, pipeconf),
325 "dumpActionProfileMemberIds-" + actionProfileId.id());
326 }
327
328 @Override
329 public CompletableFuture<List<PiActionGroupMemberId>> removeActionProfileMembers(
330 PiActionProfileId actionProfileId,
331 List<PiActionGroupMemberId> memberIds,
332 PiPipeconf pipeconf) {
333 return supplyInContext(
334 () -> doRemoveActionProfileMembers(actionProfileId, memberIds, pipeconf),
335 "cleanupActionProfileMembers-" + actionProfileId.id());
336 }
337
338 @Override
339 public CompletableFuture<Boolean> writeMeterCells(List<PiMeterCellConfig> cellIds, PiPipeconf pipeconf) {
Frank Wangd7e3b4b2017-09-24 13:37:54 +0900340
341 return supplyInContext(() -> doWriteMeterCells(cellIds, pipeconf),
342 "writeMeterCells");
343 }
344
345 @Override
Carmelo Cascone58136812018-07-19 03:40:16 +0200346 public CompletableFuture<Boolean> writePreMulticastGroupEntries(
Carmelo Casconee44592f2018-09-12 02:24:47 -0700347 List<PiMulticastGroupEntry> entries,
Carmelo Cascone58136812018-07-19 03:40:16 +0200348 WriteOperationType opType) {
349 return supplyInContext(() -> doWriteMulticastGroupEntries(entries, opType),
350 "writePreMulticastGroupEntries");
351 }
352
353 @Override
Carmelo Casconee44592f2018-09-12 02:24:47 -0700354 public CompletableFuture<List<PiMulticastGroupEntry>> readAllMulticastGroupEntries() {
Carmelo Cascone58136812018-07-19 03:40:16 +0200355 return supplyInContext(this::doReadAllMulticastGroupEntries,
356 "readAllMulticastGroupEntries");
357 }
358
359 @Override
Carmelo Casconee44592f2018-09-12 02:24:47 -0700360 public CompletableFuture<List<PiMeterCellConfig>> readMeterCells(Set<PiMeterCellId> cellIds,
361 PiPipeconf pipeconf) {
362 return supplyInContext(() -> doReadMeterCells(Lists.newArrayList(cellIds), pipeconf),
Frank Wangd7e3b4b2017-09-24 13:37:54 +0900363 "readMeterCells-" + cellIds.hashCode());
364 }
365
366 @Override
Carmelo Casconee44592f2018-09-12 02:24:47 -0700367 public CompletableFuture<List<PiMeterCellConfig>> readAllMeterCells(Set<PiMeterId> meterIds,
368 PiPipeconf pipeconf) {
369 return supplyInContext(() -> doReadAllMeterCells(Lists.newArrayList(meterIds), pipeconf),
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700370 "readAllMeterCells-" + meterIds.hashCode());
Frank Wangd7e3b4b2017-09-24 13:37:54 +0900371 }
Yi Tseng3e7f1452017-10-20 10:31:53 -0700372
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400373 /* Blocking method implementations below */
374
Carmelo Cascone85d72ef2018-08-23 19:06:29 -0700375 private boolean sendMasterArbitrationUpdate(boolean asMaster) {
376 BigInteger newId = controller.newMasterElectionId(deviceId);
377 if (asMaster) {
378 // Becoming master is a race. Here we increase our chances of win
379 // against other ONOS nodes in the cluster that are calling start()
380 // (which is used to start the stream RPC session, not to become
381 // master).
382 newId = newId.add(BigInteger.valueOf(1000));
Carmelo Casconee5b28722018-06-22 17:28:28 +0200383 }
Carmelo Cascone85d72ef2018-08-23 19:06:29 -0700384 final Uint128 idMsg = bigIntegerToUint128(
385 controller.newMasterElectionId(deviceId));
Andrea Campanella1e573442018-05-17 17:07:13 +0200386
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700387 log.debug("Sending arbitration update to {}... electionId={}",
388 deviceId, newId);
389
390 streamChannelManager.send(
391 StreamMessageRequest.newBuilder()
392 .setArbitration(
393 MasterArbitrationUpdate
394 .newBuilder()
395 .setDeviceId(p4DeviceId)
396 .setElectionId(idMsg)
397 .build())
398 .build());
399 clientElectionId = idMsg;
400 return true;
401 }
402
403 private ForwardingPipelineConfig getPipelineConfig(
404 PiPipeconf pipeconf, ByteBuffer deviceData) {
405 P4Info p4Info = PipeconfHelper.getP4Info(pipeconf);
406 if (p4Info == null) {
407 // Problem logged by PipeconfHelper.
408 return null;
Yi Tseng3e7f1452017-10-20 10:31:53 -0700409 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700410
411 // FIXME: This is specific to PI P4Runtime implementation.
412 P4Config.P4DeviceConfig p4DeviceConfigMsg = P4Config.P4DeviceConfig
413 .newBuilder()
414 .setExtras(P4Config.P4DeviceConfig.Extras.getDefaultInstance())
415 .setReassign(true)
416 .setDeviceData(ByteString.copyFrom(deviceData))
417 .build();
418
419 return ForwardingPipelineConfig
420 .newBuilder()
421 .setP4Info(p4Info)
422 .setP4DeviceConfig(p4DeviceConfigMsg.toByteString())
423 .build();
424 }
425
426 private boolean doIsPipelineConfigSet(PiPipeconf pipeconf, ByteBuffer deviceData) {
427
428 GetForwardingPipelineConfigRequest request = GetForwardingPipelineConfigRequest
429 .newBuilder()
430 .setDeviceId(p4DeviceId)
431 .build();
432
433 GetForwardingPipelineConfigResponse resp;
434 try {
435 resp = this.blockingStub
436 .getForwardingPipelineConfig(request);
437 } catch (StatusRuntimeException ex) {
438 checkGrpcException(ex);
439 // FAILED_PRECONDITION means that a pipeline config was not set in
440 // the first place. Don't bother logging.
441 if (!ex.getStatus().getCode()
442 .equals(Status.FAILED_PRECONDITION.getCode())) {
443 log.warn("Unable to get pipeline config from {}: {}",
444 deviceId, ex.getMessage());
445 }
446 return false;
447 }
448
449 ForwardingPipelineConfig expectedConfig = getPipelineConfig(
450 pipeconf, deviceData);
451
452 if (expectedConfig == null) {
453 return false;
454 }
455 if (!resp.hasConfig()) {
456 log.warn("{} returned GetForwardingPipelineConfigResponse " +
457 "with 'config' field unset",
458 deviceId);
459 return false;
460 }
461 if (resp.getConfig().getP4DeviceConfig().isEmpty()
462 && !expectedConfig.getP4DeviceConfig().isEmpty()) {
463 // Don't bother with a warn or error since we don't really allow
464 // updating the pipeline to a different one. So the P4Info should be
465 // enough for us.
466 log.debug("{} returned GetForwardingPipelineConfigResponse " +
467 "with empty 'p4_device_config' field, " +
468 "equality will be based only on P4Info",
469 deviceId);
470 return resp.getConfig().getP4Info().equals(
471 expectedConfig.getP4Info());
472 } else {
473 return resp.getConfig().equals(expectedConfig);
474 }
Yi Tseng3e7f1452017-10-20 10:31:53 -0700475 }
Carmelo Casconee5b28722018-06-22 17:28:28 +0200476
Carmelo Casconed61fdb32017-10-30 10:09:57 -0700477 private boolean doSetPipelineConfig(PiPipeconf pipeconf, ByteBuffer deviceData) {
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400478
Carmelo Casconed61fdb32017-10-30 10:09:57 -0700479 log.info("Setting pipeline config for {} to {}...", deviceId, pipeconf.id());
480
481 checkNotNull(deviceData, "deviceData cannot be null");
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400482
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700483 ForwardingPipelineConfig pipelineConfig = getPipelineConfig(pipeconf, deviceData);
484
485 if (pipelineConfig == null) {
486 // Error logged in getPipelineConfig()
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400487 return false;
488 }
489
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400490 SetForwardingPipelineConfigRequest request = SetForwardingPipelineConfigRequest
491 .newBuilder()
Andrea Campanella8bcd5862017-12-11 11:34:45 +0100492 .setDeviceId(p4DeviceId)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200493 .setElectionId(clientElectionId)
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400494 .setAction(VERIFY_AND_COMMIT)
Andrea Campanella8bcd5862017-12-11 11:34:45 +0100495 .setConfig(pipelineConfig)
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400496 .build();
497
498 try {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700499 //noinspection ResultOfMethodCallIgnored
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400500 this.blockingStub.setForwardingPipelineConfig(request);
Carmelo Casconed61fdb32017-10-30 10:09:57 -0700501 return true;
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400502 } catch (StatusRuntimeException ex) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700503 checkGrpcException(ex);
Carmelo Cascone5bc7e102018-02-18 18:27:55 -0800504 log.warn("Unable to set pipeline config on {}: {}", deviceId, ex.getMessage());
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400505 return false;
506 }
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400507 }
508
Carmelo Casconee44592f2018-09-12 02:24:47 -0700509 private boolean doWriteTableEntries(List<PiTableEntry> piTableEntries, WriteOperationType opType,
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400510 PiPipeconf pipeconf) {
Carmelo Cascone5bc7e102018-02-18 18:27:55 -0800511 if (piTableEntries.size() == 0) {
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400512 return true;
513 }
514
Carmelo Casconee44592f2018-09-12 02:24:47 -0700515 List<Update> updateMsgs;
Carmelo Cascone5bc7e102018-02-18 18:27:55 -0800516 try {
517 updateMsgs = TableEntryEncoder.encode(piTableEntries, pipeconf)
518 .stream()
519 .map(tableEntryMsg ->
520 Update.newBuilder()
521 .setEntity(Entity.newBuilder()
522 .setTableEntry(tableEntryMsg)
523 .build())
524 .setType(UPDATE_TYPES.get(opType))
525 .build())
526 .collect(Collectors.toList());
527 } catch (EncodeException e) {
528 log.error("Unable to encode table entries, aborting {} operation: {}",
529 opType.name(), e.getMessage());
530 return false;
531 }
532
Carmelo Cascone58136812018-07-19 03:40:16 +0200533 return write(updateMsgs, piTableEntries, opType, "table entry");
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400534 }
535
Carmelo Casconee44592f2018-09-12 02:24:47 -0700536 private List<PiTableEntry> doDumpTables(
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700537 Set<PiTableId> piTableIds, boolean defaultEntries, PiPipeconf pipeconf) {
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400538
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700539 log.debug("Dumping tables {} from {} (pipeconf {})...",
540 piTableIds, deviceId, pipeconf.id());
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400541
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700542 Set<Integer> tableIds = Sets.newHashSet();
543 if (piTableIds == null) {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200544 // Dump all tables.
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700545 tableIds.add(0);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200546 } else {
547 P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
Carmelo Cascone58136812018-07-19 03:40:16 +0200548 if (browser == null) {
549 log.warn("Unable to get a P4Info browser for pipeconf {}", pipeconf);
550 return Collections.emptyList();
551 }
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700552 piTableIds.forEach(piTableId -> {
553 try {
554 tableIds.add(browser.tables().getByName(piTableId.id()).getPreamble().getId());
555 } catch (P4InfoBrowser.NotFoundException e) {
556 log.warn("Unable to dump table {}: {}", piTableId, e.getMessage());
557 }
558 });
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400559 }
560
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700561 if (tableIds.isEmpty()) {
562 return Collections.emptyList();
563 }
564
565 ReadRequest.Builder requestMsgBuilder = ReadRequest.newBuilder()
566 .setDeviceId(p4DeviceId);
567 tableIds.forEach(tableId -> requestMsgBuilder.addEntities(
568 Entity.newBuilder()
569 .setTableEntry(
570 TableEntry.newBuilder()
571 .setTableId(tableId)
572 .setIsDefaultAction(defaultEntries)
573 .build())
574 .build())
575 .build());
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400576
577 Iterator<ReadResponse> responses;
578 try {
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700579 responses = blockingStub.read(requestMsgBuilder.build());
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400580 } catch (StatusRuntimeException e) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700581 checkGrpcException(e);
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700582 log.warn("Unable to dump tables from {}: {}", deviceId, e.getMessage());
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400583 return Collections.emptyList();
584 }
585
586 Iterable<ReadResponse> responseIterable = () -> responses;
587 List<TableEntry> tableEntryMsgs = StreamSupport
588 .stream(responseIterable.spliterator(), false)
589 .map(ReadResponse::getEntitiesList)
590 .flatMap(List::stream)
591 .filter(entity -> entity.getEntityCase() == TABLE_ENTRY)
592 .map(Entity::getTableEntry)
593 .collect(Collectors.toList());
594
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700595 log.debug("Retrieved {} entries from {} tables on {}...",
596 tableEntryMsgs.size(), tableIds.size(), deviceId);
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400597
598 return TableEntryEncoder.decode(tableEntryMsgs, pipeconf);
599 }
600
Andrea Campanellafc1d34c2017-07-18 17:01:41 +0200601 private boolean doPacketOut(PiPacketOperation packet, PiPipeconf pipeconf) {
602 try {
603 //encode the PiPacketOperation into a PacketOut
604 PacketOut packetOut = PacketIOCodec.encodePacketOut(packet, pipeconf);
605
606 //Build the request
607 StreamMessageRequest packetOutRequest = StreamMessageRequest
608 .newBuilder().setPacket(packetOut).build();
609
610 //Send the request
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700611 streamChannelManager.send(packetOutRequest);
Andrea Campanellafc1d34c2017-07-18 17:01:41 +0200612
613 } catch (P4InfoBrowser.NotFoundException e) {
614 log.error("Cant find expected metadata in p4Info file. {}", e.getMessage());
615 log.debug("Exception", e);
616 return false;
617 }
618 return true;
619 }
620
Carmelo Casconea966c342017-07-30 01:56:30 -0400621 private void doPacketIn(PacketIn packetInMsg) {
622
623 // Retrieve the pipeconf for this client's device.
624 PiPipeconfService pipeconfService = DefaultServiceDirectory.getService(PiPipeconfService.class);
625 if (pipeconfService == null) {
626 throw new IllegalStateException("PiPipeconfService is null. Can't handle packet in.");
627 }
628 final PiPipeconf pipeconf;
629 if (pipeconfService.ofDevice(deviceId).isPresent() &&
630 pipeconfService.getPipeconf(pipeconfService.ofDevice(deviceId).get()).isPresent()) {
631 pipeconf = pipeconfService.getPipeconf(pipeconfService.ofDevice(deviceId).get()).get();
632 } else {
633 log.warn("Unable to get pipeconf of {}. Can't handle packet in", deviceId);
634 return;
635 }
636 // Decode packet message and post event.
Carmelo Cascone87892e22017-11-13 16:01:29 -0800637 PiPacketOperation packetOperation = PacketIOCodec.decodePacketIn(packetInMsg, pipeconf, deviceId);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200638 PacketInEvent packetInEventSubject = new PacketInEvent(deviceId, packetOperation);
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +0200639 P4RuntimeEvent event = new P4RuntimeEvent(P4RuntimeEvent.Type.PACKET_IN, packetInEventSubject);
Carmelo Casconea966c342017-07-30 01:56:30 -0400640 log.debug("Received packet in: {}", event);
641 controller.postEvent(event);
642 }
643
Carmelo Casconee5b28722018-06-22 17:28:28 +0200644 private void doArbitrationResponse(MasterArbitrationUpdate msg) {
645 // From the spec...
646 // - Election_id: The stream RPC with the highest election_id is the
647 // master. Switch populates with the highest election ID it
648 // has received from all connected controllers.
649 // - Status: Switch populates this with OK for the client that is the
650 // master, and with an error status for all other connected clients (at
651 // every mastership change).
652 if (!msg.hasElectionId() || !msg.hasStatus()) {
Yi Tseng3e7f1452017-10-20 10:31:53 -0700653 return;
654 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700655 final boolean isMaster =
656 msg.getStatus().getCode() == Status.OK.getCode().value();
657 log.debug("Received arbitration update from {}: isMaster={}, electionId={}",
658 deviceId, isMaster, uint128ToBigInteger(msg.getElectionId()));
Carmelo Casconee5b28722018-06-22 17:28:28 +0200659 controller.postEvent(new P4RuntimeEvent(
660 P4RuntimeEvent.Type.ARBITRATION_RESPONSE,
661 new ArbitrationResponse(deviceId, isMaster)));
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700662 isClientMaster.set(isMaster);
Carmelo Casconea966c342017-07-30 01:56:30 -0400663 }
664
Carmelo Casconee44592f2018-09-12 02:24:47 -0700665 private List<PiCounterCellData> doReadAllCounterCells(
666 List<PiCounterId> counterIds, PiPipeconf pipeconf) {
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700667 return doReadCounterEntities(
668 CounterEntryCodec.readAllCellsEntities(counterIds, pipeconf),
669 pipeconf);
670 }
Carmelo Casconeb045ddc2017-09-01 01:26:35 +0200671
Carmelo Casconee44592f2018-09-12 02:24:47 -0700672 private List<PiCounterCellData> doReadCounterCells(
673 List<PiCounterCellId> cellIds, PiPipeconf pipeconf) {
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700674 return doReadCounterEntities(
675 CounterEntryCodec.encodePiCounterCellIds(cellIds, pipeconf),
676 pipeconf);
677 }
678
Carmelo Casconee44592f2018-09-12 02:24:47 -0700679 private List<PiCounterCellData> doReadCounterEntities(
680 List<Entity> counterEntities, PiPipeconf pipeconf) {
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700681
682 if (counterEntities.size() == 0) {
683 return Collections.emptyList();
684 }
Carmelo Casconeb045ddc2017-09-01 01:26:35 +0200685
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200686 final ReadRequest request = ReadRequest.newBuilder()
687 .setDeviceId(p4DeviceId)
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700688 .addAllEntities(counterEntities)
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200689 .build();
690
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200691 final Iterable<ReadResponse> responses;
Carmelo Casconeb045ddc2017-09-01 01:26:35 +0200692 try {
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200693 responses = () -> blockingStub.read(request);
Carmelo Casconeb045ddc2017-09-01 01:26:35 +0200694 } catch (StatusRuntimeException e) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700695 checkGrpcException(e);
Carmelo Cascone5bc7e102018-02-18 18:27:55 -0800696 log.warn("Unable to read counter cells from {}: {}", deviceId, e.getMessage());
Carmelo Casconeb045ddc2017-09-01 01:26:35 +0200697 return Collections.emptyList();
698 }
699
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200700 List<Entity> entities = StreamSupport.stream(responses.spliterator(), false)
Carmelo Casconeb045ddc2017-09-01 01:26:35 +0200701 .map(ReadResponse::getEntitiesList)
702 .flatMap(List::stream)
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200703 .collect(Collectors.toList());
Carmelo Casconeb045ddc2017-09-01 01:26:35 +0200704
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700705 return CounterEntryCodec.decodeCounterEntities(entities, pipeconf);
Carmelo Casconeb045ddc2017-09-01 01:26:35 +0200706 }
707
Carmelo Casconee44592f2018-09-12 02:24:47 -0700708 private boolean doWriteActionGroupMembers(List<PiActionGroupMember> members,
Yi Tseng8d355132018-04-13 01:40:48 +0800709 WriteOperationType opType, PiPipeconf pipeconf) {
Carmelo Casconee44592f2018-09-12 02:24:47 -0700710 final List<ActionProfileMember> actionProfileMembers = Lists.newArrayList();
Carmelo Cascone5bc7e102018-02-18 18:27:55 -0800711
Yi Tseng8d355132018-04-13 01:40:48 +0800712 for (PiActionGroupMember member : members) {
Carmelo Cascone5bc7e102018-02-18 18:27:55 -0800713 try {
Carmelo Casconee44592f2018-09-12 02:24:47 -0700714 actionProfileMembers.add(ActionProfileMemberEncoder.encode(member, pipeconf));
Carmelo Cascone5bc7e102018-02-18 18:27:55 -0800715 } catch (EncodeException | P4InfoBrowser.NotFoundException e) {
716 log.warn("Unable to encode group member, aborting {} operation: {} [{}]",
717 opType.name(), e.getMessage(), member.toString());
718 return false;
Yi Tseng82512da2017-08-16 19:46:36 -0700719 }
Yi Tseng82512da2017-08-16 19:46:36 -0700720 }
721
Carmelo Casconee44592f2018-09-12 02:24:47 -0700722 final List<Update> updateMsgs = actionProfileMembers.stream()
Yi Tseng82512da2017-08-16 19:46:36 -0700723 .map(actionProfileMember ->
724 Update.newBuilder()
725 .setEntity(Entity.newBuilder()
726 .setActionProfileMember(actionProfileMember)
727 .build())
728 .setType(UPDATE_TYPES.get(opType))
729 .build())
730 .collect(Collectors.toList());
731
732 if (updateMsgs.size() == 0) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200733 // Nothing to update.
Yi Tseng82512da2017-08-16 19:46:36 -0700734 return true;
735 }
736
Carmelo Cascone58136812018-07-19 03:40:16 +0200737 return write(updateMsgs, members, opType, "group member");
Yi Tseng82512da2017-08-16 19:46:36 -0700738 }
739
Carmelo Casconee44592f2018-09-12 02:24:47 -0700740 private List<PiActionGroup> doDumpGroups(PiActionProfileId piActionProfileId, PiPipeconf pipeconf) {
Yi Tseng82512da2017-08-16 19:46:36 -0700741 log.debug("Dumping groups from action profile {} from {} (pipeconf {})...",
742 piActionProfileId.id(), deviceId, pipeconf.id());
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200743
744 final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
Yi Tseng82512da2017-08-16 19:46:36 -0700745 if (browser == null) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200746 log.warn("Unable to get a P4Info browser for pipeconf {}, aborting dump action profile", pipeconf);
Carmelo Casconee44592f2018-09-12 02:24:47 -0700747 return Collections.emptyList();
Yi Tseng82512da2017-08-16 19:46:36 -0700748 }
749
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200750 final int actionProfileId;
Yi Tseng82512da2017-08-16 19:46:36 -0700751 try {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200752 actionProfileId = browser
753 .actionProfiles()
Carmelo Casconecb0a49c2017-10-03 14:32:23 +0200754 .getByName(piActionProfileId.id())
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200755 .getPreamble()
756 .getId();
Yi Tseng82512da2017-08-16 19:46:36 -0700757 } catch (P4InfoBrowser.NotFoundException e) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200758 log.warn("Unable to dump groups: {}", e.getMessage());
Carmelo Casconee44592f2018-09-12 02:24:47 -0700759 return Collections.emptyList();
Yi Tseng82512da2017-08-16 19:46:36 -0700760 }
761
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200762 // Prepare read request to read all groups from the given action profile.
763 final ReadRequest groupRequestMsg = ReadRequest.newBuilder()
Yi Tseng82512da2017-08-16 19:46:36 -0700764 .setDeviceId(p4DeviceId)
765 .addEntities(Entity.newBuilder()
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200766 .setActionProfileGroup(
767 ActionProfileGroup.newBuilder()
768 .setActionProfileId(actionProfileId)
769 .build())
Yi Tseng82512da2017-08-16 19:46:36 -0700770 .build())
771 .build();
772
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200773 // Read groups.
774 final Iterator<ReadResponse> groupResponses;
Yi Tseng82512da2017-08-16 19:46:36 -0700775 try {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200776 groupResponses = blockingStub.read(groupRequestMsg);
Yi Tseng82512da2017-08-16 19:46:36 -0700777 } catch (StatusRuntimeException e) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700778 checkGrpcException(e);
Carmelo Cascone5bc7e102018-02-18 18:27:55 -0800779 log.warn("Unable to dump action profile {} from {}: {}", piActionProfileId, deviceId, e.getMessage());
Carmelo Casconee44592f2018-09-12 02:24:47 -0700780 return Collections.emptyList();
Yi Tseng82512da2017-08-16 19:46:36 -0700781 }
782
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200783 final List<ActionProfileGroup> groupMsgs = Tools.stream(() -> groupResponses)
784 .map(ReadResponse::getEntitiesList)
785 .flatMap(List::stream)
786 .filter(entity -> entity.getEntityCase() == ACTION_PROFILE_GROUP)
787 .map(Entity::getActionProfileGroup)
788 .collect(Collectors.toList());
Yi Tseng82512da2017-08-16 19:46:36 -0700789
790 log.debug("Retrieved {} groups from action profile {} on {}...",
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200791 groupMsgs.size(), piActionProfileId.id(), deviceId);
Yi Tseng82512da2017-08-16 19:46:36 -0700792
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200793 // Returned groups contain only a minimal description of their members.
794 // We need to issue a new request to get the full description of each member.
Yi Tseng82512da2017-08-16 19:46:36 -0700795
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200796 // Keep a map of all member IDs for each group ID, will need it later.
797 final Multimap<Integer, Integer> groupIdToMemberIdsMap = HashMultimap.create();
798 groupMsgs.forEach(g -> groupIdToMemberIdsMap.putAll(
799 g.getGroupId(),
800 g.getMembersList().stream()
801 .map(ActionProfileGroup.Member::getMemberId)
802 .collect(Collectors.toList())));
Yi Tseng82512da2017-08-16 19:46:36 -0700803
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200804 // Prepare one big read request to read all members in one shot.
805 final Set<Entity> entityMsgs = groupMsgs.stream()
806 .flatMap(g -> g.getMembersList().stream())
807 .map(ActionProfileGroup.Member::getMemberId)
808 // Prevent issuing many read requests for the same member.
809 .distinct()
810 .map(id -> ActionProfileMember.newBuilder()
811 .setActionProfileId(actionProfileId)
812 .setMemberId(id)
813 .build())
814 .map(m -> Entity.newBuilder()
815 .setActionProfileMember(m)
816 .build())
817 .collect(Collectors.toSet());
818 final ReadRequest memberRequestMsg = ReadRequest.newBuilder().setDeviceId(p4DeviceId)
819 .addAllEntities(entityMsgs)
820 .build();
Yi Tseng82512da2017-08-16 19:46:36 -0700821
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200822 // Read members.
823 final Iterator<ReadResponse> memberResponses;
824 try {
825 memberResponses = blockingStub.read(memberRequestMsg);
826 } catch (StatusRuntimeException e) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700827 checkGrpcException(e);
Carmelo Cascone5bc7e102018-02-18 18:27:55 -0800828 log.warn("Unable to read members of action profile {} from {}: {}",
829 piActionProfileId, deviceId, e.getMessage());
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200830 return Collections.emptyList();
Yi Tseng82512da2017-08-16 19:46:36 -0700831 }
832
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200833 final Multimap<Integer, ActionProfileMember> groupIdToMembersMap = HashMultimap.create();
834 Tools.stream(() -> memberResponses)
835 .map(ReadResponse::getEntitiesList)
836 .flatMap(List::stream)
837 .filter(e -> e.getEntityCase() == ACTION_PROFILE_MEMBER)
838 .map(Entity::getActionProfileMember)
839 .forEach(member -> groupIdToMemberIdsMap.asMap()
840 // Get all group IDs that contain this member.
841 .entrySet()
842 .stream()
843 .filter(entry -> entry.getValue().contains(member.getMemberId()))
844 .map(Map.Entry::getKey)
845 .forEach(gid -> groupIdToMembersMap.put(gid, member)));
846
847 log.debug("Retrieved {} group members from action profile {} on {}...",
848 groupIdToMembersMap.size(), piActionProfileId.id(), deviceId);
849
850 return groupMsgs.stream()
851 .map(groupMsg -> {
852 try {
853 return ActionProfileGroupEncoder.decode(groupMsg,
854 groupIdToMembersMap.get(groupMsg.getGroupId()),
855 pipeconf);
856 } catch (P4InfoBrowser.NotFoundException | EncodeException e) {
857 log.warn("Unable to decode group: {}\n {}", e.getMessage(), groupMsg);
858 return null;
859 }
860 })
861 .filter(Objects::nonNull)
862 .collect(Collectors.toList());
Yi Tseng82512da2017-08-16 19:46:36 -0700863 }
864
Carmelo Casconee44592f2018-09-12 02:24:47 -0700865 private List<PiActionGroupMemberId> doDumpActionProfileMemberIds(
866 PiActionProfileId actionProfileId, PiPipeconf pipeconf) {
867
868 final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
869 if (browser == null) {
870 log.warn("Unable to get a P4Info browser for pipeconf {}, " +
871 "aborting cleanup of action profile members",
872 pipeconf);
873 return Collections.emptyList();
874 }
875
876 final int p4ActProfId;
877 try {
878 p4ActProfId = browser
879 .actionProfiles()
880 .getByName(actionProfileId.id())
881 .getPreamble()
882 .getId();
883 } catch (P4InfoBrowser.NotFoundException e) {
884 log.warn("Unable to cleanup action profile members: {}", e.getMessage());
885 return Collections.emptyList();
886 }
887
888 final ReadRequest memberRequestMsg = ReadRequest.newBuilder()
889 .setDeviceId(p4DeviceId)
890 .addEntities(Entity.newBuilder().setActionProfileMember(
891 ActionProfileMember.newBuilder()
892 .setActionProfileId(p4ActProfId)
893 .build()).build())
894 .build();
895
896 // Read members.
897 final Iterator<ReadResponse> memberResponses;
898 try {
899 memberResponses = blockingStub.read(memberRequestMsg);
900 } catch (StatusRuntimeException e) {
901 checkGrpcException(e);
902 log.warn("Unable to read members of action profile {} from {}: {}",
903 actionProfileId, deviceId, e.getMessage());
904 return Collections.emptyList();
905 }
906
907 return Tools.stream(() -> memberResponses)
908 .map(ReadResponse::getEntitiesList)
909 .flatMap(List::stream)
910 .filter(e -> e.getEntityCase() == ACTION_PROFILE_MEMBER)
911 .map(Entity::getActionProfileMember)
912 // Perhaps not needed, but better to double check to avoid
913 // removing members of other groups.
914 .filter(m -> m.getActionProfileId() == p4ActProfId)
915 .map(ActionProfileMember::getMemberId)
916 .map(PiActionGroupMemberId::of)
917 .collect(Collectors.toList());
918 }
919
920 private List<PiActionGroupMemberId> doRemoveActionProfileMembers(
921 PiActionProfileId actionProfileId,
922 List<PiActionGroupMemberId> memberIds,
923 PiPipeconf pipeconf) {
924
925 if (memberIds.isEmpty()) {
926 return Collections.emptyList();
927 }
928
929 final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
930 if (browser == null) {
931 log.warn("Unable to get a P4Info browser for pipeconf {}, " +
932 "aborting cleanup of action profile members",
933 pipeconf);
934 return Collections.emptyList();
935 }
936
937 final int p4ActProfId;
938 try {
939 p4ActProfId = browser.actionProfiles()
940 .getByName(actionProfileId.id()).getPreamble().getId();
941 } catch (P4InfoBrowser.NotFoundException e) {
942 log.warn("Unable to cleanup action profile members: {}", e.getMessage());
943 return Collections.emptyList();
944 }
945
946 final List<Update> updateMsgs = memberIds.stream()
947 .map(m -> ActionProfileMember.newBuilder()
948 .setActionProfileId(p4ActProfId)
949 .setMemberId(m.id()).build())
950 .map(m -> Entity.newBuilder().setActionProfileMember(m).build())
951 .map(e -> Update.newBuilder().setEntity(e)
952 .setType(Update.Type.DELETE).build())
953 .collect(Collectors.toList());
954
955 log.debug("Removing {} members of action profile '{}'...",
956 memberIds.size(), actionProfileId);
957
958 return writeAndReturnSuccessEntities(
959 updateMsgs, memberIds, WriteOperationType.DELETE,
960 "action profile members");
961 }
962
Yi Tseng82512da2017-08-16 19:46:36 -0700963 private boolean doWriteActionGroup(PiActionGroup group, WriteOperationType opType, PiPipeconf pipeconf) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200964 final ActionProfileGroup actionProfileGroup;
Yi Tseng82512da2017-08-16 19:46:36 -0700965 try {
966 actionProfileGroup = ActionProfileGroupEncoder.encode(group, pipeconf);
967 } catch (EncodeException | P4InfoBrowser.NotFoundException e) {
Carmelo Cascone5bc7e102018-02-18 18:27:55 -0800968 log.warn("Unable to encode group, aborting {} operation: {}", e.getMessage(), opType.name());
Yi Tseng82512da2017-08-16 19:46:36 -0700969 return false;
970 }
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200971
Carmelo Cascone58136812018-07-19 03:40:16 +0200972 final Update updateMsg = Update.newBuilder()
973 .setEntity(Entity.newBuilder()
974 .setActionProfileGroup(actionProfileGroup)
975 .build())
976 .setType(UPDATE_TYPES.get(opType))
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200977 .build();
Carmelo Cascone58136812018-07-19 03:40:16 +0200978
Carmelo Casconee44592f2018-09-12 02:24:47 -0700979 return write(singletonList(updateMsg), singletonList(group),
Carmelo Cascone58136812018-07-19 03:40:16 +0200980 opType, "group");
Yi Tseng82512da2017-08-16 19:46:36 -0700981 }
982
Carmelo Casconee44592f2018-09-12 02:24:47 -0700983 private List<PiMeterCellConfig> doReadAllMeterCells(
984 List<PiMeterId> meterIds, PiPipeconf pipeconf) {
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700985 return doReadMeterEntities(MeterEntryCodec.readAllCellsEntities(
986 meterIds, pipeconf), pipeconf);
987 }
Frank Wangd7e3b4b2017-09-24 13:37:54 +0900988
Carmelo Casconee44592f2018-09-12 02:24:47 -0700989 private List<PiMeterCellConfig> doReadMeterCells(
990 List<PiMeterCellId> cellIds, PiPipeconf pipeconf) {
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700991
Carmelo Casconee44592f2018-09-12 02:24:47 -0700992 final List<PiMeterCellConfig> piMeterCellConfigs = cellIds.stream()
Frank Wangd7e3b4b2017-09-24 13:37:54 +0900993 .map(cellId -> PiMeterCellConfig.builder()
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700994 .withMeterCellId(cellId)
995 .build())
Frank Wangd7e3b4b2017-09-24 13:37:54 +0900996 .collect(Collectors.toList());
997
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700998 return doReadMeterEntities(MeterEntryCodec.encodePiMeterCellConfigs(
999 piMeterCellConfigs, pipeconf), pipeconf);
1000 }
1001
Carmelo Casconee44592f2018-09-12 02:24:47 -07001002 private List<PiMeterCellConfig> doReadMeterEntities(
1003 List<Entity> entitiesToRead, PiPipeconf pipeconf) {
Carmelo Cascone81929aa2018-04-07 01:38:55 -07001004
1005 if (entitiesToRead.size() == 0) {
1006 return Collections.emptyList();
1007 }
1008
Frank Wangd7e3b4b2017-09-24 13:37:54 +09001009 final ReadRequest request = ReadRequest.newBuilder()
1010 .setDeviceId(p4DeviceId)
Carmelo Cascone81929aa2018-04-07 01:38:55 -07001011 .addAllEntities(entitiesToRead)
Frank Wangd7e3b4b2017-09-24 13:37:54 +09001012 .build();
1013
Frank Wangd7e3b4b2017-09-24 13:37:54 +09001014 final Iterable<ReadResponse> responses;
1015 try {
1016 responses = () -> blockingStub.read(request);
1017 } catch (StatusRuntimeException e) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -07001018 checkGrpcException(e);
Carmelo Cascone81929aa2018-04-07 01:38:55 -07001019 log.warn("Unable to read meter cells: {}", e.getMessage());
Frank Wangd7e3b4b2017-09-24 13:37:54 +09001020 log.debug("exception", e);
1021 return Collections.emptyList();
1022 }
1023
Carmelo Cascone81929aa2018-04-07 01:38:55 -07001024 List<Entity> responseEntities = StreamSupport
1025 .stream(responses.spliterator(), false)
Frank Wangd7e3b4b2017-09-24 13:37:54 +09001026 .map(ReadResponse::getEntitiesList)
1027 .flatMap(List::stream)
1028 .collect(Collectors.toList());
1029
Carmelo Cascone81929aa2018-04-07 01:38:55 -07001030 return MeterEntryCodec.decodeMeterEntities(responseEntities, pipeconf);
Frank Wangd7e3b4b2017-09-24 13:37:54 +09001031 }
1032
Carmelo Casconee44592f2018-09-12 02:24:47 -07001033 private boolean doWriteMeterCells(List<PiMeterCellConfig> cellConfigs, PiPipeconf pipeconf) {
Frank Wangd7e3b4b2017-09-24 13:37:54 +09001034
Carmelo Casconee44592f2018-09-12 02:24:47 -07001035 List<Update> updateMsgs = MeterEntryCodec.encodePiMeterCellConfigs(cellConfigs, pipeconf)
Frank Wangd7e3b4b2017-09-24 13:37:54 +09001036 .stream()
1037 .map(meterEntryMsg ->
1038 Update.newBuilder()
1039 .setEntity(meterEntryMsg)
1040 .setType(UPDATE_TYPES.get(WriteOperationType.MODIFY))
1041 .build())
1042 .collect(Collectors.toList());
1043
1044 if (updateMsgs.size() == 0) {
1045 return true;
1046 }
1047
Carmelo Cascone58136812018-07-19 03:40:16 +02001048 return write(updateMsgs, cellConfigs, WriteOperationType.MODIFY, "meter cell config");
1049 }
1050
1051 private boolean doWriteMulticastGroupEntries(
Carmelo Casconee44592f2018-09-12 02:24:47 -07001052 List<PiMulticastGroupEntry> entries,
Carmelo Cascone58136812018-07-19 03:40:16 +02001053 WriteOperationType opType) {
1054
1055 final List<Update> updateMsgs = entries.stream()
1056 .map(MulticastGroupEntryCodec::encode)
1057 .map(mcMsg -> PacketReplicationEngineEntry.newBuilder()
1058 .setMulticastGroupEntry(mcMsg)
1059 .build())
1060 .map(preMsg -> Entity.newBuilder()
1061 .setPacketReplicationEngineEntry(preMsg)
1062 .build())
1063 .map(entityMsg -> Update.newBuilder()
1064 .setEntity(entityMsg)
1065 .setType(UPDATE_TYPES.get(opType))
1066 .build())
1067 .collect(Collectors.toList());
1068 return write(updateMsgs, entries, opType, "multicast group entry");
1069 }
1070
Carmelo Casconee44592f2018-09-12 02:24:47 -07001071 private List<PiMulticastGroupEntry> doReadAllMulticastGroupEntries() {
Carmelo Cascone58136812018-07-19 03:40:16 +02001072
1073 final Entity entity = Entity.newBuilder()
1074 .setPacketReplicationEngineEntry(
1075 PacketReplicationEngineEntry.newBuilder()
1076 .setMulticastGroupEntry(
1077 MulticastGroupEntry.newBuilder()
1078 .build())
1079 .build())
1080 .build();
1081
1082 final ReadRequest req = ReadRequest.newBuilder()
1083 .setDeviceId(p4DeviceId)
1084 .addEntities(entity)
1085 .build();
1086
1087 Iterator<ReadResponse> responses;
1088 try {
1089 responses = blockingStub.read(req);
1090 } catch (StatusRuntimeException e) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -07001091 checkGrpcException(e);
Carmelo Cascone58136812018-07-19 03:40:16 +02001092 log.warn("Unable to read multicast group entries from {}: {}", deviceId, e.getMessage());
1093 return Collections.emptyList();
1094 }
1095
1096 Iterable<ReadResponse> responseIterable = () -> responses;
1097 final List<PiMulticastGroupEntry> mcEntries = StreamSupport
1098 .stream(responseIterable.spliterator(), false)
1099 .map(ReadResponse::getEntitiesList)
1100 .flatMap(List::stream)
1101 .filter(e -> e.getEntityCase()
1102 .equals(PACKET_REPLICATION_ENGINE_ENTRY))
1103 .map(Entity::getPacketReplicationEngineEntry)
1104 .filter(e -> e.getTypeCase().equals(MULTICAST_GROUP_ENTRY))
1105 .map(PacketReplicationEngineEntry::getMulticastGroupEntry)
1106 .map(MulticastGroupEntryCodec::decode)
1107 .collect(Collectors.toList());
1108
1109 log.debug("Retrieved {} multicast group entries from {}...",
1110 mcEntries.size(), deviceId);
1111
1112 return mcEntries;
1113 }
1114
Carmelo Casconee44592f2018-09-12 02:24:47 -07001115 private <T> boolean write(List<Update> updates,
1116 List<T> writeEntities,
1117 WriteOperationType opType,
1118 String entryType) {
1119 // True if all entities were successfully written.
1120 return writeAndReturnSuccessEntities(updates, writeEntities, opType,
1121 entryType).size() == writeEntities.size();
1122 }
Carmelo Cascone9e4972c2018-08-30 00:29:16 -07001123
Carmelo Casconee44592f2018-09-12 02:24:47 -07001124 private <T> List<T> writeAndReturnSuccessEntities(
1125 List<Update> updates, List<T> writeEntities,
1126 WriteOperationType opType, String entryType) {
1127 try {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -07001128 //noinspection ResultOfMethodCallIgnored
Carmelo Cascone58136812018-07-19 03:40:16 +02001129 blockingStub.write(writeRequest(updates));
Carmelo Casconee44592f2018-09-12 02:24:47 -07001130 return writeEntities;
Carmelo Cascone58136812018-07-19 03:40:16 +02001131 } catch (StatusRuntimeException e) {
Carmelo Casconee44592f2018-09-12 02:24:47 -07001132 return checkAndLogWriteErrors(writeEntities, e, opType, entryType);
Carmelo Cascone58136812018-07-19 03:40:16 +02001133 }
1134 }
1135
1136 private WriteRequest writeRequest(Iterable<Update> updateMsgs) {
1137 return WriteRequest.newBuilder()
Frank Wangd7e3b4b2017-09-24 13:37:54 +09001138 .setDeviceId(p4DeviceId)
Carmelo Casconee5b28722018-06-22 17:28:28 +02001139 .setElectionId(clientElectionId)
Frank Wangd7e3b4b2017-09-24 13:37:54 +09001140 .addAllUpdates(updateMsgs)
1141 .build();
Frank Wangd7e3b4b2017-09-24 13:37:54 +09001142 }
1143
Carmelo Casconee5b28722018-06-22 17:28:28 +02001144 private Void doShutdown() {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -07001145 log.debug("Shutting down client for {}...", deviceId);
1146 streamChannelManager.complete();
1147 cancellableContext.cancel(new InterruptedException(
1148 "Requested client shutdown"));
1149 this.executorService.shutdownNow();
Carmelo Casconee5b28722018-06-22 17:28:28 +02001150 try {
1151 executorService.awaitTermination(5, TimeUnit.SECONDS);
1152 } catch (InterruptedException e) {
1153 log.warn("Executor service didn't shutdown in time.");
1154 Thread.currentThread().interrupt();
1155 }
1156 return null;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -04001157 }
1158
Carmelo Casconee44592f2018-09-12 02:24:47 -07001159 // Returns the collection of succesfully write entities.
1160 private <T> List<T> checkAndLogWriteErrors(
1161 List<T> writeEntities, StatusRuntimeException ex,
Carmelo Casconee5b28722018-06-22 17:28:28 +02001162 WriteOperationType opType, String entryType) {
1163
1164 checkGrpcException(ex);
1165
Carmelo Cascone943c6642018-09-11 13:01:03 -07001166 final List<P4RuntimeOuterClass.Error> errors = extractWriteErrorDetails(ex);
Carmelo Cascone5bc7e102018-02-18 18:27:55 -08001167
Carmelo Cascone943c6642018-09-11 13:01:03 -07001168 if (errors.isEmpty()) {
1169 final String description = ex.getStatus().getDescription();
1170 log.warn("Unable to {} {} {}(s) on {}: {}",
Carmelo Cascone50d195f2018-09-11 13:26:38 -07001171 opType.name(), writeEntities.size(), entryType, deviceId,
1172 ex.getStatus().getCode().name(),
Carmelo Cascone943c6642018-09-11 13:01:03 -07001173 description == null ? "" : " - " + description);
Carmelo Casconee44592f2018-09-12 02:24:47 -07001174 return Collections.emptyList();
Carmelo Cascone5bc7e102018-02-18 18:27:55 -08001175 }
1176
Carmelo Cascone5bc7e102018-02-18 18:27:55 -08001177 if (errors.size() == writeEntities.size()) {
Carmelo Casconee44592f2018-09-12 02:24:47 -07001178 List<T> okEntities = Lists.newArrayList();
1179 Iterator<T> entityIterator = writeEntities.iterator();
1180 for (P4RuntimeOuterClass.Error error : errors) {
1181 T entity = entityIterator.next();
1182 if (error.getCanonicalCode() != Status.OK.getCode().value()) {
1183 log.warn("Unable to {} {} on {}: {} [{}]",
1184 opType.name(), entryType, deviceId,
1185 parseP4Error(error), entity.toString());
1186 } else {
1187 okEntities.add(entity);
1188 }
1189 }
1190 return okEntities;
Carmelo Cascone5bc7e102018-02-18 18:27:55 -08001191 } else {
Carmelo Cascone943c6642018-09-11 13:01:03 -07001192 log.warn("Unable to reconcile error details to updates " +
Carmelo Casconee44592f2018-09-12 02:24:47 -07001193 "(sent {} updates, but device returned {} errors)",
1194 entryType, writeEntities.size(), errors.size());
Carmelo Cascone5bc7e102018-02-18 18:27:55 -08001195 errors.stream()
1196 .filter(err -> err.getCanonicalCode() != Status.OK.getCode().value())
1197 .forEach(err -> log.warn("Unable to {} {} (unknown): {}",
Carmelo Cascone58136812018-07-19 03:40:16 +02001198 opType.name(), entryType, parseP4Error(err)));
Carmelo Casconee44592f2018-09-12 02:24:47 -07001199 return Collections.emptyList();
Carmelo Cascone5bc7e102018-02-18 18:27:55 -08001200 }
1201 }
1202
1203 private List<P4RuntimeOuterClass.Error> extractWriteErrorDetails(
Carmelo Cascone943c6642018-09-11 13:01:03 -07001204 StatusRuntimeException ex) {
1205 if (!ex.getTrailers().containsKey(STATUS_DETAILS_KEY)) {
Carmelo Cascone5bc7e102018-02-18 18:27:55 -08001206 return Collections.emptyList();
1207 }
Carmelo Cascone943c6642018-09-11 13:01:03 -07001208 com.google.rpc.Status status = ex.getTrailers().get(STATUS_DETAILS_KEY);
1209 if (status == null) {
1210 return Collections.emptyList();
1211 }
Carmelo Cascone5bc7e102018-02-18 18:27:55 -08001212 return status.getDetailsList().stream()
1213 .map(any -> {
1214 try {
1215 return any.unpack(P4RuntimeOuterClass.Error.class);
1216 } catch (InvalidProtocolBufferException e) {
1217 log.warn("Unable to unpack P4Runtime Error: {}",
1218 any.toString());
1219 return null;
1220 }
1221 })
1222 .filter(Objects::nonNull)
1223 .collect(Collectors.toList());
Carmelo Cascone5bc7e102018-02-18 18:27:55 -08001224 }
1225
1226 private String parseP4Error(P4RuntimeOuterClass.Error err) {
Carmelo Cascone943c6642018-09-11 13:01:03 -07001227 return format("%s %s%s (%s:%d)",
1228 Status.fromCodeValue(err.getCanonicalCode()).getCode(),
Carmelo Cascone5bc7e102018-02-18 18:27:55 -08001229 err.getMessage(),
Carmelo Cascone943c6642018-09-11 13:01:03 -07001230 err.hasDetails() ? ", " + err.getDetails().toString() : "",
Carmelo Cascone5bc7e102018-02-18 18:27:55 -08001231 err.getSpace(),
Carmelo Cascone943c6642018-09-11 13:01:03 -07001232 err.getCode());
Carmelo Cascone5bc7e102018-02-18 18:27:55 -08001233 }
1234
Carmelo Casconee5b28722018-06-22 17:28:28 +02001235 private void checkGrpcException(StatusRuntimeException ex) {
1236 switch (ex.getStatus().getCode()) {
1237 case OK:
1238 break;
1239 case CANCELLED:
1240 break;
1241 case UNKNOWN:
1242 break;
1243 case INVALID_ARGUMENT:
1244 break;
1245 case DEADLINE_EXCEEDED:
1246 break;
1247 case NOT_FOUND:
1248 break;
1249 case ALREADY_EXISTS:
1250 break;
1251 case PERMISSION_DENIED:
1252 // Notify upper layers that this node is not master.
1253 controller.postEvent(new P4RuntimeEvent(
Carmelo Casconede3b6842018-09-05 17:45:10 -07001254 P4RuntimeEvent.Type.PERMISSION_DENIED,
1255 new BaseP4RuntimeEventSubject(deviceId)));
Carmelo Casconee5b28722018-06-22 17:28:28 +02001256 break;
1257 case RESOURCE_EXHAUSTED:
1258 break;
1259 case FAILED_PRECONDITION:
1260 break;
1261 case ABORTED:
1262 break;
1263 case OUT_OF_RANGE:
1264 break;
1265 case UNIMPLEMENTED:
1266 break;
1267 case INTERNAL:
1268 break;
1269 case UNAVAILABLE:
1270 // Channel might be closed.
1271 controller.postEvent(new P4RuntimeEvent(
1272 P4RuntimeEvent.Type.CHANNEL_EVENT,
1273 new ChannelEvent(deviceId, ChannelEvent.Type.ERROR)));
1274 break;
1275 case DATA_LOSS:
1276 break;
1277 case UNAUTHENTICATED:
1278 break;
1279 default:
1280 break;
1281 }
1282 }
1283
1284 private Uint128 bigIntegerToUint128(BigInteger value) {
1285 final byte[] arr = value.toByteArray();
1286 final ByteBuffer bb = ByteBuffer.allocate(Long.BYTES * 2)
1287 .put(new byte[Long.BYTES * 2 - arr.length])
1288 .put(arr);
1289 bb.rewind();
1290 return Uint128.newBuilder()
1291 .setHigh(bb.getLong())
1292 .setLow(bb.getLong())
1293 .build();
1294 }
1295
1296 private BigInteger uint128ToBigInteger(Uint128 value) {
1297 return new BigInteger(
1298 ByteBuffer.allocate(Long.BYTES * 2)
1299 .putLong(value.getHigh())
1300 .putLong(value.getLow())
1301 .array());
1302 }
1303
Carmelo Cascone8d99b172017-07-18 17:26:31 -04001304 /**
Carmelo Cascone9e4972c2018-08-30 00:29:16 -07001305 * A manager for the P4Runtime stream channel that opportunistically creates
1306 * new stream RCP stubs (e.g. when one fails because of errors) and posts
1307 * channel events via the P4Runtime controller.
1308 */
1309 private final class StreamChannelManager {
1310
1311 private final ManagedChannel channel;
1312 private final AtomicBoolean open;
1313 private final StreamObserver<StreamMessageResponse> responseObserver;
1314 private ClientCallStreamObserver<StreamMessageRequest> requestObserver;
1315
1316 private StreamChannelManager(ManagedChannel channel) {
1317 this.channel = channel;
1318 this.responseObserver = new InternalStreamResponseObserver(this);
1319 this.open = new AtomicBoolean(false);
1320 }
1321
1322 private void initIfRequired() {
1323 if (requestObserver == null) {
1324 log.debug("Creating new stream channel for {}...", deviceId);
1325 requestObserver =
1326 (ClientCallStreamObserver<StreamMessageRequest>)
1327 P4RuntimeGrpc.newStub(channel)
1328 .streamChannel(responseObserver);
1329 open.set(false);
1330 }
1331 }
1332
1333 public boolean send(StreamMessageRequest value) {
1334 synchronized (this) {
1335 initIfRequired();
1336 try {
1337 requestObserver.onNext(value);
1338 // FIXME
1339 // signalOpen();
1340 return true;
1341 } catch (Throwable ex) {
1342 if (ex instanceof StatusRuntimeException) {
1343 log.warn("Unable to send {} to {}: {}",
1344 value.getUpdateCase().toString(), deviceId, ex.getMessage());
1345 } else {
1346 log.warn(format(
1347 "Exception while sending %s to %s",
1348 value.getUpdateCase().toString(), deviceId), ex);
1349 }
1350 complete();
1351 return false;
1352 }
1353 }
1354 }
1355
1356 public void complete() {
1357 synchronized (this) {
1358 signalClosed();
1359 if (requestObserver != null) {
1360 requestObserver.onCompleted();
1361 requestObserver.cancel("Terminated", null);
1362 requestObserver = null;
1363 }
1364 }
1365 }
1366
1367 void signalOpen() {
1368 synchronized (this) {
1369 final boolean wasOpen = open.getAndSet(true);
1370 if (!wasOpen) {
1371 controller.postEvent(new P4RuntimeEvent(
1372 P4RuntimeEvent.Type.CHANNEL_EVENT,
1373 new ChannelEvent(deviceId, ChannelEvent.Type.OPEN)));
1374 }
1375 }
1376 }
1377
1378 void signalClosed() {
1379 synchronized (this) {
1380 final boolean wasOpen = open.getAndSet(false);
1381 if (wasOpen) {
1382 controller.postEvent(new P4RuntimeEvent(
1383 P4RuntimeEvent.Type.CHANNEL_EVENT,
1384 new ChannelEvent(deviceId, ChannelEvent.Type.CLOSED)));
1385 }
1386 }
1387 }
1388
1389 public boolean isOpen() {
1390 return open.get();
1391 }
1392 }
1393
1394 /**
Carmelo Cascone8d99b172017-07-18 17:26:31 -04001395 * Handles messages received from the device on the stream channel.
1396 */
Carmelo Cascone9e4972c2018-08-30 00:29:16 -07001397 private final class InternalStreamResponseObserver
Carmelo Casconee5b28722018-06-22 17:28:28 +02001398 implements StreamObserver<StreamMessageResponse> {
Carmelo Casconef7aa3f92017-07-06 23:56:50 -04001399
Carmelo Cascone9e4972c2018-08-30 00:29:16 -07001400 private final StreamChannelManager streamChannelManager;
1401
1402 private InternalStreamResponseObserver(
1403 StreamChannelManager streamChannelManager) {
1404 this.streamChannelManager = streamChannelManager;
1405 }
1406
Carmelo Casconef7aa3f92017-07-06 23:56:50 -04001407 @Override
1408 public void onNext(StreamMessageResponse message) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -07001409 streamChannelManager.signalOpen();
Carmelo Cascone8d99b172017-07-18 17:26:31 -04001410 executorService.submit(() -> doNext(message));
1411 }
Carmelo Casconef7aa3f92017-07-06 23:56:50 -04001412
Carmelo Cascone8d99b172017-07-18 17:26:31 -04001413 private void doNext(StreamMessageResponse message) {
Carmelo Casconea966c342017-07-30 01:56:30 -04001414 try {
Carmelo Casconee5b28722018-06-22 17:28:28 +02001415 log.debug("Received message on stream channel from {}: {}",
1416 deviceId, message.getUpdateCase());
Carmelo Casconea966c342017-07-30 01:56:30 -04001417 switch (message.getUpdateCase()) {
1418 case PACKET:
Carmelo Casconea966c342017-07-30 01:56:30 -04001419 doPacketIn(message.getPacket());
Andrea Campanella288b2732017-07-28 14:16:16 +02001420 return;
Carmelo Casconea966c342017-07-30 01:56:30 -04001421 case ARBITRATION:
Carmelo Casconee5b28722018-06-22 17:28:28 +02001422 doArbitrationResponse(message.getArbitration());
Carmelo Casconea966c342017-07-30 01:56:30 -04001423 return;
1424 default:
Carmelo Casconee5b28722018-06-22 17:28:28 +02001425 log.warn("Unrecognized stream message from {}: {}",
1426 deviceId, message.getUpdateCase());
Carmelo Casconea966c342017-07-30 01:56:30 -04001427 }
1428 } catch (Throwable ex) {
Carmelo Casconee5b28722018-06-22 17:28:28 +02001429 log.error("Exception while processing stream message from {}",
1430 deviceId, ex);
Carmelo Casconef7aa3f92017-07-06 23:56:50 -04001431 }
Carmelo Casconef7aa3f92017-07-06 23:56:50 -04001432 }
1433
1434 @Override
1435 public void onError(Throwable throwable) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -07001436 if (throwable instanceof StatusRuntimeException) {
1437 StatusRuntimeException sre = (StatusRuntimeException) throwable;
1438 if (sre.getStatus().getCause() instanceof ConnectException) {
1439 log.warn("Device {} is unreachable ({})",
1440 deviceId, sre.getCause().getMessage());
1441 } else {
1442 log.warn("Received error on stream channel for {}: {}",
1443 deviceId, throwable.getMessage());
1444 }
1445 } else {
1446 log.warn(format("Received exception on stream channel for %s",
1447 deviceId), throwable);
1448 }
1449 streamChannelManager.complete();
Carmelo Casconef7aa3f92017-07-06 23:56:50 -04001450 }
1451
1452 @Override
1453 public void onCompleted() {
Carmelo Cascone59f57de2017-07-11 19:55:09 -04001454 log.warn("Stream channel for {} has completed", deviceId);
Carmelo Cascone9e4972c2018-08-30 00:29:16 -07001455 streamChannelManager.complete();
Carmelo Casconef7aa3f92017-07-06 23:56:50 -04001456 }
1457 }
Carmelo Cascone87892e22017-11-13 16:01:29 -08001458}