blob: a4ae0eff5648f03510eacf6afbde213f3bbb507b [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 Cascone1ae25062019-03-07 16:02:22 -080098 // True if target supports reading and writing table entries.
99 private static final String SUPPORT_DEFAULT_TABLE_ENTRY = "supportDefaultTableEntry";
100 private static final boolean DEFAULT_SUPPORT_DEFAULT_TABLE_ENTRY = true;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700101
Carmelo Cascone61469462019-03-05 23:59:11 -0800102 // Used to make sure concurrent calls to write flow rules are serialized so
103 // that each request gets consistent access to mirror state.
104 private static final Striped<Lock> WRITE_LOCKS = Striped.lock(30);
105
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200106 private PiPipelineModel pipelineModel;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800107 private P4RuntimeTableMirror tableMirror;
108 private PiFlowRuleTranslator translator;
Frank Wang0e805082017-07-21 14:37:35 +0800109
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200110 @Override
111 protected boolean setupBehaviour() {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200112
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200113 if (!super.setupBehaviour()) {
Frank Wang0e805082017-07-21 14:37:35 +0800114 return false;
115 }
116
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200117 pipelineModel = pipeconf.pipelineModel();
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800118 tableMirror = handler().get(P4RuntimeTableMirror.class);
Yi Tsengd7716482018-10-31 15:34:30 -0700119 translator = translationService.flowRuleTranslator();
Frank Wang0e805082017-07-21 14:37:35 +0800120 return true;
121 }
122
123 @Override
124 public Collection<FlowEntry> getFlowEntries() {
125
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200126 if (!setupBehaviour()) {
Frank Wang0e805082017-07-21 14:37:35 +0800127 return Collections.emptyList();
128 }
129
Carmelo Cascone3da671a2018-02-12 10:43:35 -0800130 if (driverBoolProperty(READ_FROM_MIRROR, DEFAULT_READ_FROM_MIRROR)) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800131 return getFlowEntriesFromMirror();
Carmelo Casconefe99be92017-09-11 21:55:54 +0200132 }
133
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800134 final ImmutableList.Builder<FlowEntry> result = ImmutableList.builder();
135 final List<PiTableEntry> inconsistentEntries = Lists.newArrayList();
Frank Wang0e805082017-07-21 14:37:35 +0800136
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800137 // Read table entries from device.
138 final Collection<PiTableEntry> deviceEntries = getAllTableEntriesFromDevice();
139 if (deviceEntries == null) {
140 // Potential error at the client level.
Carmelo Casconee5b28722018-06-22 17:28:28 +0200141 return Collections.emptyList();
142 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200143
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700144 // Synchronize mirror with the device state.
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800145 tableMirror.sync(deviceId, deviceEntries);
146
147 if (deviceEntries.isEmpty()) {
148 // Nothing to do.
149 return Collections.emptyList();
150 }
151
Carmelo Casconee5b28722018-06-22 17:28:28 +0200152 final Map<PiTableEntry, PiCounterCellData> counterCellMap =
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700153 readEntryCounters(deviceEntries);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200154 // Forge flow entries with counter values.
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700155 for (PiTableEntry entry : deviceEntries) {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200156 final FlowEntry flowEntry = forgeFlowEntry(
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700157 entry, counterCellMap.get(entry));
Carmelo Casconee5b28722018-06-22 17:28:28 +0200158 if (flowEntry == null) {
159 // Entry is on device but unknown to translation service or
160 // device mirror. Inconsistent. Mark for removal.
161 // TODO: make this behaviour configurable
162 // In some cases it's fine for the device to have rules
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700163 // that were not installed by us, e.g. original default entry.
164 if (!isOriginalDefaultEntry(entry)) {
165 inconsistentEntries.add(entry);
166 }
Carmelo Casconee5b28722018-06-22 17:28:28 +0200167 } else {
168 result.add(flowEntry);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200169 }
170 }
171
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800172 if (!inconsistentEntries.isEmpty()) {
Carmelo Cascone33b27bc2018-09-09 22:56:14 -0700173 // Trigger clean up of inconsistent entries.
Carmelo Cascone61469462019-03-05 23:59:11 -0800174 log.warn("Found {} inconsistent table entries on {}, removing them...",
175 inconsistentEntries.size(), deviceId);
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700176 // Submit delete request and update mirror when done.
177 client.write(pipeconf)
178 .entities(inconsistentEntries, DELETE)
179 .submit().whenComplete((response, ex) -> {
180 if (ex != null) {
181 log.error("Exception removing inconsistent table entries", ex);
182 } else {
183 log.debug("Successfully removed {} out of {} inconsistent entries",
184 response.success().size(), response.all().size());
185 }
186 tableMirror.applyWriteResponse(response);
187 });
188
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200189 }
190
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800191 return result.build();
Frank Wang0e805082017-07-21 14:37:35 +0800192 }
193
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800194 private Collection<PiTableEntry> getAllTableEntriesFromDevice() {
195 final P4RuntimeReadClient.ReadRequest request = client.read(pipeconf);
196 // Read entries from all non-constant tables, including default ones.
197 pipelineModel.tables().stream()
198 .filter(t -> !t.isConstantTable())
199 .forEach(t -> {
200 request.tableEntries(t.id());
Carmelo Cascone1ae25062019-03-07 16:02:22 -0800201 if (driverBoolProperty(SUPPORT_DEFAULT_TABLE_ENTRY,
202 DEFAULT_SUPPORT_DEFAULT_TABLE_ENTRY) &&
203 !t.constDefaultAction().isPresent()) {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800204 request.defaultTableEntry(t.id());
205 }
206 });
207 final P4RuntimeReadClient.ReadResponse response = request.submitSync();
208 if (!response.isSuccess()) {
209 return null;
210 }
211 return response.all(PiTableEntry.class).stream()
212 // 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.
216 .distinct()
217 .collect(Collectors.toList());
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700218 }
219
Frank Wang0e805082017-07-21 14:37:35 +0800220 @Override
221 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200222 return processFlowRules(rules, APPLY);
Frank Wang0e805082017-07-21 14:37:35 +0800223 }
224
225 @Override
226 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200227 return processFlowRules(rules, REMOVE);
Frank Wang0e805082017-07-21 14:37:35 +0800228 }
229
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800230 private FlowEntry forgeFlowEntry(PiTableEntry entry,
231 PiCounterCellData cellData) {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800232 final PiTableEntryHandle handle = entry.handle(deviceId);
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800233 final Optional<PiTranslatedEntity<FlowRule, PiTableEntry>>
234 translatedEntity = translator.lookup(handle);
235 final TimedEntry<PiTableEntry> timedEntry = tableMirror.get(handle);
236
237 if (!translatedEntity.isPresent()) {
Carmelo Cascone26600972018-09-10 00:23:20 -0700238 log.warn("Table entry handle not found in translation store: {}", handle);
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800239 return null;
240 }
Carmelo Casconee44592f2018-09-12 02:24:47 -0700241 if (!translatedEntity.get().translated().equals(entry)) {
242 log.warn("Table entry obtained from device {} is different from " +
243 "one in in translation store: device={}, store={}",
244 deviceId, entry, translatedEntity.get().translated());
245 return null;
246 }
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800247 if (timedEntry == null) {
Carmelo Cascone26600972018-09-10 00:23:20 -0700248 log.warn("Table entry handle not found in device mirror: {}", handle);
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800249 return null;
250 }
251
252 if (cellData != null) {
253 return new DefaultFlowEntry(translatedEntity.get().original(),
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700254 ADDED, timedEntry.lifeSec(), cellData.packets(),
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800255 cellData.bytes());
256 } else {
257 return new DefaultFlowEntry(translatedEntity.get().original(),
258 ADDED, timedEntry.lifeSec(), 0, 0);
259 }
260 }
261
262 private Collection<FlowEntry> getFlowEntriesFromMirror() {
263 return tableMirror.getAll(deviceId).stream()
264 .map(timedEntry -> forgeFlowEntry(
265 timedEntry.entry(), null))
Carmelo Cascone26600972018-09-10 00:23:20 -0700266 .filter(Objects::nonNull)
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800267 .collect(Collectors.toList());
268 }
269
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800270 private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules,
271 Operation driverOperation) {
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700272 if (!setupBehaviour() || rules.isEmpty()) {
Frank Wang0e805082017-07-21 14:37:35 +0800273 return Collections.emptyList();
274 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800275 // Created batched write request.
Carmelo Cascone61469462019-03-05 23:59:11 -0800276 final WriteRequest request = client.write(pipeconf);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800277 // For each rule, translate to PI and append to write request.
278 final Map<PiHandle, FlowRule> handleToRuleMap = Maps.newHashMap();
279 final List<FlowRule> skippedRules = Lists.newArrayList();
Carmelo Cascone61469462019-03-05 23:59:11 -0800280 final CompletableFuture<WriteResponse> futureResponse;
281 WRITE_LOCKS.get(deviceId).lock();
282 try {
283 for (FlowRule rule : rules) {
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700284 // Translate.
Carmelo Cascone61469462019-03-05 23:59:11 -0800285 final PiTableEntry entry;
286 try {
287 entry = translator.translate(rule, pipeconf);
288 } catch (PiTranslationException e) {
289 log.warn("Unable to translate flow rule for pipeconf '{}': {} [{}]",
290 pipeconf.id(), e.getMessage(), rule);
291 // Next rule.
292 continue;
293 }
294 final PiTableEntryHandle handle = entry.handle(deviceId);
295 handleToRuleMap.put(handle, rule);
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700296 // Update translation store.
297 if (driverOperation.equals(APPLY)) {
298 translator.learn(handle, new PiTranslatedEntity<>(
299 rule, entry, handle));
300 } else {
301 translator.forget(handle);
302 }
Carmelo Cascone61469462019-03-05 23:59:11 -0800303 // Append entry to batched write request (returns false), or skip (true)
304 if (appendEntryToWriteRequestOrSkip(
305 request, handle, entry, driverOperation)) {
306 skippedRules.add(rule);
Carmelo Cascone61469462019-03-05 23:59:11 -0800307 }
Frank Wang0e805082017-07-21 14:37:35 +0800308 }
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700309 if (request.pendingUpdates().isEmpty()) {
310 // All good. No need to write on device.
311 return rules;
312 }
Carmelo Cascone61469462019-03-05 23:59:11 -0800313 // Update mirror.
314 tableMirror.applyWriteRequest(request);
315 // Async submit request to server.
316 futureResponse = request.submit();
317 } finally {
318 WRITE_LOCKS.get(deviceId).unlock();
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200319 }
Carmelo Cascone61469462019-03-05 23:59:11 -0800320 // Wait for response.
321 final WriteResponse response = Futures.getUnchecked(futureResponse);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800322 // Derive successfully applied flow rule from response.
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700323 final List<FlowRule> appliedRules = getAppliedFlowRules(
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800324 response, handleToRuleMap, driverOperation);
325 // Return skipped and applied rules.
326 return ImmutableList.<FlowRule>builder()
327 .addAll(skippedRules).addAll(appliedRules).build();
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800328 }
329
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700330 private List<FlowRule> getAppliedFlowRules(
Carmelo Cascone61469462019-03-05 23:59:11 -0800331 WriteResponse response,
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800332 Map<PiHandle, FlowRule> handleToFlowRuleMap,
333 Operation driverOperation) {
334 // Returns a list of flow rules that were successfully written on the
335 // server according to the given write response and operation.
336 return response.success().stream()
337 .filter(r -> r.entityType().equals(PiEntityType.TABLE_ENTRY))
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700338 .filter(r -> {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800339 // Filter intermediate responses (e.g. P4Runtime DELETE
340 // during FlowRule APPLY because we are performing
341 // delete-before-update)
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700342 return isUpdateTypeRelevant(r.updateType(), driverOperation);
343 })
344 .map(r -> {
345 final FlowRule rule = handleToFlowRuleMap.get(r.handle());
346 if (rule == null) {
347 log.warn("Server returned unrecognized table entry " +
348 "handle in write response: {}", r.handle());
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800349 }
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700350 return rule;
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800351 })
352 .filter(Objects::nonNull)
353 .collect(Collectors.toList());
354 }
355
356 private boolean isUpdateTypeRelevant(UpdateType p4UpdateType, Operation driverOperation) {
357 switch (p4UpdateType) {
358 case INSERT:
359 case MODIFY:
360 if (!driverOperation.equals(APPLY)) {
361 return false;
362 }
363 break;
364 case DELETE:
365 if (!driverOperation.equals(REMOVE)) {
366 return false;
367 }
368 break;
369 default:
370 log.error("Unknown update type {}", p4UpdateType);
371 return false;
372 }
373 return true;
374 }
375
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800376 private boolean appendEntryToWriteRequestOrSkip(
Carmelo Cascone61469462019-03-05 23:59:11 -0800377 final WriteRequest writeRequest,
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800378 final PiTableEntryHandle handle,
379 PiTableEntry piEntryToApply,
380 final Operation driverOperation) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800381 // Depending on the driver operation, and if a matching rule exists on
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800382 // the device/mirror, decide which P4Runtime update operation to perform
383 // for this entry. In some cases, the entry is skipped from the write
384 // request but we want to return the corresponding flow rule as
385 // successfully written. In this case, we return true.
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800386 final TimedEntry<PiTableEntry> piEntryOnDevice = tableMirror.get(handle);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800387 final UpdateType updateType;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700388
Carmelo Cascone1ae25062019-03-07 16:02:22 -0800389 final boolean supportDefaultEntry = driverBoolProperty(
390 SUPPORT_DEFAULT_TABLE_ENTRY, DEFAULT_SUPPORT_DEFAULT_TABLE_ENTRY);
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700391 final boolean deleteBeforeUpdate = driverBoolProperty(
392 DELETE_BEFORE_UPDATE, DEFAULT_DELETE_BEFORE_UPDATE);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800393
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800394 if (driverOperation == APPLY) {
395 if (piEntryOnDevice == null) {
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700396 // Entry is first-timer, INSERT or MODIFY if default action.
Carmelo Cascone1ae25062019-03-07 16:02:22 -0800397 updateType = !piEntryToApply.isDefaultAction() || !supportDefaultEntry
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700398 ? INSERT : MODIFY;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800399 } else {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800400 if (piEntryToApply.action().equals(piEntryOnDevice.entry().action())) {
401 // FIXME: should we check for other attributes of the table
402 // entry? For example can we modify the priority?
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800403 log.debug("Ignoring re-apply of existing entry: {}", piEntryToApply);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800404 return true;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700405 } else if (deleteBeforeUpdate && !piEntryToApply.isDefaultAction()) {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800406 // Some devices return error when updating existing entries.
407 // If requested, remove entry before re-inserting the
408 // modified one, except the default action entry, that
409 // cannot be removed.
410 writeRequest.delete(handle);
411 updateType = INSERT;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800412 } else {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800413 updateType = MODIFY;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800414 }
415 }
416 } else {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800417 // REMOVE.
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700418 if (piEntryToApply.isDefaultAction()) {
419 // Cannot remove default action. Instead we should use the
420 // original defined by the interpreter (if any).
421 piEntryToApply = getOriginalDefaultEntry(piEntryToApply.table());
422 if (piEntryToApply == null) {
423 return false;
424 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800425 updateType = MODIFY;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700426 } else {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800427 updateType = DELETE;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700428 }
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800429 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800430 writeRequest.entity(piEntryToApply, updateType);
431 return false;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800432 }
433
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700434 private PiTableEntry getOriginalDefaultEntry(PiTableId tableId) {
435 final PiPipelineInterpreter interpreter = getInterpreter();
436 if (interpreter == null) {
437 log.warn("Missing interpreter for {}, cannot get default action",
438 deviceId);
439 return null;
440 }
441 if (!interpreter.getOriginalDefaultAction(tableId).isPresent()) {
442 log.warn("Interpreter of {} doesn't define a default action for " +
443 "table {}, cannot produce default action entry",
444 deviceId, tableId);
445 return null;
446 }
447 return PiTableEntry.builder()
448 .forTable(tableId)
449 .withAction(interpreter.getOriginalDefaultAction(tableId).get())
450 .build();
451 }
452
453 private boolean isOriginalDefaultEntry(PiTableEntry entry) {
454 if (!entry.isDefaultAction()) {
455 return false;
456 }
457 final PiTableEntry originalDefaultEntry = getOriginalDefaultEntry(entry.table());
458 return originalDefaultEntry != null &&
459 originalDefaultEntry.action().equals(entry.action());
460 }
461
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800462 private Map<PiTableEntry, PiCounterCellData> readEntryCounters(
Carmelo Cascone255125d2018-04-11 14:03:22 -0700463 Collection<PiTableEntry> tableEntries) {
464 if (!driverBoolProperty(SUPPORT_TABLE_COUNTERS,
Carmelo Casconee5b28722018-06-22 17:28:28 +0200465 DEFAULT_SUPPORT_TABLE_COUNTERS)
466 || tableEntries.isEmpty()) {
Carmelo Cascone255125d2018-04-11 14:03:22 -0700467 return Collections.emptyMap();
468 }
469
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800470 final Map<PiTableEntry, PiCounterCellData> cellDataMap = Maps.newHashMap();
471
472 // We expect the server to return table entries with counter data (if
473 // the table supports counter). Here we extract such counter data and we
474 // determine if there are missing counter cells (if, for example, the
475 // serves does not support returning counter data with table entries)
476 final Set<PiHandle> missingCellHandles = tableEntries.stream()
477 .map(t -> {
478 if (t.counter() != null) {
479 // Counter data found in table entry.
480 cellDataMap.put(t, t.counter());
481 return null;
482 } else {
483 return t;
484 }
485 })
486 .filter(Objects::nonNull)
487 // Ignore for default entries and for tables without counters.
488 .filter(e -> !e.isDefaultAction())
489 .filter(e -> tableHasCounter(e.table()))
490 .map(PiCounterCellId::ofDirect)
491 .map(id -> PiCounterCellHandle.of(deviceId, id))
492 .collect(Collectors.toSet());
493 // We might be sending a large read request (for thousands or more
494 // of counter cell handles). We request the driver to vet this
495 // operation via driver property.
496 if (!missingCellHandles.isEmpty()
497 && !driverBoolProperty(READ_COUNTERS_WITH_TABLE_ENTRIES,
498 DEFAULT_READ_COUNTERS_WITH_TABLE_ENTRIES)) {
499 client.read(pipeconf)
500 .handles(missingCellHandles)
501 .submitSync()
502 .all(PiCounterCell.class).stream()
503 .filter(c -> c.cellId().counterType().equals(PiCounterType.DIRECT))
504 .forEach(c -> cellDataMap.put(c.cellId().tableEntry(), c.data()));
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800505 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800506
507 return cellDataMap;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200508 }
509
Carmelo Cascone255125d2018-04-11 14:03:22 -0700510 private boolean tableHasCounter(PiTableId tableId) {
511 return pipelineModel.table(tableId).isPresent() &&
512 !pipelineModel.table(tableId).get().counters().isEmpty();
513 }
514
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200515 enum Operation {
516 APPLY, REMOVE
Frank Wang0e805082017-07-21 14:37:35 +0800517 }
Carmelo Cascone87892e22017-11-13 16:01:29 -0800518}