blob: 63c83b5cdcbe21b3d184cd9f21047d97f4d1fbb8 [file] [log] [blame]
Frank Wang0e805082017-07-21 14:37:35 +08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Frank Wang0e805082017-07-21 14:37:35 +08003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Andrea Campanella0288c872017-08-07 18:32:51 +020017package org.onosproject.drivers.p4runtime;
Frank Wang0e805082017-07-21 14:37:35 +080018
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020019import com.google.common.collect.ImmutableList;
20import com.google.common.collect.Lists;
Carmelo Cascone50d195f2018-09-11 13:26:38 -070021import com.google.common.collect.Maps;
Carmelo Cascone61469462019-03-05 23:59:11 -080022import com.google.common.util.concurrent.Futures;
23import com.google.common.util.concurrent.Striped;
CyberHasHe9ba39c2019-10-11 05:59:12 +080024import org.onosproject.drivers.p4runtime.mirror.P4RuntimeDefaultEntryMirror;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080025import org.onosproject.drivers.p4runtime.mirror.P4RuntimeTableMirror;
26import org.onosproject.drivers.p4runtime.mirror.TimedEntry;
Frank Wang0e805082017-07-21 14:37:35 +080027import org.onosproject.net.flow.DefaultFlowEntry;
28import org.onosproject.net.flow.FlowEntry;
29import org.onosproject.net.flow.FlowRule;
30import org.onosproject.net.flow.FlowRuleProgrammable;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080031import org.onosproject.net.pi.model.PiCounterType;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020032import org.onosproject.net.pi.model.PiPipelineModel;
Carmelo Cascone87892e22017-11-13 16:01:29 -080033import org.onosproject.net.pi.model.PiTableId;
steven308017632e152018-10-20 00:51:08 +080034import org.onosproject.net.pi.runtime.PiCounterCell;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020035import org.onosproject.net.pi.runtime.PiCounterCellData;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080036import org.onosproject.net.pi.runtime.PiCounterCellHandle;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020037import org.onosproject.net.pi.runtime.PiCounterCellId;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080038import org.onosproject.net.pi.runtime.PiEntityType;
39import org.onosproject.net.pi.runtime.PiHandle;
CyberHasHe9ba39c2019-10-11 05:59:12 +080040import org.onosproject.net.pi.runtime.PiMatchKey;
Frank Wang0e805082017-07-21 14:37:35 +080041import org.onosproject.net.pi.runtime.PiTableEntry;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080042import org.onosproject.net.pi.runtime.PiTableEntryHandle;
43import org.onosproject.net.pi.service.PiFlowRuleTranslator;
44import org.onosproject.net.pi.service.PiTranslatedEntity;
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080045import org.onosproject.net.pi.service.PiTranslationException;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080046import org.onosproject.p4runtime.api.P4RuntimeReadClient;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080047import org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType;
Carmelo Cascone61469462019-03-05 23:59:11 -080048import org.onosproject.p4runtime.api.P4RuntimeWriteClient.WriteRequest;
49import org.onosproject.p4runtime.api.P4RuntimeWriteClient.WriteResponse;
Frank Wang0e805082017-07-21 14:37:35 +080050
51import java.util.Collection;
52import java.util.Collections;
Manjunath Vanaraj59ad6572017-12-26 11:10:57 +053053import java.util.List;
Carmelo Cascone3da671a2018-02-12 10:43:35 -080054import java.util.Map;
Carmelo Cascone26600972018-09-10 00:23:20 -070055import java.util.Objects;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080056import java.util.Optional;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020057import java.util.Set;
Carmelo Cascone61469462019-03-05 23:59:11 -080058import java.util.concurrent.CompletableFuture;
59import java.util.concurrent.locks.Lock;
Carmelo Casconefe99be92017-09-11 21:55:54 +020060import java.util.stream.Collectors;
Daniele Moro46b27632021-02-01 17:12:16 +010061import java.util.stream.Stream;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020062
CyberHasHe9ba39c2019-10-11 05:59:12 +080063import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_DELETE_BEFORE_UPDATE;
64import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_READ_COUNTERS_WITH_TABLE_ENTRIES;
65import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_READ_FROM_MIRROR;
66import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_SUPPORT_DEFAULT_TABLE_ENTRY;
67import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_SUPPORT_TABLE_COUNTERS;
Daniele Moro46b27632021-02-01 17:12:16 +010068import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_TABLE_WILCARD_READS;
CyberHasHe9ba39c2019-10-11 05:59:12 +080069import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DELETE_BEFORE_UPDATE;
70import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.READ_COUNTERS_WITH_TABLE_ENTRIES;
71import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.READ_FROM_MIRROR;
72import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.SUPPORT_DEFAULT_TABLE_ENTRY;
73import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.SUPPORT_TABLE_COUNTERS;
Daniele Moro46b27632021-02-01 17:12:16 +010074import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.TABLE_WILCARD_READS;
Andrea Campanella0288c872017-08-07 18:32:51 +020075import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.APPLY;
76import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.REMOVE;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020077import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080078import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType.DELETE;
79import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType.INSERT;
80import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType.MODIFY;
Frank Wang0e805082017-07-21 14:37:35 +080081
82/**
Carmelo Casconee3a7c742017-09-01 01:25:52 +020083 * Implementation of the flow rule programmable behaviour for P4Runtime.
Frank Wang0e805082017-07-21 14:37:35 +080084 */
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080085public class P4RuntimeFlowRuleProgrammable
86 extends AbstractP4RuntimeHandlerBehaviour
87 implements FlowRuleProgrammable {
Frank Wang0e805082017-07-21 14:37:35 +080088
Carmelo Cascone61469462019-03-05 23:59:11 -080089 // Used to make sure concurrent calls to write flow rules are serialized so
90 // that each request gets consistent access to mirror state.
91 private static final Striped<Lock> WRITE_LOCKS = Striped.lock(30);
92
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020093 private PiPipelineModel pipelineModel;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080094 private P4RuntimeTableMirror tableMirror;
95 private PiFlowRuleTranslator translator;
CyberHasHe9ba39c2019-10-11 05:59:12 +080096 private P4RuntimeDefaultEntryMirror defaultEntryMirror;
Frank Wang0e805082017-07-21 14:37:35 +080097
Carmelo Casconee3a7c742017-09-01 01:25:52 +020098 @Override
Carmelo Casconec32976e2019-04-08 14:50:52 -070099 protected boolean setupBehaviour(String opName) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200100
Carmelo Casconec32976e2019-04-08 14:50:52 -0700101 if (!super.setupBehaviour(opName)) {
Frank Wang0e805082017-07-21 14:37:35 +0800102 return false;
103 }
104
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200105 pipelineModel = pipeconf.pipelineModel();
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800106 tableMirror = handler().get(P4RuntimeTableMirror.class);
Yi Tsengd7716482018-10-31 15:34:30 -0700107 translator = translationService.flowRuleTranslator();
CyberHasHe9ba39c2019-10-11 05:59:12 +0800108 defaultEntryMirror = handler().get(P4RuntimeDefaultEntryMirror.class);
Frank Wang0e805082017-07-21 14:37:35 +0800109 return true;
110 }
111
112 @Override
113 public Collection<FlowEntry> getFlowEntries() {
114
Carmelo Casconec32976e2019-04-08 14:50:52 -0700115 if (!setupBehaviour("getFlowEntries()")) {
Frank Wang0e805082017-07-21 14:37:35 +0800116 return Collections.emptyList();
117 }
118
CyberHasHe9ba39c2019-10-11 05:59:12 +0800119 if (driverBoolProperty(READ_FROM_MIRROR,
Carmelo Casconecd9c2dd2019-11-21 14:42:38 -0800120 DEFAULT_READ_FROM_MIRROR)) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800121 return getFlowEntriesFromMirror();
Carmelo Casconefe99be92017-09-11 21:55:54 +0200122 }
123
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800124 final ImmutableList.Builder<FlowEntry> result = ImmutableList.builder();
125 final List<PiTableEntry> inconsistentEntries = Lists.newArrayList();
Frank Wang0e805082017-07-21 14:37:35 +0800126
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800127 // Read table entries from device.
128 final Collection<PiTableEntry> deviceEntries = getAllTableEntriesFromDevice();
129 if (deviceEntries == null) {
130 // Potential error at the client level.
Carmelo Casconee5b28722018-06-22 17:28:28 +0200131 return Collections.emptyList();
132 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200133
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700134 // Synchronize mirror with the device state.
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800135 tableMirror.sync(deviceId, deviceEntries);
136
137 if (deviceEntries.isEmpty()) {
138 // Nothing to do.
139 return Collections.emptyList();
140 }
141
Carmelo Casconec32976e2019-04-08 14:50:52 -0700142 final Map<PiTableEntryHandle, PiCounterCellData> counterCellMap =
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700143 readEntryCounters(deviceEntries);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200144 // Forge flow entries with counter values.
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700145 for (PiTableEntry entry : deviceEntries) {
Carmelo Casconec32976e2019-04-08 14:50:52 -0700146 final PiTableEntryHandle handle = entry.handle(deviceId);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200147 final FlowEntry flowEntry = forgeFlowEntry(
Carmelo Casconec32976e2019-04-08 14:50:52 -0700148 entry, handle, counterCellMap.get(handle));
Carmelo Casconee5b28722018-06-22 17:28:28 +0200149 if (flowEntry == null) {
150 // Entry is on device but unknown to translation service or
Carmelo Casconecd9c2dd2019-11-21 14:42:38 -0800151 // device mirror. Inconsistent. Mark for removal if this is not
152 // an original default entry (i.e, the same defined in the P4
153 // program via default_action, which cannot be removed.)
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700154 if (!isOriginalDefaultEntry(entry)) {
155 inconsistentEntries.add(entry);
156 }
Carmelo Casconee5b28722018-06-22 17:28:28 +0200157 } else {
158 result.add(flowEntry);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200159 }
160 }
161
pierventre1e28f772021-02-12 17:22:38 +0100162 // Default entries need to be treated in a different way according to the spec:
163 // the client can modify (reset or update) them but cannot remove the entries
164 List<PiTableEntry> inconsistentDefaultEntries = Lists.newArrayList();
165 List<PiTableEntry> tempDefaultEntries = inconsistentEntries.stream()
166 .filter(PiTableEntry::isDefaultAction)
167 .collect(Collectors.toList());
168 inconsistentEntries.removeAll(tempDefaultEntries);
169 // Once we have removed the default entry from inconsistentEntries we need to
170 // craft for each default entry a copy without the action field. According to
171 // the spec leaving the action field unset will reset the original default entry.
172 tempDefaultEntries.forEach(piTableEntry -> {
173 PiTableEntry resetEntry = PiTableEntry.builder()
174 .forTable(piTableEntry.table()).build();
175 inconsistentDefaultEntries.add(resetEntry);
176 });
177
178 // Clean up of inconsistent entries.
179 if (!inconsistentEntries.isEmpty() || !inconsistentDefaultEntries.isEmpty()) {
180 WriteRequest writeRequest = client.write(p4DeviceId, pipeconf);
181 // Trigger remove of inconsistent entries.
182 if (!inconsistentEntries.isEmpty()) {
183 log.warn("Found {} inconsistent table entries on {}, removing them...",
184 inconsistentEntries.size(), deviceId);
185 writeRequest = writeRequest.entities(inconsistentEntries, DELETE);
186 }
187
188 // Trigger reset of inconsistent default entries.
189 if (!inconsistentDefaultEntries.isEmpty()) {
190 log.warn("Found {} inconsistent default table entries on {}, resetting them...",
191 inconsistentDefaultEntries.size(), deviceId);
192 writeRequest = writeRequest.entities(inconsistentDefaultEntries, MODIFY);
193 }
194
195 // Submit delete request for non-default entries and modify request
196 // for default entries. Updates mirror when done.
197 writeRequest.submit().whenComplete((response, ex) -> {
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700198 if (ex != null) {
199 log.error("Exception removing inconsistent table entries", ex);
200 } else {
201 log.debug("Successfully removed {} out of {} inconsistent entries",
pierventre1e28f772021-02-12 17:22:38 +0100202 response.success().size(), response.all().size());
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700203 }
pierventre1e28f772021-02-12 17:22:38 +0100204 // We can use the entity as the handle does not contain the action field
205 // so the key will be removed even if the table entry is different
206 response.success().forEach(entity -> tableMirror.remove((PiTableEntryHandle) entity.handle()));
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700207 });
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200208 }
209
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800210 return result.build();
Frank Wang0e805082017-07-21 14:37:35 +0800211 }
212
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800213 private Collection<PiTableEntry> getAllTableEntriesFromDevice() {
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700214 final P4RuntimeReadClient.ReadRequest request = client.read(
215 p4DeviceId, pipeconf);
Daniele Moro46b27632021-02-01 17:12:16 +0100216 final boolean supportDefaultTableEntry = driverBoolProperty(
217 SUPPORT_DEFAULT_TABLE_ENTRY, DEFAULT_SUPPORT_DEFAULT_TABLE_ENTRY);
218 final boolean tableWildcardReads = driverBoolProperty(
219 TABLE_WILCARD_READS, DEFAULT_TABLE_WILCARD_READS);
220 if (!tableWildcardReads) {
221 // Read entries from all non-constant tables, including default ones.
222 pipelineModel.tables().stream()
223 .filter(t -> !t.isConstantTable())
224 .forEach(t -> {
225 request.tableEntries(t.id());
226 if (supportDefaultTableEntry && t.constDefaultAction().isEmpty()) {
227 request.defaultTableEntry(t.id());
228 }
229 });
230 } else {
231 request.allTableEntries();
232 if (supportDefaultTableEntry) {
233 request.allDefaultTableEntries();
234 }
235 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800236 final P4RuntimeReadClient.ReadResponse response = request.submitSync();
237 if (!response.isSuccess()) {
238 return null;
239 }
Daniele Moro46b27632021-02-01 17:12:16 +0100240 Stream<PiTableEntry> piTableEntries = response.all(PiTableEntry.class).stream()
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800241 // Device implementation might return duplicate entries. For
242 // example if reading only default ones is not supported and
243 // non-default entries are returned, by using distinct() we
244 // are robust against that possibility.
Daniele Moro46b27632021-02-01 17:12:16 +0100245 .distinct();
246 if (tableWildcardReads) {
247 // When doing a wildcard read on all tables, the device might
248 // return table entries of tables not present in the pipeline
249 // model or constant (default) entries that are filtered out.
250 piTableEntries = piTableEntries.filter(te -> {
251 var piTableModel = pipelineModel.table(te.table());
252 if (piTableModel.isEmpty() ||
253 piTableModel.get().isConstantTable() ||
254 (supportDefaultTableEntry && piTableModel.get().constDefaultAction().isPresent())) {
255 return false;
256 }
257 return true;
258 });
259 }
260 return piTableEntries.collect(Collectors.toList());
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700261 }
262
Frank Wang0e805082017-07-21 14:37:35 +0800263 @Override
264 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200265 return processFlowRules(rules, APPLY);
Frank Wang0e805082017-07-21 14:37:35 +0800266 }
267
268 @Override
269 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200270 return processFlowRules(rules, REMOVE);
Frank Wang0e805082017-07-21 14:37:35 +0800271 }
272
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800273 private FlowEntry forgeFlowEntry(PiTableEntry entry,
Carmelo Casconec32976e2019-04-08 14:50:52 -0700274 PiTableEntryHandle handle,
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800275 PiCounterCellData cellData) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800276 final Optional<PiTranslatedEntity<FlowRule, PiTableEntry>>
277 translatedEntity = translator.lookup(handle);
278 final TimedEntry<PiTableEntry> timedEntry = tableMirror.get(handle);
279
CyberHasHe9ba39c2019-10-11 05:59:12 +0800280 // A default entry might not be present in the translation store if it
281 // was not inserted by an app. No need to log.
Carmelo Casconecd9c2dd2019-11-21 14:42:38 -0800282 if (translatedEntity.isEmpty()) {
CyberHasHe9ba39c2019-10-11 05:59:12 +0800283 if (!isOriginalDefaultEntry(entry)) {
284 log.warn("Table entry handle not found in translation store: {}", handle);
285 }
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800286 return null;
287 }
Carmelo Casconee44592f2018-09-12 02:24:47 -0700288 if (!translatedEntity.get().translated().equals(entry)) {
289 log.warn("Table entry obtained from device {} is different from " +
290 "one in in translation store: device={}, store={}",
291 deviceId, entry, translatedEntity.get().translated());
292 return null;
293 }
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800294 if (timedEntry == null) {
Carmelo Cascone26600972018-09-10 00:23:20 -0700295 log.warn("Table entry handle not found in device mirror: {}", handle);
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800296 return null;
297 }
298
299 if (cellData != null) {
300 return new DefaultFlowEntry(translatedEntity.get().original(),
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700301 ADDED, timedEntry.lifeSec(), cellData.packets(),
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800302 cellData.bytes());
303 } else {
304 return new DefaultFlowEntry(translatedEntity.get().original(),
305 ADDED, timedEntry.lifeSec(), 0, 0);
306 }
307 }
308
309 private Collection<FlowEntry> getFlowEntriesFromMirror() {
310 return tableMirror.getAll(deviceId).stream()
311 .map(timedEntry -> forgeFlowEntry(
Carmelo Casconec32976e2019-04-08 14:50:52 -0700312 timedEntry.entry(), timedEntry.entry().handle(deviceId), null))
Carmelo Cascone26600972018-09-10 00:23:20 -0700313 .filter(Objects::nonNull)
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800314 .collect(Collectors.toList());
315 }
316
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800317 private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules,
318 Operation driverOperation) {
Carmelo Casconec32976e2019-04-08 14:50:52 -0700319 if (!setupBehaviour("processFlowRules()") || rules.isEmpty()) {
Frank Wang0e805082017-07-21 14:37:35 +0800320 return Collections.emptyList();
321 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800322 // Created batched write request.
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700323 final WriteRequest request = client.write(p4DeviceId, pipeconf);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800324 // For each rule, translate to PI and append to write request.
325 final Map<PiHandle, FlowRule> handleToRuleMap = Maps.newHashMap();
326 final List<FlowRule> skippedRules = Lists.newArrayList();
Carmelo Cascone61469462019-03-05 23:59:11 -0800327 final CompletableFuture<WriteResponse> futureResponse;
328 WRITE_LOCKS.get(deviceId).lock();
329 try {
330 for (FlowRule rule : rules) {
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700331 // Translate.
Carmelo Cascone61469462019-03-05 23:59:11 -0800332 final PiTableEntry entry;
333 try {
334 entry = translator.translate(rule, pipeconf);
335 } catch (PiTranslationException e) {
336 log.warn("Unable to translate flow rule for pipeconf '{}': {} [{}]",
337 pipeconf.id(), e.getMessage(), rule);
338 // Next rule.
339 continue;
340 }
341 final PiTableEntryHandle handle = entry.handle(deviceId);
342 handleToRuleMap.put(handle, rule);
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700343 // Update translation store.
344 if (driverOperation.equals(APPLY)) {
345 translator.learn(handle, new PiTranslatedEntity<>(
346 rule, entry, handle));
347 } else {
348 translator.forget(handle);
349 }
Carmelo Cascone61469462019-03-05 23:59:11 -0800350 // Append entry to batched write request (returns false), or skip (true)
351 if (appendEntryToWriteRequestOrSkip(
352 request, handle, entry, driverOperation)) {
353 skippedRules.add(rule);
Carmelo Cascone61469462019-03-05 23:59:11 -0800354 }
Frank Wang0e805082017-07-21 14:37:35 +0800355 }
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700356 if (request.pendingUpdates().isEmpty()) {
357 // All good. No need to write on device.
358 return rules;
359 }
Carmelo Cascone61469462019-03-05 23:59:11 -0800360 // Update mirror.
361 tableMirror.applyWriteRequest(request);
362 // Async submit request to server.
363 futureResponse = request.submit();
364 } finally {
365 WRITE_LOCKS.get(deviceId).unlock();
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200366 }
Carmelo Cascone61469462019-03-05 23:59:11 -0800367 // Wait for response.
368 final WriteResponse response = Futures.getUnchecked(futureResponse);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800369 // Derive successfully applied flow rule from response.
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700370 final List<FlowRule> appliedRules = getAppliedFlowRules(
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800371 response, handleToRuleMap, driverOperation);
372 // Return skipped and applied rules.
373 return ImmutableList.<FlowRule>builder()
374 .addAll(skippedRules).addAll(appliedRules).build();
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800375 }
376
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700377 private List<FlowRule> getAppliedFlowRules(
Carmelo Cascone61469462019-03-05 23:59:11 -0800378 WriteResponse response,
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800379 Map<PiHandle, FlowRule> handleToFlowRuleMap,
380 Operation driverOperation) {
381 // Returns a list of flow rules that were successfully written on the
382 // server according to the given write response and operation.
383 return response.success().stream()
384 .filter(r -> r.entityType().equals(PiEntityType.TABLE_ENTRY))
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700385 .filter(r -> {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800386 // Filter intermediate responses (e.g. P4Runtime DELETE
387 // during FlowRule APPLY because we are performing
388 // delete-before-update)
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700389 return isUpdateTypeRelevant(r.updateType(), driverOperation);
390 })
391 .map(r -> {
392 final FlowRule rule = handleToFlowRuleMap.get(r.handle());
393 if (rule == null) {
394 log.warn("Server returned unrecognized table entry " +
395 "handle in write response: {}", r.handle());
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800396 }
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700397 return rule;
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800398 })
399 .filter(Objects::nonNull)
400 .collect(Collectors.toList());
401 }
402
403 private boolean isUpdateTypeRelevant(UpdateType p4UpdateType, Operation driverOperation) {
404 switch (p4UpdateType) {
405 case INSERT:
406 case MODIFY:
407 if (!driverOperation.equals(APPLY)) {
408 return false;
409 }
410 break;
411 case DELETE:
412 if (!driverOperation.equals(REMOVE)) {
413 return false;
414 }
415 break;
416 default:
417 log.error("Unknown update type {}", p4UpdateType);
418 return false;
419 }
420 return true;
421 }
422
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800423 private boolean appendEntryToWriteRequestOrSkip(
Carmelo Cascone61469462019-03-05 23:59:11 -0800424 final WriteRequest writeRequest,
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800425 final PiTableEntryHandle handle,
426 PiTableEntry piEntryToApply,
427 final Operation driverOperation) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800428 // Depending on the driver operation, and if a matching rule exists on
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800429 // the device/mirror, decide which P4Runtime update operation to perform
430 // for this entry. In some cases, the entry is skipped from the write
431 // request but we want to return the corresponding flow rule as
432 // successfully written. In this case, we return true.
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800433 final TimedEntry<PiTableEntry> piEntryOnDevice = tableMirror.get(handle);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800434 final UpdateType updateType;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700435
Carmelo Cascone1ae25062019-03-07 16:02:22 -0800436 final boolean supportDefaultEntry = driverBoolProperty(
CyberHasHe9ba39c2019-10-11 05:59:12 +0800437 SUPPORT_DEFAULT_TABLE_ENTRY,
438 DEFAULT_SUPPORT_DEFAULT_TABLE_ENTRY);
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700439 final boolean deleteBeforeUpdate = driverBoolProperty(
CyberHasHe9ba39c2019-10-11 05:59:12 +0800440 DELETE_BEFORE_UPDATE,
441 DEFAULT_DELETE_BEFORE_UPDATE);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800442
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800443 if (driverOperation == APPLY) {
444 if (piEntryOnDevice == null) {
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700445 // Entry is first-timer, INSERT or MODIFY if default action.
Carmelo Cascone1ae25062019-03-07 16:02:22 -0800446 updateType = !piEntryToApply.isDefaultAction() || !supportDefaultEntry
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700447 ? INSERT : MODIFY;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800448 } else {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800449 if (piEntryToApply.action().equals(piEntryOnDevice.entry().action())) {
450 // FIXME: should we check for other attributes of the table
451 // entry? For example can we modify the priority?
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800452 log.debug("Ignoring re-apply of existing entry: {}", piEntryToApply);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800453 return true;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700454 } else if (deleteBeforeUpdate && !piEntryToApply.isDefaultAction()) {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800455 // Some devices return error when updating existing entries.
456 // If requested, remove entry before re-inserting the
457 // modified one, except the default action entry, that
458 // cannot be removed.
459 writeRequest.delete(handle);
460 updateType = INSERT;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800461 } else {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800462 updateType = MODIFY;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800463 }
464 }
465 } else {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800466 // REMOVE.
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700467 if (piEntryToApply.isDefaultAction()) {
CyberHasHe9ba39c2019-10-11 05:59:12 +0800468 // Cannot remove default action. Instead we should modify it to
469 // use the original one as specified in the P4 program.
Carmelo Casconeaf877e92019-04-28 13:44:01 -0700470 final PiTableEntry originalDefaultEntry = getOriginalDefaultEntry(
471 piEntryToApply.table());
472 if (originalDefaultEntry == null) {
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700473 return false;
474 }
Carmelo Casconeaf877e92019-04-28 13:44:01 -0700475 return appendEntryToWriteRequestOrSkip(
476 writeRequest, originalDefaultEntry.handle(deviceId),
477 originalDefaultEntry, APPLY);
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700478 } else {
Carmelo Casconeaf877e92019-04-28 13:44:01 -0700479 if (piEntryOnDevice == null) {
480 log.debug("Ignoring delete of missing entry: {}",
481 piEntryToApply);
482 return true;
483 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800484 updateType = DELETE;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700485 }
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800486 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800487 writeRequest.entity(piEntryToApply, updateType);
488 return false;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800489 }
490
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700491 private PiTableEntry getOriginalDefaultEntry(PiTableId tableId) {
CyberHasHe9ba39c2019-10-11 05:59:12 +0800492 final PiTableEntryHandle handle = PiTableEntry.builder()
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700493 .forTable(tableId)
CyberHasHe9ba39c2019-10-11 05:59:12 +0800494 .withMatchKey(PiMatchKey.EMPTY)
495 .build()
496 .handle(deviceId);
497 final TimedEntry<PiTableEntry> originalDefaultEntry = defaultEntryMirror.get(handle);
498 if (originalDefaultEntry != null) {
499 return originalDefaultEntry.entry();
500 }
501 return null;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700502 }
503
504 private boolean isOriginalDefaultEntry(PiTableEntry entry) {
505 if (!entry.isDefaultAction()) {
506 return false;
507 }
508 final PiTableEntry originalDefaultEntry = getOriginalDefaultEntry(entry.table());
CyberHasHe9ba39c2019-10-11 05:59:12 +0800509 if (originalDefaultEntry == null) {
510 return false;
511 }
512 // Sometimes the default action may be null
513 // e.g. In basic pipeline, the default action in wcmp_table is null
514 if (originalDefaultEntry.action() == null) {
515 return entry.action() == null;
516 }
517 return originalDefaultEntry.action().equals(entry.action());
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700518 }
519
Carmelo Casconec32976e2019-04-08 14:50:52 -0700520 private Map<PiTableEntryHandle, PiCounterCellData> readEntryCounters(
Carmelo Cascone255125d2018-04-11 14:03:22 -0700521 Collection<PiTableEntry> tableEntries) {
Carmelo Casconec32976e2019-04-08 14:50:52 -0700522
Carmelo Cascone255125d2018-04-11 14:03:22 -0700523 if (!driverBoolProperty(SUPPORT_TABLE_COUNTERS,
Carmelo Casconee5b28722018-06-22 17:28:28 +0200524 DEFAULT_SUPPORT_TABLE_COUNTERS)
525 || tableEntries.isEmpty()) {
Carmelo Cascone255125d2018-04-11 14:03:22 -0700526 return Collections.emptyMap();
527 }
528
Carmelo Casconec32976e2019-04-08 14:50:52 -0700529 if (driverBoolProperty(READ_COUNTERS_WITH_TABLE_ENTRIES,
530 DEFAULT_READ_COUNTERS_WITH_TABLE_ENTRIES)) {
531 return tableEntries.stream()
532 .filter(t -> t.counter() != null)
533 .collect(Collectors.toMap(
534 t -> t.handle(deviceId), PiTableEntry::counter));
535 } else {
536 final Set<PiHandle> cellHandles = tableEntries.stream()
537 .filter(e -> !e.isDefaultAction())
538 .filter(e -> tableHasCounter(e.table()))
539 .map(PiCounterCellId::ofDirect)
540 .map(id -> PiCounterCellHandle.of(deviceId, id))
541 .collect(Collectors.toSet());
542 // FIXME: We might be sending a very large read request...
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700543 return client.read(p4DeviceId, pipeconf)
Carmelo Casconec32976e2019-04-08 14:50:52 -0700544 .handles(cellHandles)
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800545 .submitSync()
546 .all(PiCounterCell.class).stream()
547 .filter(c -> c.cellId().counterType().equals(PiCounterType.DIRECT))
Carmelo Casconec32976e2019-04-08 14:50:52 -0700548 .collect(Collectors.toMap(
549 c -> c.cellId().tableEntry().handle(deviceId),
550 PiCounterCell::data));
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800551 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200552 }
553
Carmelo Cascone255125d2018-04-11 14:03:22 -0700554 private boolean tableHasCounter(PiTableId tableId) {
555 return pipelineModel.table(tableId).isPresent() &&
556 !pipelineModel.table(tableId).get().counters().isEmpty();
557 }
558
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200559 enum Operation {
560 APPLY, REMOVE
Frank Wang0e805082017-07-21 14:37:35 +0800561 }
Carmelo Cascone87892e22017-11-13 16:01:29 -0800562}