blob: f905c4c046ef273e2d6c0c85b35ec4c378e24ba3 [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 Cascone6a0b5a32017-11-20 23:08:32 -080022import org.onlab.util.SharedExecutors;
23import org.onosproject.drivers.p4runtime.mirror.P4RuntimeTableMirror;
24import org.onosproject.drivers.p4runtime.mirror.TimedEntry;
Frank Wang0e805082017-07-21 14:37:35 +080025import org.onosproject.net.flow.DefaultFlowEntry;
26import org.onosproject.net.flow.FlowEntry;
27import org.onosproject.net.flow.FlowRule;
28import org.onosproject.net.flow.FlowRuleProgrammable;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080029import org.onosproject.net.pi.model.PiCounterType;
Carmelo Cascone50d195f2018-09-11 13:26:38 -070030import org.onosproject.net.pi.model.PiPipelineInterpreter;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020031import org.onosproject.net.pi.model.PiPipelineModel;
Carmelo Cascone87892e22017-11-13 16:01:29 -080032import org.onosproject.net.pi.model.PiTableId;
steven308017632e152018-10-20 00:51:08 +080033import org.onosproject.net.pi.runtime.PiCounterCell;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020034import org.onosproject.net.pi.runtime.PiCounterCellData;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080035import org.onosproject.net.pi.runtime.PiCounterCellHandle;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020036import org.onosproject.net.pi.runtime.PiCounterCellId;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080037import org.onosproject.net.pi.runtime.PiEntityType;
38import org.onosproject.net.pi.runtime.PiHandle;
Frank Wang0e805082017-07-21 14:37:35 +080039import org.onosproject.net.pi.runtime.PiTableEntry;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080040import org.onosproject.net.pi.runtime.PiTableEntryHandle;
41import org.onosproject.net.pi.service.PiFlowRuleTranslator;
42import org.onosproject.net.pi.service.PiTranslatedEntity;
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080043import org.onosproject.net.pi.service.PiTranslationException;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080044import org.onosproject.p4runtime.api.P4RuntimeReadClient;
45import org.onosproject.p4runtime.api.P4RuntimeWriteClient;
46import org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType;
Frank Wang0e805082017-07-21 14:37:35 +080047
48import java.util.Collection;
49import java.util.Collections;
Manjunath Vanaraj59ad6572017-12-26 11:10:57 +053050import java.util.List;
Carmelo Cascone3da671a2018-02-12 10:43:35 -080051import java.util.Map;
Carmelo Cascone26600972018-09-10 00:23:20 -070052import java.util.Objects;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080053import java.util.Optional;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020054import java.util.Set;
Carmelo Casconefe99be92017-09-11 21:55:54 +020055import java.util.stream.Collectors;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020056
Andrea Campanella0288c872017-08-07 18:32:51 +020057import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.APPLY;
58import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.REMOVE;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020059import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080060import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType.DELETE;
61import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType.INSERT;
62import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType.MODIFY;
Frank Wang0e805082017-07-21 14:37:35 +080063
64/**
Carmelo Casconee3a7c742017-09-01 01:25:52 +020065 * Implementation of the flow rule programmable behaviour for P4Runtime.
Frank Wang0e805082017-07-21 14:37:35 +080066 */
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080067public class P4RuntimeFlowRuleProgrammable
68 extends AbstractP4RuntimeHandlerBehaviour
69 implements FlowRuleProgrammable {
Frank Wang0e805082017-07-21 14:37:35 +080070
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080071 // When updating an existing rule, if true, we issue a DELETE operation
72 // before inserting the new one, otherwise we issue a MODIFY operation. This
73 // is useful fore devices that do not support MODIFY operations for table
74 // entries.
Carmelo Cascone3da671a2018-02-12 10:43:35 -080075 private static final String DELETE_BEFORE_UPDATE = "tableDeleteBeforeUpdate";
76 private static final boolean DEFAULT_DELETE_BEFORE_UPDATE = false;
Carmelo Cascone2308e522017-08-25 02:35:12 +020077
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080078 // If true, we avoid querying the device and return what's already known by
79 // the ONOS store.
Carmelo Cascone3da671a2018-02-12 10:43:35 -080080 private static final String READ_FROM_MIRROR = "tableReadFromMirror";
81 private static final boolean DEFAULT_READ_FROM_MIRROR = false;
Carmelo Casconefe99be92017-09-11 21:55:54 +020082
Carmelo Cascone255125d2018-04-11 14:03:22 -070083 // If true, we read counters when reading table entries (if table has
84 // counters). Otherwise, we don't.
85 private static final String SUPPORT_TABLE_COUNTERS = "supportTableCounters";
86 private static final boolean DEFAULT_SUPPORT_TABLE_COUNTERS = true;
87
steven308017632e152018-10-20 00:51:08 +080088 // If true, assumes that the device returns table entry message populated
89 // with direct counter values. If false, we issue a second P4Runtime request
90 // to read the direct counter values.
91 private static final String READ_COUNTERS_WITH_TABLE_ENTRIES = "tableReadCountersWithTableEntries";
92 private static final boolean DEFAULT_READ_COUNTERS_WITH_TABLE_ENTRIES = true;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020093
Carmelo Cascone50d195f2018-09-11 13:26:38 -070094 // For default entries, P4Runtime mandates that only MODIFY messages are
95 // allowed. If true, treats default entries as normal table entries,
96 // e.g. inserting them first.
97 private static final String TABLE_DEFAULT_AS_ENTRY = "tableDefaultAsEntry";
98 private static final boolean DEFAULT_TABLE_DEFAULT_AS_ENTRY = false;
99
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200100 private PiPipelineModel pipelineModel;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800101 private P4RuntimeTableMirror tableMirror;
102 private PiFlowRuleTranslator translator;
Frank Wang0e805082017-07-21 14:37:35 +0800103
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200104 @Override
105 protected boolean setupBehaviour() {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200106
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200107 if (!super.setupBehaviour()) {
Frank Wang0e805082017-07-21 14:37:35 +0800108 return false;
109 }
110
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200111 pipelineModel = pipeconf.pipelineModel();
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800112 tableMirror = handler().get(P4RuntimeTableMirror.class);
Yi Tsengd7716482018-10-31 15:34:30 -0700113 translator = translationService.flowRuleTranslator();
Frank Wang0e805082017-07-21 14:37:35 +0800114 return true;
115 }
116
117 @Override
118 public Collection<FlowEntry> getFlowEntries() {
119
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200120 if (!setupBehaviour()) {
Frank Wang0e805082017-07-21 14:37:35 +0800121 return Collections.emptyList();
122 }
123
Carmelo Cascone3da671a2018-02-12 10:43:35 -0800124 if (driverBoolProperty(READ_FROM_MIRROR, DEFAULT_READ_FROM_MIRROR)) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800125 return getFlowEntriesFromMirror();
Carmelo Casconefe99be92017-09-11 21:55:54 +0200126 }
127
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800128 final ImmutableList.Builder<FlowEntry> result = ImmutableList.builder();
129 final List<PiTableEntry> inconsistentEntries = Lists.newArrayList();
Frank Wang0e805082017-07-21 14:37:35 +0800130
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800131 // Read table entries from device.
132 final Collection<PiTableEntry> deviceEntries = getAllTableEntriesFromDevice();
133 if (deviceEntries == null) {
134 // Potential error at the client level.
Carmelo Casconee5b28722018-06-22 17:28:28 +0200135 return Collections.emptyList();
136 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200137
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700138 // Synchronize mirror with the device state.
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800139 tableMirror.sync(deviceId, deviceEntries);
140
141 if (deviceEntries.isEmpty()) {
142 // Nothing to do.
143 return Collections.emptyList();
144 }
145
Carmelo Casconee5b28722018-06-22 17:28:28 +0200146 final Map<PiTableEntry, PiCounterCellData> counterCellMap =
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700147 readEntryCounters(deviceEntries);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200148 // Forge flow entries with counter values.
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700149 for (PiTableEntry entry : deviceEntries) {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200150 final FlowEntry flowEntry = forgeFlowEntry(
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700151 entry, counterCellMap.get(entry));
Carmelo Casconee5b28722018-06-22 17:28:28 +0200152 if (flowEntry == null) {
153 // Entry is on device but unknown to translation service or
154 // device mirror. Inconsistent. Mark for removal.
155 // TODO: make this behaviour configurable
156 // In some cases it's fine for the device to have rules
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700157 // that were not installed by us, e.g. original default entry.
158 if (!isOriginalDefaultEntry(entry)) {
159 inconsistentEntries.add(entry);
160 }
Carmelo Casconee5b28722018-06-22 17:28:28 +0200161 } else {
162 result.add(flowEntry);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200163 }
164 }
165
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800166 if (!inconsistentEntries.isEmpty()) {
Carmelo Cascone33b27bc2018-09-09 22:56:14 -0700167 // Trigger clean up of inconsistent entries.
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800168 SharedExecutors.getSingleThreadExecutor().execute(
169 () -> cleanUpInconsistentEntries(inconsistentEntries));
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200170 }
171
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800172 return result.build();
Frank Wang0e805082017-07-21 14:37:35 +0800173 }
174
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800175 private Collection<PiTableEntry> getAllTableEntriesFromDevice() {
176 final P4RuntimeReadClient.ReadRequest request = client.read(pipeconf);
177 // Read entries from all non-constant tables, including default ones.
178 pipelineModel.tables().stream()
179 .filter(t -> !t.isConstantTable())
180 .forEach(t -> {
181 request.tableEntries(t.id());
182 if (!t.constDefaultAction().isPresent()) {
183 request.defaultTableEntry(t.id());
184 }
185 });
186 final P4RuntimeReadClient.ReadResponse response = request.submitSync();
187 if (!response.isSuccess()) {
188 return null;
189 }
190 return response.all(PiTableEntry.class).stream()
191 // Device implementation might return duplicate entries. For
192 // example if reading only default ones is not supported and
193 // non-default entries are returned, by using distinct() we
194 // are robust against that possibility.
195 .distinct()
196 .collect(Collectors.toList());
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700197 }
198
Frank Wang0e805082017-07-21 14:37:35 +0800199 @Override
200 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200201 return processFlowRules(rules, APPLY);
Frank Wang0e805082017-07-21 14:37:35 +0800202 }
203
204 @Override
205 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200206 return processFlowRules(rules, REMOVE);
Frank Wang0e805082017-07-21 14:37:35 +0800207 }
208
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800209 private FlowEntry forgeFlowEntry(PiTableEntry entry,
210 PiCounterCellData cellData) {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800211 final PiTableEntryHandle handle = entry.handle(deviceId);
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800212 final Optional<PiTranslatedEntity<FlowRule, PiTableEntry>>
213 translatedEntity = translator.lookup(handle);
214 final TimedEntry<PiTableEntry> timedEntry = tableMirror.get(handle);
215
216 if (!translatedEntity.isPresent()) {
Carmelo Cascone26600972018-09-10 00:23:20 -0700217 log.warn("Table entry handle not found in translation store: {}", handle);
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800218 return null;
219 }
Carmelo Casconee44592f2018-09-12 02:24:47 -0700220 if (!translatedEntity.get().translated().equals(entry)) {
221 log.warn("Table entry obtained from device {} is different from " +
222 "one in in translation store: device={}, store={}",
223 deviceId, entry, translatedEntity.get().translated());
224 return null;
225 }
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800226 if (timedEntry == null) {
Carmelo Cascone26600972018-09-10 00:23:20 -0700227 log.warn("Table entry handle not found in device mirror: {}", handle);
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800228 return null;
229 }
230
231 if (cellData != null) {
232 return new DefaultFlowEntry(translatedEntity.get().original(),
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700233 ADDED, timedEntry.lifeSec(), cellData.packets(),
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800234 cellData.bytes());
235 } else {
236 return new DefaultFlowEntry(translatedEntity.get().original(),
237 ADDED, timedEntry.lifeSec(), 0, 0);
238 }
239 }
240
241 private Collection<FlowEntry> getFlowEntriesFromMirror() {
242 return tableMirror.getAll(deviceId).stream()
243 .map(timedEntry -> forgeFlowEntry(
244 timedEntry.entry(), null))
Carmelo Cascone26600972018-09-10 00:23:20 -0700245 .filter(Objects::nonNull)
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800246 .collect(Collectors.toList());
247 }
248
249 private void cleanUpInconsistentEntries(Collection<PiTableEntry> piEntries) {
Carmelo Cascone26600972018-09-10 00:23:20 -0700250 log.warn("Found {} inconsistent table entries on {}, removing them...",
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800251 piEntries.size(), deviceId);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800252 // Remove entries and update mirror.
253 tableMirror.replayWriteResponse(
254 client.write(pipeconf).entities(piEntries, DELETE).submitSync());
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800255 }
256
257 private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules,
258 Operation driverOperation) {
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700259 if (!setupBehaviour() || rules.isEmpty()) {
Frank Wang0e805082017-07-21 14:37:35 +0800260 return Collections.emptyList();
261 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800262 // Created batched write request.
263 final P4RuntimeWriteClient.WriteRequest request = client.write(pipeconf);
264 // For each rule, translate to PI and append to write request.
265 final Map<PiHandle, FlowRule> handleToRuleMap = Maps.newHashMap();
266 final List<FlowRule> skippedRules = Lists.newArrayList();
267 for (FlowRule rule : rules) {
268 final PiTableEntry entry;
Frank Wang0e805082017-07-21 14:37:35 +0800269 try {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800270 entry = translator.translate(rule, pipeconf);
Carmelo Cascone326ad2d2017-11-28 18:09:13 -0800271 } catch (PiTranslationException e) {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800272 log.warn("Unable to translate flow rule for pipeconf '{}': {} [{}]",
273 pipeconf.id(), e.getMessage(), rule);
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800274 // Next rule.
275 continue;
Frank Wang0e805082017-07-21 14:37:35 +0800276 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800277 final PiTableEntryHandle handle = entry.handle(deviceId);
278 handleToRuleMap.put(handle, rule);
279 // Append entry to batched write request (returns false), or skip (true)
280 if (appendEntryToWriteRequestOrSkip(
281 request, handle, entry, driverOperation)) {
282 skippedRules.add(rule);
283 updateTranslationStore(
284 driverOperation, handle, rule, entry);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200285 }
286 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800287 // Submit request to server.
288 final P4RuntimeWriteClient.WriteResponse response = request.submitSync();
289 // Update mirror.
290 tableMirror.replayWriteResponse(response);
291 // Derive successfully applied flow rule from response.
292 final List<FlowRule> appliedRules = getAppliedFlowRulesAndUpdateTranslator(
293 response, handleToRuleMap, driverOperation);
294 // Return skipped and applied rules.
295 return ImmutableList.<FlowRule>builder()
296 .addAll(skippedRules).addAll(appliedRules).build();
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800297 }
298
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800299 private List<FlowRule> getAppliedFlowRulesAndUpdateTranslator(
300 P4RuntimeWriteClient.WriteResponse response,
301 Map<PiHandle, FlowRule> handleToFlowRuleMap,
302 Operation driverOperation) {
303 // Returns a list of flow rules that were successfully written on the
304 // server according to the given write response and operation.
305 return response.success().stream()
306 .filter(r -> r.entityType().equals(PiEntityType.TABLE_ENTRY))
307 .map(r -> {
308 final PiHandle handle = r.handle();
309 final FlowRule rule = handleToFlowRuleMap.get(handle);
310 if (rule == null) {
311 log.error("Server returned unrecognized table entry " +
312 "handle in write response: {}", handle);
313 return null;
314 }
315 // Filter intermediate responses (e.g. P4Runtime DELETE
316 // during FlowRule APPLY because we are performing
317 // delete-before-update)
318 if (isUpdateTypeRelevant(r.updateType(), driverOperation)) {
319 updateTranslationStore(
320 driverOperation, (PiTableEntryHandle) handle,
321 rule, (PiTableEntry) r.entity());
322 return rule;
323 }
324 return null;
325 })
326 .filter(Objects::nonNull)
327 .collect(Collectors.toList());
328 }
329
330 private boolean isUpdateTypeRelevant(UpdateType p4UpdateType, Operation driverOperation) {
331 switch (p4UpdateType) {
332 case INSERT:
333 case MODIFY:
334 if (!driverOperation.equals(APPLY)) {
335 return false;
336 }
337 break;
338 case DELETE:
339 if (!driverOperation.equals(REMOVE)) {
340 return false;
341 }
342 break;
343 default:
344 log.error("Unknown update type {}", p4UpdateType);
345 return false;
346 }
347 return true;
348 }
349
350 private void updateTranslationStore(
351 Operation operation, PiTableEntryHandle handle,
352 FlowRule rule, PiTableEntry entry) {
353 if (operation.equals(APPLY)) {
354 translator.learn(handle, new PiTranslatedEntity<>(
355 rule, entry, handle));
356 } else {
357 translator.forget(handle);
358 }
359 }
360
361 private boolean appendEntryToWriteRequestOrSkip(
362 final P4RuntimeWriteClient.WriteRequest writeRequest,
363 final PiTableEntryHandle handle,
364 PiTableEntry piEntryToApply,
365 final Operation driverOperation) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800366 // Depending on the driver operation, and if a matching rule exists on
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800367 // the device/mirror, decide which P4Runtime update operation to perform
368 // for this entry. In some cases, the entry is skipped from the write
369 // request but we want to return the corresponding flow rule as
370 // successfully written. In this case, we return true.
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800371 final TimedEntry<PiTableEntry> piEntryOnDevice = tableMirror.get(handle);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800372 final UpdateType updateType;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700373
374 final boolean defaultAsEntry = driverBoolProperty(
375 TABLE_DEFAULT_AS_ENTRY, DEFAULT_TABLE_DEFAULT_AS_ENTRY);
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700376 final boolean deleteBeforeUpdate = driverBoolProperty(
377 DELETE_BEFORE_UPDATE, DEFAULT_DELETE_BEFORE_UPDATE);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800378
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800379 if (driverOperation == APPLY) {
380 if (piEntryOnDevice == null) {
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700381 // Entry is first-timer, INSERT or MODIFY if default action.
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800382 updateType = !piEntryToApply.isDefaultAction() || defaultAsEntry
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700383 ? INSERT : MODIFY;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800384 } else {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800385 if (piEntryToApply.action().equals(piEntryOnDevice.entry().action())) {
386 // FIXME: should we check for other attributes of the table
387 // entry? For example can we modify the priority?
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800388 log.debug("Ignoring re-apply of existing entry: {}", piEntryToApply);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800389 return true;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700390 } else if (deleteBeforeUpdate && !piEntryToApply.isDefaultAction()) {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800391 // Some devices return error when updating existing entries.
392 // If requested, remove entry before re-inserting the
393 // modified one, except the default action entry, that
394 // cannot be removed.
395 writeRequest.delete(handle);
396 updateType = INSERT;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800397 } else {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800398 updateType = MODIFY;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800399 }
400 }
401 } else {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800402 // REMOVE.
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700403 if (piEntryToApply.isDefaultAction()) {
404 // Cannot remove default action. Instead we should use the
405 // original defined by the interpreter (if any).
406 piEntryToApply = getOriginalDefaultEntry(piEntryToApply.table());
407 if (piEntryToApply == null) {
408 return false;
409 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800410 updateType = MODIFY;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700411 } else {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800412 updateType = DELETE;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700413 }
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800414 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800415 writeRequest.entity(piEntryToApply, updateType);
416 return false;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800417 }
418
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700419 private PiTableEntry getOriginalDefaultEntry(PiTableId tableId) {
420 final PiPipelineInterpreter interpreter = getInterpreter();
421 if (interpreter == null) {
422 log.warn("Missing interpreter for {}, cannot get default action",
423 deviceId);
424 return null;
425 }
426 if (!interpreter.getOriginalDefaultAction(tableId).isPresent()) {
427 log.warn("Interpreter of {} doesn't define a default action for " +
428 "table {}, cannot produce default action entry",
429 deviceId, tableId);
430 return null;
431 }
432 return PiTableEntry.builder()
433 .forTable(tableId)
434 .withAction(interpreter.getOriginalDefaultAction(tableId).get())
435 .build();
436 }
437
438 private boolean isOriginalDefaultEntry(PiTableEntry entry) {
439 if (!entry.isDefaultAction()) {
440 return false;
441 }
442 final PiTableEntry originalDefaultEntry = getOriginalDefaultEntry(entry.table());
443 return originalDefaultEntry != null &&
444 originalDefaultEntry.action().equals(entry.action());
445 }
446
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800447 private Map<PiTableEntry, PiCounterCellData> readEntryCounters(
Carmelo Cascone255125d2018-04-11 14:03:22 -0700448 Collection<PiTableEntry> tableEntries) {
449 if (!driverBoolProperty(SUPPORT_TABLE_COUNTERS,
Carmelo Casconee5b28722018-06-22 17:28:28 +0200450 DEFAULT_SUPPORT_TABLE_COUNTERS)
451 || tableEntries.isEmpty()) {
Carmelo Cascone255125d2018-04-11 14:03:22 -0700452 return Collections.emptyMap();
453 }
454
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800455 final Map<PiTableEntry, PiCounterCellData> cellDataMap = Maps.newHashMap();
456
457 // We expect the server to return table entries with counter data (if
458 // the table supports counter). Here we extract such counter data and we
459 // determine if there are missing counter cells (if, for example, the
460 // serves does not support returning counter data with table entries)
461 final Set<PiHandle> missingCellHandles = tableEntries.stream()
462 .map(t -> {
463 if (t.counter() != null) {
464 // Counter data found in table entry.
465 cellDataMap.put(t, t.counter());
466 return null;
467 } else {
468 return t;
469 }
470 })
471 .filter(Objects::nonNull)
472 // Ignore for default entries and for tables without counters.
473 .filter(e -> !e.isDefaultAction())
474 .filter(e -> tableHasCounter(e.table()))
475 .map(PiCounterCellId::ofDirect)
476 .map(id -> PiCounterCellHandle.of(deviceId, id))
477 .collect(Collectors.toSet());
478 // We might be sending a large read request (for thousands or more
479 // of counter cell handles). We request the driver to vet this
480 // operation via driver property.
481 if (!missingCellHandles.isEmpty()
482 && !driverBoolProperty(READ_COUNTERS_WITH_TABLE_ENTRIES,
483 DEFAULT_READ_COUNTERS_WITH_TABLE_ENTRIES)) {
484 client.read(pipeconf)
485 .handles(missingCellHandles)
486 .submitSync()
487 .all(PiCounterCell.class).stream()
488 .filter(c -> c.cellId().counterType().equals(PiCounterType.DIRECT))
489 .forEach(c -> cellDataMap.put(c.cellId().tableEntry(), c.data()));
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800490 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800491
492 return cellDataMap;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200493 }
494
Carmelo Cascone255125d2018-04-11 14:03:22 -0700495 private boolean tableHasCounter(PiTableId tableId) {
496 return pipelineModel.table(tableId).isPresent() &&
497 !pipelineModel.table(tableId).get().counters().isEmpty();
498 }
499
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200500 enum Operation {
501 APPLY, REMOVE
Frank Wang0e805082017-07-21 14:37:35 +0800502 }
Carmelo Cascone87892e22017-11-13 16:01:29 -0800503}