blob: 8a1f99ab96db6e33cc9d45d9e367633593826c17 [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;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080024import org.onosproject.drivers.p4runtime.mirror.P4RuntimeTableMirror;
25import org.onosproject.drivers.p4runtime.mirror.TimedEntry;
Frank Wang0e805082017-07-21 14:37:35 +080026import org.onosproject.net.flow.DefaultFlowEntry;
27import org.onosproject.net.flow.FlowEntry;
28import org.onosproject.net.flow.FlowRule;
29import org.onosproject.net.flow.FlowRuleProgrammable;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080030import org.onosproject.net.pi.model.PiCounterType;
Carmelo Cascone50d195f2018-09-11 13:26:38 -070031import org.onosproject.net.pi.model.PiPipelineInterpreter;
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;
Frank Wang0e805082017-07-21 14:37:35 +080040import org.onosproject.net.pi.runtime.PiTableEntry;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080041import org.onosproject.net.pi.runtime.PiTableEntryHandle;
42import org.onosproject.net.pi.service.PiFlowRuleTranslator;
43import org.onosproject.net.pi.service.PiTranslatedEntity;
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080044import org.onosproject.net.pi.service.PiTranslationException;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080045import org.onosproject.p4runtime.api.P4RuntimeReadClient;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080046import org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType;
Carmelo Cascone61469462019-03-05 23:59:11 -080047import org.onosproject.p4runtime.api.P4RuntimeWriteClient.WriteRequest;
48import org.onosproject.p4runtime.api.P4RuntimeWriteClient.WriteResponse;
Frank Wang0e805082017-07-21 14:37:35 +080049
50import java.util.Collection;
51import java.util.Collections;
Manjunath Vanaraj59ad6572017-12-26 11:10:57 +053052import java.util.List;
Carmelo Cascone3da671a2018-02-12 10:43:35 -080053import java.util.Map;
Carmelo Cascone26600972018-09-10 00:23:20 -070054import java.util.Objects;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080055import java.util.Optional;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020056import java.util.Set;
Carmelo Cascone61469462019-03-05 23:59:11 -080057import java.util.concurrent.CompletableFuture;
58import java.util.concurrent.locks.Lock;
Carmelo Casconefe99be92017-09-11 21:55:54 +020059import java.util.stream.Collectors;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020060
Andrea Campanella0288c872017-08-07 18:32:51 +020061import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.APPLY;
62import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.REMOVE;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020063import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080064import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType.DELETE;
65import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType.INSERT;
66import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType.MODIFY;
Frank Wang0e805082017-07-21 14:37:35 +080067
68/**
Carmelo Casconee3a7c742017-09-01 01:25:52 +020069 * Implementation of the flow rule programmable behaviour for P4Runtime.
Frank Wang0e805082017-07-21 14:37:35 +080070 */
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080071public class P4RuntimeFlowRuleProgrammable
72 extends AbstractP4RuntimeHandlerBehaviour
73 implements FlowRuleProgrammable {
Frank Wang0e805082017-07-21 14:37:35 +080074
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080075 // When updating an existing rule, if true, we issue a DELETE operation
76 // before inserting the new one, otherwise we issue a MODIFY operation. This
77 // is useful fore devices that do not support MODIFY operations for table
78 // entries.
Carmelo Cascone3da671a2018-02-12 10:43:35 -080079 private static final String DELETE_BEFORE_UPDATE = "tableDeleteBeforeUpdate";
80 private static final boolean DEFAULT_DELETE_BEFORE_UPDATE = false;
Carmelo Cascone2308e522017-08-25 02:35:12 +020081
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080082 // If true, we avoid querying the device and return what's already known by
83 // the ONOS store.
Carmelo Cascone3da671a2018-02-12 10:43:35 -080084 private static final String READ_FROM_MIRROR = "tableReadFromMirror";
85 private static final boolean DEFAULT_READ_FROM_MIRROR = false;
Carmelo Casconefe99be92017-09-11 21:55:54 +020086
Carmelo Cascone255125d2018-04-11 14:03:22 -070087 // If true, we read counters when reading table entries (if table has
88 // counters). Otherwise, we don't.
89 private static final String SUPPORT_TABLE_COUNTERS = "supportTableCounters";
90 private static final boolean DEFAULT_SUPPORT_TABLE_COUNTERS = true;
91
steven308017632e152018-10-20 00:51:08 +080092 // If true, assumes that the device returns table entry message populated
93 // with direct counter values. If false, we issue a second P4Runtime request
94 // to read the direct counter values.
95 private static final String READ_COUNTERS_WITH_TABLE_ENTRIES = "tableReadCountersWithTableEntries";
96 private static final boolean DEFAULT_READ_COUNTERS_WITH_TABLE_ENTRIES = true;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020097
Carmelo Cascone50d195f2018-09-11 13:26:38 -070098 // For default entries, P4Runtime mandates that only MODIFY messages are
99 // allowed. If true, treats default entries as normal table entries,
100 // e.g. inserting them first.
101 private static final String TABLE_DEFAULT_AS_ENTRY = "tableDefaultAsEntry";
102 private static final boolean DEFAULT_TABLE_DEFAULT_AS_ENTRY = false;
103
Carmelo Cascone61469462019-03-05 23:59:11 -0800104 // Used to make sure concurrent calls to write flow rules are serialized so
105 // that each request gets consistent access to mirror state.
106 private static final Striped<Lock> WRITE_LOCKS = Striped.lock(30);
107
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200108 private PiPipelineModel pipelineModel;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800109 private P4RuntimeTableMirror tableMirror;
110 private PiFlowRuleTranslator translator;
Frank Wang0e805082017-07-21 14:37:35 +0800111
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200112 @Override
113 protected boolean setupBehaviour() {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200114
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200115 if (!super.setupBehaviour()) {
Frank Wang0e805082017-07-21 14:37:35 +0800116 return false;
117 }
118
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200119 pipelineModel = pipeconf.pipelineModel();
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800120 tableMirror = handler().get(P4RuntimeTableMirror.class);
Yi Tsengd7716482018-10-31 15:34:30 -0700121 translator = translationService.flowRuleTranslator();
Frank Wang0e805082017-07-21 14:37:35 +0800122 return true;
123 }
124
125 @Override
126 public Collection<FlowEntry> getFlowEntries() {
127
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200128 if (!setupBehaviour()) {
Frank Wang0e805082017-07-21 14:37:35 +0800129 return Collections.emptyList();
130 }
131
Carmelo Cascone3da671a2018-02-12 10:43:35 -0800132 if (driverBoolProperty(READ_FROM_MIRROR, DEFAULT_READ_FROM_MIRROR)) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800133 return getFlowEntriesFromMirror();
Carmelo Casconefe99be92017-09-11 21:55:54 +0200134 }
135
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800136 final ImmutableList.Builder<FlowEntry> result = ImmutableList.builder();
137 final List<PiTableEntry> inconsistentEntries = Lists.newArrayList();
Frank Wang0e805082017-07-21 14:37:35 +0800138
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800139 // Read table entries from device.
140 final Collection<PiTableEntry> deviceEntries = getAllTableEntriesFromDevice();
141 if (deviceEntries == null) {
142 // Potential error at the client level.
Carmelo Casconee5b28722018-06-22 17:28:28 +0200143 return Collections.emptyList();
144 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200145
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700146 // Synchronize mirror with the device state.
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800147 tableMirror.sync(deviceId, deviceEntries);
148
149 if (deviceEntries.isEmpty()) {
150 // Nothing to do.
151 return Collections.emptyList();
152 }
153
Carmelo Casconee5b28722018-06-22 17:28:28 +0200154 final Map<PiTableEntry, PiCounterCellData> counterCellMap =
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700155 readEntryCounters(deviceEntries);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200156 // Forge flow entries with counter values.
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700157 for (PiTableEntry entry : deviceEntries) {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200158 final FlowEntry flowEntry = forgeFlowEntry(
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700159 entry, counterCellMap.get(entry));
Carmelo Casconee5b28722018-06-22 17:28:28 +0200160 if (flowEntry == null) {
161 // Entry is on device but unknown to translation service or
162 // device mirror. Inconsistent. Mark for removal.
163 // TODO: make this behaviour configurable
164 // In some cases it's fine for the device to have rules
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700165 // that were not installed by us, e.g. original default entry.
166 if (!isOriginalDefaultEntry(entry)) {
167 inconsistentEntries.add(entry);
168 }
Carmelo Casconee5b28722018-06-22 17:28:28 +0200169 } else {
170 result.add(flowEntry);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200171 }
172 }
173
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800174 if (!inconsistentEntries.isEmpty()) {
Carmelo Cascone33b27bc2018-09-09 22:56:14 -0700175 // Trigger clean up of inconsistent entries.
Carmelo Cascone61469462019-03-05 23:59:11 -0800176 log.warn("Found {} inconsistent table entries on {}, removing them...",
177 inconsistentEntries.size(), deviceId);
178 final WriteRequest request = client.write(pipeconf)
179 .entities(inconsistentEntries, DELETE);
180 WRITE_LOCKS.get(deviceId).lock();
181 // Update mirror and async submit delete request.
182 try {
183 tableMirror.applyWriteRequest(request);
184 request.submit().whenComplete((response, ex) -> {
185 if (ex != null) {
186 log.error("Exception removing inconsistent table entries", ex);
187 } else {
188 log.debug("Successfully removed {} out of {} inconsistent entries",
189 response.success().size(), response.all().size());
190 }
191 });
192 } finally {
193 WRITE_LOCKS.get(deviceId).unlock();
194 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200195 }
196
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800197 return result.build();
Frank Wang0e805082017-07-21 14:37:35 +0800198 }
199
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800200 private Collection<PiTableEntry> getAllTableEntriesFromDevice() {
201 final P4RuntimeReadClient.ReadRequest request = client.read(pipeconf);
202 // Read entries from all non-constant tables, including default ones.
203 pipelineModel.tables().stream()
204 .filter(t -> !t.isConstantTable())
205 .forEach(t -> {
206 request.tableEntries(t.id());
207 if (!t.constDefaultAction().isPresent()) {
208 request.defaultTableEntry(t.id());
209 }
210 });
211 final P4RuntimeReadClient.ReadResponse response = request.submitSync();
212 if (!response.isSuccess()) {
213 return null;
214 }
215 return response.all(PiTableEntry.class).stream()
216 // Device implementation might return duplicate entries. For
217 // example if reading only default ones is not supported and
218 // non-default entries are returned, by using distinct() we
219 // are robust against that possibility.
220 .distinct()
221 .collect(Collectors.toList());
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700222 }
223
Frank Wang0e805082017-07-21 14:37:35 +0800224 @Override
225 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200226 return processFlowRules(rules, APPLY);
Frank Wang0e805082017-07-21 14:37:35 +0800227 }
228
229 @Override
230 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200231 return processFlowRules(rules, REMOVE);
Frank Wang0e805082017-07-21 14:37:35 +0800232 }
233
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800234 private FlowEntry forgeFlowEntry(PiTableEntry entry,
235 PiCounterCellData cellData) {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800236 final PiTableEntryHandle handle = entry.handle(deviceId);
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800237 final Optional<PiTranslatedEntity<FlowRule, PiTableEntry>>
238 translatedEntity = translator.lookup(handle);
239 final TimedEntry<PiTableEntry> timedEntry = tableMirror.get(handle);
240
241 if (!translatedEntity.isPresent()) {
Carmelo Cascone26600972018-09-10 00:23:20 -0700242 log.warn("Table entry handle not found in translation store: {}", handle);
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800243 return null;
244 }
Carmelo Casconee44592f2018-09-12 02:24:47 -0700245 if (!translatedEntity.get().translated().equals(entry)) {
246 log.warn("Table entry obtained from device {} is different from " +
247 "one in in translation store: device={}, store={}",
248 deviceId, entry, translatedEntity.get().translated());
249 return null;
250 }
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800251 if (timedEntry == null) {
Carmelo Cascone26600972018-09-10 00:23:20 -0700252 log.warn("Table entry handle not found in device mirror: {}", handle);
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800253 return null;
254 }
255
256 if (cellData != null) {
257 return new DefaultFlowEntry(translatedEntity.get().original(),
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700258 ADDED, timedEntry.lifeSec(), cellData.packets(),
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800259 cellData.bytes());
260 } else {
261 return new DefaultFlowEntry(translatedEntity.get().original(),
262 ADDED, timedEntry.lifeSec(), 0, 0);
263 }
264 }
265
266 private Collection<FlowEntry> getFlowEntriesFromMirror() {
267 return tableMirror.getAll(deviceId).stream()
268 .map(timedEntry -> forgeFlowEntry(
269 timedEntry.entry(), null))
Carmelo Cascone26600972018-09-10 00:23:20 -0700270 .filter(Objects::nonNull)
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800271 .collect(Collectors.toList());
272 }
273
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800274 private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules,
275 Operation driverOperation) {
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700276 if (!setupBehaviour() || rules.isEmpty()) {
Frank Wang0e805082017-07-21 14:37:35 +0800277 return Collections.emptyList();
278 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800279 // Created batched write request.
Carmelo Cascone61469462019-03-05 23:59:11 -0800280 final WriteRequest request = client.write(pipeconf);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800281 // For each rule, translate to PI and append to write request.
282 final Map<PiHandle, FlowRule> handleToRuleMap = Maps.newHashMap();
283 final List<FlowRule> skippedRules = Lists.newArrayList();
Carmelo Cascone61469462019-03-05 23:59:11 -0800284 final CompletableFuture<WriteResponse> futureResponse;
285 WRITE_LOCKS.get(deviceId).lock();
286 try {
287 for (FlowRule rule : rules) {
288 final PiTableEntry entry;
289 try {
290 entry = translator.translate(rule, pipeconf);
291 } catch (PiTranslationException e) {
292 log.warn("Unable to translate flow rule for pipeconf '{}': {} [{}]",
293 pipeconf.id(), e.getMessage(), rule);
294 // Next rule.
295 continue;
296 }
297 final PiTableEntryHandle handle = entry.handle(deviceId);
298 handleToRuleMap.put(handle, rule);
299 // Append entry to batched write request (returns false), or skip (true)
300 if (appendEntryToWriteRequestOrSkip(
301 request, handle, entry, driverOperation)) {
302 skippedRules.add(rule);
303 updateTranslationStore(
304 driverOperation, handle, rule, entry);
305 }
Frank Wang0e805082017-07-21 14:37:35 +0800306 }
Carmelo Cascone61469462019-03-05 23:59:11 -0800307 // Update mirror.
308 tableMirror.applyWriteRequest(request);
309 // Async submit request to server.
310 futureResponse = request.submit();
311 } finally {
312 WRITE_LOCKS.get(deviceId).unlock();
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200313 }
Carmelo Cascone61469462019-03-05 23:59:11 -0800314 // Wait for response.
315 final WriteResponse response = Futures.getUnchecked(futureResponse);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800316 // Derive successfully applied flow rule from response.
317 final List<FlowRule> appliedRules = getAppliedFlowRulesAndUpdateTranslator(
318 response, handleToRuleMap, driverOperation);
319 // Return skipped and applied rules.
320 return ImmutableList.<FlowRule>builder()
321 .addAll(skippedRules).addAll(appliedRules).build();
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800322 }
323
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800324 private List<FlowRule> getAppliedFlowRulesAndUpdateTranslator(
Carmelo Cascone61469462019-03-05 23:59:11 -0800325 WriteResponse response,
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800326 Map<PiHandle, FlowRule> handleToFlowRuleMap,
327 Operation driverOperation) {
328 // Returns a list of flow rules that were successfully written on the
329 // server according to the given write response and operation.
330 return response.success().stream()
331 .filter(r -> r.entityType().equals(PiEntityType.TABLE_ENTRY))
332 .map(r -> {
333 final PiHandle handle = r.handle();
334 final FlowRule rule = handleToFlowRuleMap.get(handle);
335 if (rule == null) {
336 log.error("Server returned unrecognized table entry " +
337 "handle in write response: {}", handle);
338 return null;
339 }
340 // Filter intermediate responses (e.g. P4Runtime DELETE
341 // during FlowRule APPLY because we are performing
342 // delete-before-update)
343 if (isUpdateTypeRelevant(r.updateType(), driverOperation)) {
344 updateTranslationStore(
345 driverOperation, (PiTableEntryHandle) handle,
346 rule, (PiTableEntry) r.entity());
347 return rule;
348 }
349 return null;
350 })
351 .filter(Objects::nonNull)
352 .collect(Collectors.toList());
353 }
354
355 private boolean isUpdateTypeRelevant(UpdateType p4UpdateType, Operation driverOperation) {
356 switch (p4UpdateType) {
357 case INSERT:
358 case MODIFY:
359 if (!driverOperation.equals(APPLY)) {
360 return false;
361 }
362 break;
363 case DELETE:
364 if (!driverOperation.equals(REMOVE)) {
365 return false;
366 }
367 break;
368 default:
369 log.error("Unknown update type {}", p4UpdateType);
370 return false;
371 }
372 return true;
373 }
374
375 private void updateTranslationStore(
376 Operation operation, PiTableEntryHandle handle,
377 FlowRule rule, PiTableEntry entry) {
378 if (operation.equals(APPLY)) {
379 translator.learn(handle, new PiTranslatedEntity<>(
380 rule, entry, handle));
381 } else {
382 translator.forget(handle);
383 }
384 }
385
386 private boolean appendEntryToWriteRequestOrSkip(
Carmelo Cascone61469462019-03-05 23:59:11 -0800387 final WriteRequest writeRequest,
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800388 final PiTableEntryHandle handle,
389 PiTableEntry piEntryToApply,
390 final Operation driverOperation) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800391 // Depending on the driver operation, and if a matching rule exists on
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800392 // the device/mirror, decide which P4Runtime update operation to perform
393 // for this entry. In some cases, the entry is skipped from the write
394 // request but we want to return the corresponding flow rule as
395 // successfully written. In this case, we return true.
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800396 final TimedEntry<PiTableEntry> piEntryOnDevice = tableMirror.get(handle);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800397 final UpdateType updateType;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700398
399 final boolean defaultAsEntry = driverBoolProperty(
400 TABLE_DEFAULT_AS_ENTRY, DEFAULT_TABLE_DEFAULT_AS_ENTRY);
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700401 final boolean deleteBeforeUpdate = driverBoolProperty(
402 DELETE_BEFORE_UPDATE, DEFAULT_DELETE_BEFORE_UPDATE);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800403
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800404 if (driverOperation == APPLY) {
405 if (piEntryOnDevice == null) {
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700406 // Entry is first-timer, INSERT or MODIFY if default action.
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800407 updateType = !piEntryToApply.isDefaultAction() || defaultAsEntry
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700408 ? INSERT : MODIFY;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800409 } else {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800410 if (piEntryToApply.action().equals(piEntryOnDevice.entry().action())) {
411 // FIXME: should we check for other attributes of the table
412 // entry? For example can we modify the priority?
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800413 log.debug("Ignoring re-apply of existing entry: {}", piEntryToApply);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800414 return true;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700415 } else if (deleteBeforeUpdate && !piEntryToApply.isDefaultAction()) {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800416 // Some devices return error when updating existing entries.
417 // If requested, remove entry before re-inserting the
418 // modified one, except the default action entry, that
419 // cannot be removed.
420 writeRequest.delete(handle);
421 updateType = INSERT;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800422 } else {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800423 updateType = MODIFY;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800424 }
425 }
426 } else {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800427 // REMOVE.
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700428 if (piEntryToApply.isDefaultAction()) {
429 // Cannot remove default action. Instead we should use the
430 // original defined by the interpreter (if any).
431 piEntryToApply = getOriginalDefaultEntry(piEntryToApply.table());
432 if (piEntryToApply == null) {
433 return false;
434 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800435 updateType = MODIFY;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700436 } else {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800437 updateType = DELETE;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700438 }
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800439 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800440 writeRequest.entity(piEntryToApply, updateType);
441 return false;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800442 }
443
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700444 private PiTableEntry getOriginalDefaultEntry(PiTableId tableId) {
445 final PiPipelineInterpreter interpreter = getInterpreter();
446 if (interpreter == null) {
447 log.warn("Missing interpreter for {}, cannot get default action",
448 deviceId);
449 return null;
450 }
451 if (!interpreter.getOriginalDefaultAction(tableId).isPresent()) {
452 log.warn("Interpreter of {} doesn't define a default action for " +
453 "table {}, cannot produce default action entry",
454 deviceId, tableId);
455 return null;
456 }
457 return PiTableEntry.builder()
458 .forTable(tableId)
459 .withAction(interpreter.getOriginalDefaultAction(tableId).get())
460 .build();
461 }
462
463 private boolean isOriginalDefaultEntry(PiTableEntry entry) {
464 if (!entry.isDefaultAction()) {
465 return false;
466 }
467 final PiTableEntry originalDefaultEntry = getOriginalDefaultEntry(entry.table());
468 return originalDefaultEntry != null &&
469 originalDefaultEntry.action().equals(entry.action());
470 }
471
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800472 private Map<PiTableEntry, PiCounterCellData> readEntryCounters(
Carmelo Cascone255125d2018-04-11 14:03:22 -0700473 Collection<PiTableEntry> tableEntries) {
474 if (!driverBoolProperty(SUPPORT_TABLE_COUNTERS,
Carmelo Casconee5b28722018-06-22 17:28:28 +0200475 DEFAULT_SUPPORT_TABLE_COUNTERS)
476 || tableEntries.isEmpty()) {
Carmelo Cascone255125d2018-04-11 14:03:22 -0700477 return Collections.emptyMap();
478 }
479
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800480 final Map<PiTableEntry, PiCounterCellData> cellDataMap = Maps.newHashMap();
481
482 // We expect the server to return table entries with counter data (if
483 // the table supports counter). Here we extract such counter data and we
484 // determine if there are missing counter cells (if, for example, the
485 // serves does not support returning counter data with table entries)
486 final Set<PiHandle> missingCellHandles = tableEntries.stream()
487 .map(t -> {
488 if (t.counter() != null) {
489 // Counter data found in table entry.
490 cellDataMap.put(t, t.counter());
491 return null;
492 } else {
493 return t;
494 }
495 })
496 .filter(Objects::nonNull)
497 // Ignore for default entries and for tables without counters.
498 .filter(e -> !e.isDefaultAction())
499 .filter(e -> tableHasCounter(e.table()))
500 .map(PiCounterCellId::ofDirect)
501 .map(id -> PiCounterCellHandle.of(deviceId, id))
502 .collect(Collectors.toSet());
503 // We might be sending a large read request (for thousands or more
504 // of counter cell handles). We request the driver to vet this
505 // operation via driver property.
506 if (!missingCellHandles.isEmpty()
507 && !driverBoolProperty(READ_COUNTERS_WITH_TABLE_ENTRIES,
508 DEFAULT_READ_COUNTERS_WITH_TABLE_ENTRIES)) {
509 client.read(pipeconf)
510 .handles(missingCellHandles)
511 .submitSync()
512 .all(PiCounterCell.class).stream()
513 .filter(c -> c.cellId().counterType().equals(PiCounterType.DIRECT))
514 .forEach(c -> cellDataMap.put(c.cellId().tableEntry(), c.data()));
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800515 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800516
517 return cellDataMap;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200518 }
519
Carmelo Cascone255125d2018-04-11 14:03:22 -0700520 private boolean tableHasCounter(PiTableId tableId) {
521 return pipelineModel.table(tableId).isPresent() &&
522 !pipelineModel.table(tableId).get().counters().isEmpty();
523 }
524
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200525 enum Operation {
526 APPLY, REMOVE
Frank Wang0e805082017-07-21 14:37:35 +0800527 }
Carmelo Cascone87892e22017-11-13 16:01:29 -0800528}