blob: 2cb5b68c1572d7407570d3e6565e24ca8cda4279 [file] [log] [blame]
Frank Wang0e805082017-07-21 14:37:35 +08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Frank Wang0e805082017-07-21 14:37:35 +08003 *
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
Andrea Campanella0288c872017-08-07 18:32:51 +020017package org.onosproject.drivers.p4runtime;
Frank Wang0e805082017-07-21 14:37:35 +080018
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020019import com.google.common.collect.ImmutableList;
20import com.google.common.collect.Lists;
Carmelo Cascone50d195f2018-09-11 13:26:38 -070021import com.google.common.collect.Maps;
Carmelo Cascone61469462019-03-05 23:59:11 -080022import com.google.common.util.concurrent.Futures;
23import com.google.common.util.concurrent.Striped;
CyberHasHe9ba39c2019-10-11 05:59:12 +080024import org.onosproject.drivers.p4runtime.mirror.P4RuntimeDefaultEntryMirror;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080025import org.onosproject.drivers.p4runtime.mirror.P4RuntimeTableMirror;
26import org.onosproject.drivers.p4runtime.mirror.TimedEntry;
Frank Wang0e805082017-07-21 14:37:35 +080027import org.onosproject.net.flow.DefaultFlowEntry;
28import org.onosproject.net.flow.FlowEntry;
29import org.onosproject.net.flow.FlowRule;
30import org.onosproject.net.flow.FlowRuleProgrammable;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080031import org.onosproject.net.pi.model.PiCounterType;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020032import org.onosproject.net.pi.model.PiPipelineModel;
Carmelo Cascone87892e22017-11-13 16:01:29 -080033import org.onosproject.net.pi.model.PiTableId;
steven308017632e152018-10-20 00:51:08 +080034import org.onosproject.net.pi.runtime.PiCounterCell;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020035import org.onosproject.net.pi.runtime.PiCounterCellData;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080036import org.onosproject.net.pi.runtime.PiCounterCellHandle;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020037import org.onosproject.net.pi.runtime.PiCounterCellId;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080038import org.onosproject.net.pi.runtime.PiEntityType;
39import org.onosproject.net.pi.runtime.PiHandle;
CyberHasHe9ba39c2019-10-11 05:59:12 +080040import org.onosproject.net.pi.runtime.PiMatchKey;
Frank Wang0e805082017-07-21 14:37:35 +080041import org.onosproject.net.pi.runtime.PiTableEntry;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080042import org.onosproject.net.pi.runtime.PiTableEntryHandle;
43import org.onosproject.net.pi.service.PiFlowRuleTranslator;
44import org.onosproject.net.pi.service.PiTranslatedEntity;
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080045import org.onosproject.net.pi.service.PiTranslationException;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080046import org.onosproject.p4runtime.api.P4RuntimeReadClient;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080047import org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType;
Carmelo Cascone61469462019-03-05 23:59:11 -080048import org.onosproject.p4runtime.api.P4RuntimeWriteClient.WriteRequest;
49import org.onosproject.p4runtime.api.P4RuntimeWriteClient.WriteResponse;
Frank Wang0e805082017-07-21 14:37:35 +080050
51import java.util.Collection;
52import java.util.Collections;
Manjunath Vanaraj59ad6572017-12-26 11:10:57 +053053import java.util.List;
Carmelo Cascone3da671a2018-02-12 10:43:35 -080054import java.util.Map;
Carmelo Cascone26600972018-09-10 00:23:20 -070055import java.util.Objects;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080056import java.util.Optional;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020057import java.util.Set;
Carmelo Cascone61469462019-03-05 23:59:11 -080058import java.util.concurrent.CompletableFuture;
59import java.util.concurrent.locks.Lock;
Carmelo Casconefe99be92017-09-11 21:55:54 +020060import java.util.stream.Collectors;
Daniele Moro46b27632021-02-01 17:12:16 +010061import java.util.stream.Stream;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020062
CyberHasHe9ba39c2019-10-11 05:59:12 +080063import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_DELETE_BEFORE_UPDATE;
64import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_READ_COUNTERS_WITH_TABLE_ENTRIES;
65import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_READ_FROM_MIRROR;
66import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_SUPPORT_DEFAULT_TABLE_ENTRY;
67import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_SUPPORT_TABLE_COUNTERS;
Daniele Moro46b27632021-02-01 17:12:16 +010068import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_TABLE_WILCARD_READS;
CyberHasHe9ba39c2019-10-11 05:59:12 +080069import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DELETE_BEFORE_UPDATE;
70import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.READ_COUNTERS_WITH_TABLE_ENTRIES;
71import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.READ_FROM_MIRROR;
72import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.SUPPORT_DEFAULT_TABLE_ENTRY;
73import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.SUPPORT_TABLE_COUNTERS;
Daniele Moro46b27632021-02-01 17:12:16 +010074import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.TABLE_WILCARD_READS;
Andrea Campanella0288c872017-08-07 18:32:51 +020075import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.APPLY;
76import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.REMOVE;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020077import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080078import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType.DELETE;
79import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType.INSERT;
80import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType.MODIFY;
Frank Wang0e805082017-07-21 14:37:35 +080081
82/**
Carmelo Casconee3a7c742017-09-01 01:25:52 +020083 * Implementation of the flow rule programmable behaviour for P4Runtime.
Frank Wang0e805082017-07-21 14:37:35 +080084 */
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080085public class P4RuntimeFlowRuleProgrammable
86 extends AbstractP4RuntimeHandlerBehaviour
87 implements FlowRuleProgrammable {
Frank Wang0e805082017-07-21 14:37:35 +080088
Carmelo Cascone61469462019-03-05 23:59:11 -080089 // Used to make sure concurrent calls to write flow rules are serialized so
90 // that each request gets consistent access to mirror state.
91 private static final Striped<Lock> WRITE_LOCKS = Striped.lock(30);
92
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020093 private PiPipelineModel pipelineModel;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080094 private P4RuntimeTableMirror tableMirror;
95 private PiFlowRuleTranslator translator;
CyberHasHe9ba39c2019-10-11 05:59:12 +080096 private P4RuntimeDefaultEntryMirror defaultEntryMirror;
Frank Wang0e805082017-07-21 14:37:35 +080097
Carmelo Casconee3a7c742017-09-01 01:25:52 +020098 @Override
Carmelo Casconec32976e2019-04-08 14:50:52 -070099 protected boolean setupBehaviour(String opName) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200100
Carmelo Casconec32976e2019-04-08 14:50:52 -0700101 if (!super.setupBehaviour(opName)) {
Frank Wang0e805082017-07-21 14:37:35 +0800102 return false;
103 }
104
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200105 pipelineModel = pipeconf.pipelineModel();
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800106 tableMirror = handler().get(P4RuntimeTableMirror.class);
Yi Tsengd7716482018-10-31 15:34:30 -0700107 translator = translationService.flowRuleTranslator();
CyberHasHe9ba39c2019-10-11 05:59:12 +0800108 defaultEntryMirror = handler().get(P4RuntimeDefaultEntryMirror.class);
Frank Wang0e805082017-07-21 14:37:35 +0800109 return true;
110 }
111
112 @Override
113 public Collection<FlowEntry> getFlowEntries() {
114
Carmelo Casconec32976e2019-04-08 14:50:52 -0700115 if (!setupBehaviour("getFlowEntries()")) {
Frank Wang0e805082017-07-21 14:37:35 +0800116 return Collections.emptyList();
117 }
118
CyberHasHe9ba39c2019-10-11 05:59:12 +0800119 if (driverBoolProperty(READ_FROM_MIRROR,
Carmelo Casconecd9c2dd2019-11-21 14:42:38 -0800120 DEFAULT_READ_FROM_MIRROR)) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800121 return getFlowEntriesFromMirror();
Carmelo Casconefe99be92017-09-11 21:55:54 +0200122 }
123
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800124 final ImmutableList.Builder<FlowEntry> result = ImmutableList.builder();
125 final List<PiTableEntry> inconsistentEntries = Lists.newArrayList();
Frank Wang0e805082017-07-21 14:37:35 +0800126
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800127 // Read table entries from device.
128 final Collection<PiTableEntry> deviceEntries = getAllTableEntriesFromDevice();
129 if (deviceEntries == null) {
130 // Potential error at the client level.
Carmelo Casconee5b28722018-06-22 17:28:28 +0200131 return Collections.emptyList();
132 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200133
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700134 // Synchronize mirror with the device state.
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800135 tableMirror.sync(deviceId, deviceEntries);
136
137 if (deviceEntries.isEmpty()) {
138 // Nothing to do.
139 return Collections.emptyList();
140 }
141
Carmelo Casconec32976e2019-04-08 14:50:52 -0700142 final Map<PiTableEntryHandle, PiCounterCellData> counterCellMap =
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700143 readEntryCounters(deviceEntries);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200144 // Forge flow entries with counter values.
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700145 for (PiTableEntry entry : deviceEntries) {
Carmelo Casconec32976e2019-04-08 14:50:52 -0700146 final PiTableEntryHandle handle = entry.handle(deviceId);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200147 final FlowEntry flowEntry = forgeFlowEntry(
Carmelo Casconec32976e2019-04-08 14:50:52 -0700148 entry, handle, counterCellMap.get(handle));
Carmelo Casconee5b28722018-06-22 17:28:28 +0200149 if (flowEntry == null) {
150 // Entry is on device but unknown to translation service or
Carmelo Casconecd9c2dd2019-11-21 14:42:38 -0800151 // device mirror. Inconsistent. Mark for removal if this is not
152 // an original default entry (i.e, the same defined in the P4
153 // program via default_action, which cannot be removed.)
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700154 if (!isOriginalDefaultEntry(entry)) {
155 inconsistentEntries.add(entry);
156 }
Carmelo Casconee5b28722018-06-22 17:28:28 +0200157 } else {
158 result.add(flowEntry);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200159 }
160 }
161
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800162 if (!inconsistentEntries.isEmpty()) {
Carmelo Cascone33b27bc2018-09-09 22:56:14 -0700163 // Trigger clean up of inconsistent entries.
Carmelo Cascone61469462019-03-05 23:59:11 -0800164 log.warn("Found {} inconsistent table entries on {}, removing them...",
165 inconsistentEntries.size(), deviceId);
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700166 // Submit delete request and update mirror when done.
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700167 client.write(p4DeviceId, pipeconf)
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700168 .entities(inconsistentEntries, DELETE)
169 .submit().whenComplete((response, ex) -> {
170 if (ex != null) {
171 log.error("Exception removing inconsistent table entries", ex);
172 } else {
173 log.debug("Successfully removed {} out of {} inconsistent entries",
174 response.success().size(), response.all().size());
175 }
176 tableMirror.applyWriteResponse(response);
177 });
178
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200179 }
180
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800181 return result.build();
Frank Wang0e805082017-07-21 14:37:35 +0800182 }
183
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800184 private Collection<PiTableEntry> getAllTableEntriesFromDevice() {
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700185 final P4RuntimeReadClient.ReadRequest request = client.read(
186 p4DeviceId, pipeconf);
Daniele Moro46b27632021-02-01 17:12:16 +0100187 final boolean supportDefaultTableEntry = driverBoolProperty(
188 SUPPORT_DEFAULT_TABLE_ENTRY, DEFAULT_SUPPORT_DEFAULT_TABLE_ENTRY);
189 final boolean tableWildcardReads = driverBoolProperty(
190 TABLE_WILCARD_READS, DEFAULT_TABLE_WILCARD_READS);
191 if (!tableWildcardReads) {
192 // Read entries from all non-constant tables, including default ones.
193 pipelineModel.tables().stream()
194 .filter(t -> !t.isConstantTable())
195 .forEach(t -> {
196 request.tableEntries(t.id());
197 if (supportDefaultTableEntry && t.constDefaultAction().isEmpty()) {
198 request.defaultTableEntry(t.id());
199 }
200 });
201 } else {
202 request.allTableEntries();
203 if (supportDefaultTableEntry) {
204 request.allDefaultTableEntries();
205 }
206 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800207 final P4RuntimeReadClient.ReadResponse response = request.submitSync();
208 if (!response.isSuccess()) {
209 return null;
210 }
Daniele Moro46b27632021-02-01 17:12:16 +0100211 Stream<PiTableEntry> piTableEntries = response.all(PiTableEntry.class).stream()
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800212 // Device implementation might return duplicate entries. For
213 // example if reading only default ones is not supported and
214 // non-default entries are returned, by using distinct() we
215 // are robust against that possibility.
Daniele Moro46b27632021-02-01 17:12:16 +0100216 .distinct();
217 if (tableWildcardReads) {
218 // When doing a wildcard read on all tables, the device might
219 // return table entries of tables not present in the pipeline
220 // model or constant (default) entries that are filtered out.
221 piTableEntries = piTableEntries.filter(te -> {
222 var piTableModel = pipelineModel.table(te.table());
223 if (piTableModel.isEmpty() ||
224 piTableModel.get().isConstantTable() ||
225 (supportDefaultTableEntry && piTableModel.get().constDefaultAction().isPresent())) {
226 return false;
227 }
228 return true;
229 });
230 }
231 return piTableEntries.collect(Collectors.toList());
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700232 }
233
Frank Wang0e805082017-07-21 14:37:35 +0800234 @Override
235 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200236 return processFlowRules(rules, APPLY);
Frank Wang0e805082017-07-21 14:37:35 +0800237 }
238
239 @Override
240 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200241 return processFlowRules(rules, REMOVE);
Frank Wang0e805082017-07-21 14:37:35 +0800242 }
243
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800244 private FlowEntry forgeFlowEntry(PiTableEntry entry,
Carmelo Casconec32976e2019-04-08 14:50:52 -0700245 PiTableEntryHandle handle,
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800246 PiCounterCellData cellData) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800247 final Optional<PiTranslatedEntity<FlowRule, PiTableEntry>>
248 translatedEntity = translator.lookup(handle);
249 final TimedEntry<PiTableEntry> timedEntry = tableMirror.get(handle);
250
CyberHasHe9ba39c2019-10-11 05:59:12 +0800251 // A default entry might not be present in the translation store if it
252 // was not inserted by an app. No need to log.
Carmelo Casconecd9c2dd2019-11-21 14:42:38 -0800253 if (translatedEntity.isEmpty()) {
CyberHasHe9ba39c2019-10-11 05:59:12 +0800254 if (!isOriginalDefaultEntry(entry)) {
255 log.warn("Table entry handle not found in translation store: {}", handle);
256 }
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800257 return null;
258 }
Carmelo Casconee44592f2018-09-12 02:24:47 -0700259 if (!translatedEntity.get().translated().equals(entry)) {
260 log.warn("Table entry obtained from device {} is different from " +
261 "one in in translation store: device={}, store={}",
262 deviceId, entry, translatedEntity.get().translated());
263 return null;
264 }
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800265 if (timedEntry == null) {
Carmelo Cascone26600972018-09-10 00:23:20 -0700266 log.warn("Table entry handle not found in device mirror: {}", handle);
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800267 return null;
268 }
269
270 if (cellData != null) {
271 return new DefaultFlowEntry(translatedEntity.get().original(),
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700272 ADDED, timedEntry.lifeSec(), cellData.packets(),
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800273 cellData.bytes());
274 } else {
275 return new DefaultFlowEntry(translatedEntity.get().original(),
276 ADDED, timedEntry.lifeSec(), 0, 0);
277 }
278 }
279
280 private Collection<FlowEntry> getFlowEntriesFromMirror() {
281 return tableMirror.getAll(deviceId).stream()
282 .map(timedEntry -> forgeFlowEntry(
Carmelo Casconec32976e2019-04-08 14:50:52 -0700283 timedEntry.entry(), timedEntry.entry().handle(deviceId), null))
Carmelo Cascone26600972018-09-10 00:23:20 -0700284 .filter(Objects::nonNull)
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800285 .collect(Collectors.toList());
286 }
287
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800288 private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules,
289 Operation driverOperation) {
Carmelo Casconec32976e2019-04-08 14:50:52 -0700290 if (!setupBehaviour("processFlowRules()") || rules.isEmpty()) {
Frank Wang0e805082017-07-21 14:37:35 +0800291 return Collections.emptyList();
292 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800293 // Created batched write request.
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700294 final WriteRequest request = client.write(p4DeviceId, pipeconf);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800295 // For each rule, translate to PI and append to write request.
296 final Map<PiHandle, FlowRule> handleToRuleMap = Maps.newHashMap();
297 final List<FlowRule> skippedRules = Lists.newArrayList();
Carmelo Cascone61469462019-03-05 23:59:11 -0800298 final CompletableFuture<WriteResponse> futureResponse;
299 WRITE_LOCKS.get(deviceId).lock();
300 try {
301 for (FlowRule rule : rules) {
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700302 // Translate.
Carmelo Cascone61469462019-03-05 23:59:11 -0800303 final PiTableEntry entry;
304 try {
305 entry = translator.translate(rule, pipeconf);
306 } catch (PiTranslationException e) {
307 log.warn("Unable to translate flow rule for pipeconf '{}': {} [{}]",
308 pipeconf.id(), e.getMessage(), rule);
309 // Next rule.
310 continue;
311 }
312 final PiTableEntryHandle handle = entry.handle(deviceId);
313 handleToRuleMap.put(handle, rule);
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700314 // Update translation store.
315 if (driverOperation.equals(APPLY)) {
316 translator.learn(handle, new PiTranslatedEntity<>(
317 rule, entry, handle));
318 } else {
319 translator.forget(handle);
320 }
Carmelo Cascone61469462019-03-05 23:59:11 -0800321 // Append entry to batched write request (returns false), or skip (true)
322 if (appendEntryToWriteRequestOrSkip(
323 request, handle, entry, driverOperation)) {
324 skippedRules.add(rule);
Carmelo Cascone61469462019-03-05 23:59:11 -0800325 }
Frank Wang0e805082017-07-21 14:37:35 +0800326 }
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700327 if (request.pendingUpdates().isEmpty()) {
328 // All good. No need to write on device.
329 return rules;
330 }
Carmelo Cascone61469462019-03-05 23:59:11 -0800331 // Update mirror.
332 tableMirror.applyWriteRequest(request);
333 // Async submit request to server.
334 futureResponse = request.submit();
335 } finally {
336 WRITE_LOCKS.get(deviceId).unlock();
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200337 }
Carmelo Cascone61469462019-03-05 23:59:11 -0800338 // Wait for response.
339 final WriteResponse response = Futures.getUnchecked(futureResponse);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800340 // Derive successfully applied flow rule from response.
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700341 final List<FlowRule> appliedRules = getAppliedFlowRules(
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800342 response, handleToRuleMap, driverOperation);
343 // Return skipped and applied rules.
344 return ImmutableList.<FlowRule>builder()
345 .addAll(skippedRules).addAll(appliedRules).build();
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800346 }
347
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700348 private List<FlowRule> getAppliedFlowRules(
Carmelo Cascone61469462019-03-05 23:59:11 -0800349 WriteResponse response,
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800350 Map<PiHandle, FlowRule> handleToFlowRuleMap,
351 Operation driverOperation) {
352 // Returns a list of flow rules that were successfully written on the
353 // server according to the given write response and operation.
354 return response.success().stream()
355 .filter(r -> r.entityType().equals(PiEntityType.TABLE_ENTRY))
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700356 .filter(r -> {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800357 // Filter intermediate responses (e.g. P4Runtime DELETE
358 // during FlowRule APPLY because we are performing
359 // delete-before-update)
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700360 return isUpdateTypeRelevant(r.updateType(), driverOperation);
361 })
362 .map(r -> {
363 final FlowRule rule = handleToFlowRuleMap.get(r.handle());
364 if (rule == null) {
365 log.warn("Server returned unrecognized table entry " +
366 "handle in write response: {}", r.handle());
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800367 }
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700368 return rule;
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800369 })
370 .filter(Objects::nonNull)
371 .collect(Collectors.toList());
372 }
373
374 private boolean isUpdateTypeRelevant(UpdateType p4UpdateType, Operation driverOperation) {
375 switch (p4UpdateType) {
376 case INSERT:
377 case MODIFY:
378 if (!driverOperation.equals(APPLY)) {
379 return false;
380 }
381 break;
382 case DELETE:
383 if (!driverOperation.equals(REMOVE)) {
384 return false;
385 }
386 break;
387 default:
388 log.error("Unknown update type {}", p4UpdateType);
389 return false;
390 }
391 return true;
392 }
393
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800394 private boolean appendEntryToWriteRequestOrSkip(
Carmelo Cascone61469462019-03-05 23:59:11 -0800395 final WriteRequest writeRequest,
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800396 final PiTableEntryHandle handle,
397 PiTableEntry piEntryToApply,
398 final Operation driverOperation) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800399 // Depending on the driver operation, and if a matching rule exists on
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800400 // the device/mirror, decide which P4Runtime update operation to perform
401 // for this entry. In some cases, the entry is skipped from the write
402 // request but we want to return the corresponding flow rule as
403 // successfully written. In this case, we return true.
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800404 final TimedEntry<PiTableEntry> piEntryOnDevice = tableMirror.get(handle);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800405 final UpdateType updateType;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700406
Carmelo Cascone1ae25062019-03-07 16:02:22 -0800407 final boolean supportDefaultEntry = driverBoolProperty(
CyberHasHe9ba39c2019-10-11 05:59:12 +0800408 SUPPORT_DEFAULT_TABLE_ENTRY,
409 DEFAULT_SUPPORT_DEFAULT_TABLE_ENTRY);
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700410 final boolean deleteBeforeUpdate = driverBoolProperty(
CyberHasHe9ba39c2019-10-11 05:59:12 +0800411 DELETE_BEFORE_UPDATE,
412 DEFAULT_DELETE_BEFORE_UPDATE);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800413
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800414 if (driverOperation == APPLY) {
415 if (piEntryOnDevice == null) {
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700416 // Entry is first-timer, INSERT or MODIFY if default action.
Carmelo Cascone1ae25062019-03-07 16:02:22 -0800417 updateType = !piEntryToApply.isDefaultAction() || !supportDefaultEntry
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700418 ? INSERT : MODIFY;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800419 } else {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800420 if (piEntryToApply.action().equals(piEntryOnDevice.entry().action())) {
421 // FIXME: should we check for other attributes of the table
422 // entry? For example can we modify the priority?
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800423 log.debug("Ignoring re-apply of existing entry: {}", piEntryToApply);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800424 return true;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700425 } else if (deleteBeforeUpdate && !piEntryToApply.isDefaultAction()) {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800426 // Some devices return error when updating existing entries.
427 // If requested, remove entry before re-inserting the
428 // modified one, except the default action entry, that
429 // cannot be removed.
430 writeRequest.delete(handle);
431 updateType = INSERT;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800432 } else {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800433 updateType = MODIFY;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800434 }
435 }
436 } else {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800437 // REMOVE.
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700438 if (piEntryToApply.isDefaultAction()) {
CyberHasHe9ba39c2019-10-11 05:59:12 +0800439 // Cannot remove default action. Instead we should modify it to
440 // use the original one as specified in the P4 program.
Carmelo Casconeaf877e92019-04-28 13:44:01 -0700441 final PiTableEntry originalDefaultEntry = getOriginalDefaultEntry(
442 piEntryToApply.table());
443 if (originalDefaultEntry == null) {
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700444 return false;
445 }
Carmelo Casconeaf877e92019-04-28 13:44:01 -0700446 return appendEntryToWriteRequestOrSkip(
447 writeRequest, originalDefaultEntry.handle(deviceId),
448 originalDefaultEntry, APPLY);
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700449 } else {
Carmelo Casconeaf877e92019-04-28 13:44:01 -0700450 if (piEntryOnDevice == null) {
451 log.debug("Ignoring delete of missing entry: {}",
452 piEntryToApply);
453 return true;
454 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800455 updateType = DELETE;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700456 }
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800457 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800458 writeRequest.entity(piEntryToApply, updateType);
459 return false;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800460 }
461
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700462 private PiTableEntry getOriginalDefaultEntry(PiTableId tableId) {
CyberHasHe9ba39c2019-10-11 05:59:12 +0800463 final PiTableEntryHandle handle = PiTableEntry.builder()
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700464 .forTable(tableId)
CyberHasHe9ba39c2019-10-11 05:59:12 +0800465 .withMatchKey(PiMatchKey.EMPTY)
466 .build()
467 .handle(deviceId);
468 final TimedEntry<PiTableEntry> originalDefaultEntry = defaultEntryMirror.get(handle);
469 if (originalDefaultEntry != null) {
470 return originalDefaultEntry.entry();
471 }
472 return null;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700473 }
474
475 private boolean isOriginalDefaultEntry(PiTableEntry entry) {
476 if (!entry.isDefaultAction()) {
477 return false;
478 }
479 final PiTableEntry originalDefaultEntry = getOriginalDefaultEntry(entry.table());
CyberHasHe9ba39c2019-10-11 05:59:12 +0800480 if (originalDefaultEntry == null) {
481 return false;
482 }
483 // Sometimes the default action may be null
484 // e.g. In basic pipeline, the default action in wcmp_table is null
485 if (originalDefaultEntry.action() == null) {
486 return entry.action() == null;
487 }
488 return originalDefaultEntry.action().equals(entry.action());
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700489 }
490
Carmelo Casconec32976e2019-04-08 14:50:52 -0700491 private Map<PiTableEntryHandle, PiCounterCellData> readEntryCounters(
Carmelo Cascone255125d2018-04-11 14:03:22 -0700492 Collection<PiTableEntry> tableEntries) {
Carmelo Casconec32976e2019-04-08 14:50:52 -0700493
Carmelo Cascone255125d2018-04-11 14:03:22 -0700494 if (!driverBoolProperty(SUPPORT_TABLE_COUNTERS,
Carmelo Casconee5b28722018-06-22 17:28:28 +0200495 DEFAULT_SUPPORT_TABLE_COUNTERS)
496 || tableEntries.isEmpty()) {
Carmelo Cascone255125d2018-04-11 14:03:22 -0700497 return Collections.emptyMap();
498 }
499
Carmelo Casconec32976e2019-04-08 14:50:52 -0700500 if (driverBoolProperty(READ_COUNTERS_WITH_TABLE_ENTRIES,
501 DEFAULT_READ_COUNTERS_WITH_TABLE_ENTRIES)) {
502 return tableEntries.stream()
503 .filter(t -> t.counter() != null)
504 .collect(Collectors.toMap(
505 t -> t.handle(deviceId), PiTableEntry::counter));
506 } else {
507 final Set<PiHandle> cellHandles = tableEntries.stream()
508 .filter(e -> !e.isDefaultAction())
509 .filter(e -> tableHasCounter(e.table()))
510 .map(PiCounterCellId::ofDirect)
511 .map(id -> PiCounterCellHandle.of(deviceId, id))
512 .collect(Collectors.toSet());
513 // FIXME: We might be sending a very large read request...
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700514 return client.read(p4DeviceId, pipeconf)
Carmelo Casconec32976e2019-04-08 14:50:52 -0700515 .handles(cellHandles)
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800516 .submitSync()
517 .all(PiCounterCell.class).stream()
518 .filter(c -> c.cellId().counterType().equals(PiCounterType.DIRECT))
Carmelo Casconec32976e2019-04-08 14:50:52 -0700519 .collect(Collectors.toMap(
520 c -> c.cellId().tableEntry().handle(deviceId),
521 PiCounterCell::data));
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800522 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200523 }
524
Carmelo Cascone255125d2018-04-11 14:03:22 -0700525 private boolean tableHasCounter(PiTableId tableId) {
526 return pipelineModel.table(tableId).isPresent() &&
527 !pipelineModel.table(tableId).get().counters().isEmpty();
528 }
529
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200530 enum Operation {
531 APPLY, REMOVE
Frank Wang0e805082017-07-21 14:37:35 +0800532 }
Carmelo Cascone87892e22017-11-13 16:01:29 -0800533}