blob: 5a233ef40b80b1baacb25688129bdc3dd6a14193 [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 Cascone33b27bc2018-09-09 22:56:14 -070022import com.google.common.util.concurrent.Striped;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080023import org.onlab.util.SharedExecutors;
24import 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 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;
Carmelo Cascone50d195f2018-09-11 13:26:38 -070033import org.onosproject.net.pi.model.PiTableModel;
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;
36import org.onosproject.net.pi.runtime.PiCounterCellId;
Frank Wang0e805082017-07-21 14:37:35 +080037import org.onosproject.net.pi.runtime.PiTableEntry;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080038import org.onosproject.net.pi.runtime.PiTableEntryHandle;
39import org.onosproject.net.pi.service.PiFlowRuleTranslator;
40import org.onosproject.net.pi.service.PiTranslatedEntity;
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080041import org.onosproject.net.pi.service.PiTranslationException;
Frank Wang0e805082017-07-21 14:37:35 +080042import org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType;
Frank Wang0e805082017-07-21 14:37:35 +080043
44import java.util.Collection;
45import java.util.Collections;
Manjunath Vanaraj59ad6572017-12-26 11:10:57 +053046import java.util.List;
Carmelo Cascone3da671a2018-02-12 10:43:35 -080047import java.util.Map;
Carmelo Cascone26600972018-09-10 00:23:20 -070048import java.util.Objects;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080049import java.util.Optional;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020050import java.util.Set;
Carmelo Casconee5b28722018-06-22 17:28:28 +020051import java.util.concurrent.CompletableFuture;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020052import java.util.concurrent.locks.Lock;
Carmelo Casconefe99be92017-09-11 21:55:54 +020053import java.util.stream.Collectors;
Carmelo Cascone50d195f2018-09-11 13:26:38 -070054import java.util.stream.Stream;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020055
56import static com.google.common.collect.Lists.newArrayList;
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 Cascone87892e22017-11-13 16:01:29 -080060import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.DELETE;
61import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.INSERT;
62import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.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 Cascone81929aa2018-04-07 01:38:55 -070078 // If true, we ignore re-installing rules that already exist in the
79 // device mirror, i.e. same match key and action.
Carmelo Cascone3da671a2018-02-12 10:43:35 -080080 private static final String IGNORE_SAME_ENTRY_UPDATE = "tableIgnoreSameEntryUpdate";
81 private static final boolean DEFAULT_IGNORE_SAME_ENTRY_UPDATE = false;
Carmelo Cascone2308e522017-08-25 02:35:12 +020082
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080083 // If true, we avoid querying the device and return what's already known by
84 // the ONOS store.
Carmelo Cascone3da671a2018-02-12 10:43:35 -080085 private static final String READ_FROM_MIRROR = "tableReadFromMirror";
86 private static final boolean DEFAULT_READ_FROM_MIRROR = false;
Carmelo Casconefe99be92017-09-11 21:55:54 +020087
Carmelo Cascone255125d2018-04-11 14:03:22 -070088 // If true, we read counters when reading table entries (if table has
89 // counters). Otherwise, we don't.
90 private static final String SUPPORT_TABLE_COUNTERS = "supportTableCounters";
91 private static final boolean DEFAULT_SUPPORT_TABLE_COUNTERS = true;
92
steven308017632e152018-10-20 00:51:08 +080093 // If true, assumes that the device returns table entry message populated
94 // with direct counter values. If false, we issue a second P4Runtime request
95 // to read the direct counter values.
96 private static final String READ_COUNTERS_WITH_TABLE_ENTRIES = "tableReadCountersWithTableEntries";
97 private static final boolean DEFAULT_READ_COUNTERS_WITH_TABLE_ENTRIES = true;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020098
Carmelo Cascone50d195f2018-09-11 13:26:38 -070099 // For default entries, P4Runtime mandates that only MODIFY messages are
100 // allowed. If true, treats default entries as normal table entries,
101 // e.g. inserting them first.
102 private static final String TABLE_DEFAULT_AS_ENTRY = "tableDefaultAsEntry";
103 private static final boolean DEFAULT_TABLE_DEFAULT_AS_ENTRY = false;
104
Manjunath Vanaraj59ad6572017-12-26 11:10:57 +0530105 // Needed to synchronize operations over the same table entry.
Carmelo Cascone33b27bc2018-09-09 22:56:14 -0700106 private static final Striped<Lock> ENTRY_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);
121 translator = piTranslationService.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 Cascone50d195f2018-09-11 13:26:38 -0700139 // Read table entries, including default ones.
140 final Collection<PiTableEntry> deviceEntries = Stream.concat(
141 streamEntries(), streamDefaultEntries())
142 // Ignore entries from constant tables.
Carmelo Cascone33b27bc2018-09-09 22:56:14 -0700143 .filter(e -> !tableIsConstant(e.table()))
Carmelo Casconee44592f2018-09-12 02:24:47 -0700144 // Device implementation might return duplicate entries. For
145 // example if reading only default ones is not supported and
146 // non-default entries are returned, by using distinct() we are
147 // robust against that possibility.
148 .distinct()
Carmelo Cascone33b27bc2018-09-09 22:56:14 -0700149 .collect(Collectors.toList());
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200150
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700151 if (deviceEntries.isEmpty()) {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200152 return Collections.emptyList();
153 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200154
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700155 // Synchronize mirror with the device state.
156 syncMirror(deviceEntries);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200157 final Map<PiTableEntry, PiCounterCellData> counterCellMap =
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700158 readEntryCounters(deviceEntries);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200159 // Forge flow entries with counter values.
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700160 for (PiTableEntry entry : deviceEntries) {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200161 final FlowEntry flowEntry = forgeFlowEntry(
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700162 entry, counterCellMap.get(entry));
Carmelo Casconee5b28722018-06-22 17:28:28 +0200163 if (flowEntry == null) {
164 // Entry is on device but unknown to translation service or
165 // device mirror. Inconsistent. Mark for removal.
166 // TODO: make this behaviour configurable
167 // In some cases it's fine for the device to have rules
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700168 // that were not installed by us, e.g. original default entry.
169 if (!isOriginalDefaultEntry(entry)) {
170 inconsistentEntries.add(entry);
171 }
Carmelo Casconee5b28722018-06-22 17:28:28 +0200172 } else {
173 result.add(flowEntry);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200174 }
175 }
176
177 if (inconsistentEntries.size() > 0) {
Carmelo Cascone33b27bc2018-09-09 22:56:14 -0700178 // Trigger clean up of inconsistent entries.
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800179 SharedExecutors.getSingleThreadExecutor().execute(
180 () -> cleanUpInconsistentEntries(inconsistentEntries));
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200181 }
182
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800183 return result.build();
Frank Wang0e805082017-07-21 14:37:35 +0800184 }
185
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700186 private Stream<PiTableEntry> streamEntries() {
187 return getFutureWithDeadline(
188 client.dumpAllTables(pipeconf), "dumping all tables",
189 Collections.emptyList())
190 .stream();
191 }
192
193 private Stream<PiTableEntry> streamDefaultEntries() {
194 // Ignore tables with constant default action.
195 final Set<PiTableId> defaultTables = pipelineModel.tables()
196 .stream()
197 .filter(table -> !table.constDefaultAction().isPresent())
198 .map(PiTableModel::id)
199 .collect(Collectors.toSet());
200 return defaultTables.isEmpty() ? Stream.empty()
201 : getFutureWithDeadline(
202 client.dumpTables(defaultTables, true, pipeconf),
203 "dumping default table entries",
204 Collections.emptyList())
205 .stream();
206 }
207
208 private void syncMirror(Collection<PiTableEntry> entries) {
209 Map<PiTableEntryHandle, PiTableEntry> handleMap = Maps.newHashMap();
210 entries.forEach(e -> handleMap.put(PiTableEntryHandle.of(deviceId, e), e));
211 tableMirror.sync(deviceId, handleMap);
212 }
213
Frank Wang0e805082017-07-21 14:37:35 +0800214 @Override
215 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200216 return processFlowRules(rules, APPLY);
Frank Wang0e805082017-07-21 14:37:35 +0800217 }
218
219 @Override
220 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200221 return processFlowRules(rules, REMOVE);
Frank Wang0e805082017-07-21 14:37:35 +0800222 }
223
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800224 private FlowEntry forgeFlowEntry(PiTableEntry entry,
225 PiCounterCellData cellData) {
226 final PiTableEntryHandle handle = PiTableEntryHandle
227 .of(deviceId, entry);
228 final Optional<PiTranslatedEntity<FlowRule, PiTableEntry>>
229 translatedEntity = translator.lookup(handle);
230 final TimedEntry<PiTableEntry> timedEntry = tableMirror.get(handle);
231
232 if (!translatedEntity.isPresent()) {
Carmelo Cascone26600972018-09-10 00:23:20 -0700233 log.warn("Table entry handle not found in translation store: {}", handle);
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800234 return null;
235 }
Carmelo Casconee44592f2018-09-12 02:24:47 -0700236 if (!translatedEntity.get().translated().equals(entry)) {
237 log.warn("Table entry obtained from device {} is different from " +
238 "one in in translation store: device={}, store={}",
239 deviceId, entry, translatedEntity.get().translated());
240 return null;
241 }
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800242 if (timedEntry == null) {
Carmelo Cascone26600972018-09-10 00:23:20 -0700243 log.warn("Table entry handle not found in device mirror: {}", handle);
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800244 return null;
245 }
246
247 if (cellData != null) {
248 return new DefaultFlowEntry(translatedEntity.get().original(),
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700249 ADDED, timedEntry.lifeSec(), cellData.packets(),
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800250 cellData.bytes());
251 } else {
252 return new DefaultFlowEntry(translatedEntity.get().original(),
253 ADDED, timedEntry.lifeSec(), 0, 0);
254 }
255 }
256
257 private Collection<FlowEntry> getFlowEntriesFromMirror() {
258 return tableMirror.getAll(deviceId).stream()
259 .map(timedEntry -> forgeFlowEntry(
260 timedEntry.entry(), null))
Carmelo Cascone26600972018-09-10 00:23:20 -0700261 .filter(Objects::nonNull)
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800262 .collect(Collectors.toList());
263 }
264
265 private void cleanUpInconsistentEntries(Collection<PiTableEntry> piEntries) {
Carmelo Cascone26600972018-09-10 00:23:20 -0700266 log.warn("Found {} inconsistent table entries on {}, removing them...",
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800267 piEntries.size(), deviceId);
268 piEntries.forEach(entry -> {
269 log.debug(entry.toString());
Carmelo Cascone26600972018-09-10 00:23:20 -0700270 final PiTableEntryHandle handle = PiTableEntryHandle.of(deviceId, entry);
271 ENTRY_LOCKS.get(handle).lock();
272 try {
273 applyEntry(handle, entry, null, REMOVE);
274 } finally {
275 ENTRY_LOCKS.get(handle).unlock();
276 }
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800277 });
278 }
279
280 private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules,
281 Operation driverOperation) {
Frank Wang0e805082017-07-21 14:37:35 +0800282
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700283 if (!setupBehaviour() || rules.isEmpty()) {
Frank Wang0e805082017-07-21 14:37:35 +0800284 return Collections.emptyList();
285 }
286
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800287 final ImmutableList.Builder<FlowRule> result = ImmutableList.builder();
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200288
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800289 // TODO: send writes in bulk (e.g. all entries to insert, modify or delete).
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200290 // Instead of calling the client for each one of them.
291
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800292 for (FlowRule ruleToApply : rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200293
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800294 final PiTableEntry piEntryToApply;
Frank Wang0e805082017-07-21 14:37:35 +0800295 try {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800296 piEntryToApply = translator.translate(ruleToApply, pipeconf);
Carmelo Cascone326ad2d2017-11-28 18:09:13 -0800297 } catch (PiTranslationException e) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800298 log.warn("Unable to translate flow rule for pipeconf '{}': {} - {}",
299 pipeconf.id(), e.getMessage(), ruleToApply);
300 // Next rule.
301 continue;
Frank Wang0e805082017-07-21 14:37:35 +0800302 }
Frank Wang0e805082017-07-21 14:37:35 +0800303
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800304 final PiTableEntryHandle handle = PiTableEntryHandle
305 .of(deviceId, piEntryToApply);
Frank Wang0e805082017-07-21 14:37:35 +0800306
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800307 // Serialize operations over the same match key/table/device ID.
Carmelo Cascone33b27bc2018-09-09 22:56:14 -0700308 ENTRY_LOCKS.get(handle).lock();
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200309 try {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800310 if (applyEntry(handle, piEntryToApply,
311 ruleToApply, driverOperation)) {
312 result.add(ruleToApply);
Frank Wang0e805082017-07-21 14:37:35 +0800313 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200314 } finally {
Carmelo Cascone33b27bc2018-09-09 22:56:14 -0700315 ENTRY_LOCKS.get(handle).unlock();
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200316 }
317 }
318
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800319 return result.build();
320 }
321
322 /**
323 * Applies the given entry to the device, and returns true if the operation
324 * was successful, false otherwise.
325 */
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700326 private boolean applyEntry(final PiTableEntryHandle handle,
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800327 PiTableEntry piEntryToApply,
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700328 final FlowRule ruleToApply,
329 final Operation driverOperation) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800330 // Depending on the driver operation, and if a matching rule exists on
331 // the device, decide which P4 Runtime write operation to perform for
332 // this entry.
333 final TimedEntry<PiTableEntry> piEntryOnDevice = tableMirror.get(handle);
334 final WriteOperationType p4Operation;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700335 final WriteOperationType storeOperation;
336
337 final boolean defaultAsEntry = driverBoolProperty(
338 TABLE_DEFAULT_AS_ENTRY, DEFAULT_TABLE_DEFAULT_AS_ENTRY);
339 final boolean ignoreSameEntryUpdate = driverBoolProperty(
340 IGNORE_SAME_ENTRY_UPDATE, DEFAULT_IGNORE_SAME_ENTRY_UPDATE);
341 final boolean deleteBeforeUpdate = driverBoolProperty(
342 DELETE_BEFORE_UPDATE, DEFAULT_DELETE_BEFORE_UPDATE);
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800343 if (driverOperation == APPLY) {
344 if (piEntryOnDevice == null) {
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700345 // Entry is first-timer, INSERT or MODIFY if default action.
346 p4Operation = !piEntryToApply.isDefaultAction() || defaultAsEntry
347 ? INSERT : MODIFY;
348 storeOperation = p4Operation;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800349 } else {
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700350 if (ignoreSameEntryUpdate &&
351 piEntryToApply.action().equals(piEntryOnDevice.entry().action())) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800352 log.debug("Ignoring re-apply of existing entry: {}", piEntryToApply);
353 p4Operation = null;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700354 } else if (deleteBeforeUpdate && !piEntryToApply.isDefaultAction()) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800355 // Some devices return error when updating existing
356 // entries. If requested, remove entry before
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700357 // re-inserting the modified one, except the default action
358 // entry, that cannot be removed.
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800359 applyEntry(handle, piEntryOnDevice.entry(), null, REMOVE);
360 p4Operation = INSERT;
361 } else {
362 p4Operation = MODIFY;
363 }
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700364 storeOperation = p4Operation;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800365 }
366 } else {
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700367 if (piEntryToApply.isDefaultAction()) {
368 // Cannot remove default action. Instead we should use the
369 // original defined by the interpreter (if any).
370 piEntryToApply = getOriginalDefaultEntry(piEntryToApply.table());
371 if (piEntryToApply == null) {
372 return false;
373 }
374 p4Operation = MODIFY;
375 } else {
376 p4Operation = DELETE;
377 }
378 // Still want to delete the default entry from the mirror and
379 // translation store.
380 storeOperation = DELETE;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800381 }
382
383 if (p4Operation != null) {
384 if (writeEntry(piEntryToApply, p4Operation)) {
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700385 updateStores(handle, piEntryToApply, ruleToApply, storeOperation);
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800386 return true;
387 } else {
388 return false;
389 }
390 } else {
391 // If no operation, let's pretend we applied the rule to the device.
392 return true;
393 }
394 }
395
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700396 private PiTableEntry getOriginalDefaultEntry(PiTableId tableId) {
397 final PiPipelineInterpreter interpreter = getInterpreter();
398 if (interpreter == null) {
399 log.warn("Missing interpreter for {}, cannot get default action",
400 deviceId);
401 return null;
402 }
403 if (!interpreter.getOriginalDefaultAction(tableId).isPresent()) {
404 log.warn("Interpreter of {} doesn't define a default action for " +
405 "table {}, cannot produce default action entry",
406 deviceId, tableId);
407 return null;
408 }
409 return PiTableEntry.builder()
410 .forTable(tableId)
411 .withAction(interpreter.getOriginalDefaultAction(tableId).get())
412 .build();
413 }
414
415 private boolean isOriginalDefaultEntry(PiTableEntry entry) {
416 if (!entry.isDefaultAction()) {
417 return false;
418 }
419 final PiTableEntry originalDefaultEntry = getOriginalDefaultEntry(entry.table());
420 return originalDefaultEntry != null &&
421 originalDefaultEntry.action().equals(entry.action());
422 }
423
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800424 /**
425 * Performs a write operation on the device.
426 */
427 private boolean writeEntry(PiTableEntry entry,
428 WriteOperationType p4Operation) {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200429 final CompletableFuture<Boolean> future = client.writeTableEntries(
430 newArrayList(entry), p4Operation, pipeconf);
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700431 // If false, errors logged by internal calls.
432 return getFutureWithDeadline(
433 future, "performing table " + p4Operation.name(), false);
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800434 }
435
436 private void updateStores(PiTableEntryHandle handle,
437 PiTableEntry entry,
438 FlowRule rule,
439 WriteOperationType p4Operation) {
440 switch (p4Operation) {
441 case INSERT:
442 case MODIFY:
443 tableMirror.put(handle, entry);
444 translator.learn(handle, new PiTranslatedEntity<>(rule, entry, handle));
445 break;
446 case DELETE:
447 tableMirror.remove(handle);
448 translator.forget(handle);
449 break;
450 default:
451 throw new IllegalArgumentException(
452 "Unknown operation " + p4Operation.name());
453 }
454 }
455
456 private Map<PiTableEntry, PiCounterCellData> readEntryCounters(
Carmelo Cascone255125d2018-04-11 14:03:22 -0700457 Collection<PiTableEntry> tableEntries) {
458 if (!driverBoolProperty(SUPPORT_TABLE_COUNTERS,
Carmelo Casconee5b28722018-06-22 17:28:28 +0200459 DEFAULT_SUPPORT_TABLE_COUNTERS)
460 || tableEntries.isEmpty()) {
Carmelo Cascone255125d2018-04-11 14:03:22 -0700461 return Collections.emptyMap();
462 }
463
steven308017632e152018-10-20 00:51:08 +0800464 if (driverBoolProperty(READ_COUNTERS_WITH_TABLE_ENTRIES,
465 DEFAULT_READ_COUNTERS_WITH_TABLE_ENTRIES)) {
466 return tableEntries.stream().collect(Collectors.toMap(c -> c, PiTableEntry::counter));
Carmelo Casconee5b28722018-06-22 17:28:28 +0200467 } else {
steven308017632e152018-10-20 00:51:08 +0800468 Collection<PiCounterCell> cells;
Carmelo Casconee5b28722018-06-22 17:28:28 +0200469 Set<PiCounterCellId> cellIds = tableEntries.stream()
Carmelo Casconee44592f2018-09-12 02:24:47 -0700470 // Ignore counter for default entry.
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700471 .filter(e -> !e.isDefaultAction())
Carmelo Casconee5b28722018-06-22 17:28:28 +0200472 .filter(e -> tableHasCounter(e.table()))
473 .map(PiCounterCellId::ofDirect)
474 .collect(Collectors.toSet());
steven308017632e152018-10-20 00:51:08 +0800475 cells = getFutureWithDeadline(client.readCounterCells(cellIds, pipeconf),
Carmelo Casconee5b28722018-06-22 17:28:28 +0200476 "reading table counters", Collections.emptyList());
steven308017632e152018-10-20 00:51:08 +0800477 return cells.stream()
478 .collect(Collectors.toMap(c -> c.cellId().tableEntry(), PiCounterCell::data));
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800479 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200480 }
481
Carmelo Cascone255125d2018-04-11 14:03:22 -0700482 private boolean tableHasCounter(PiTableId tableId) {
483 return pipelineModel.table(tableId).isPresent() &&
484 !pipelineModel.table(tableId).get().counters().isEmpty();
485 }
486
Carmelo Cascone33b27bc2018-09-09 22:56:14 -0700487 private boolean tableIsConstant(PiTableId tableId) {
488 return pipelineModel.table(tableId).isPresent() &&
489 pipelineModel.table(tableId).get().isConstantTable();
490 }
491
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200492 enum Operation {
493 APPLY, REMOVE
Frank Wang0e805082017-07-21 14:37:35 +0800494 }
Carmelo Cascone87892e22017-11-13 16:01:29 -0800495}