blob: 3d06c2e7f74632e929301f5d4f3d951c72b263b4 [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;
CyberHasH39b808b2019-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;
CyberHasH39b808b2019-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;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020061
CyberHasH39b808b2019-10-11 05:59:12 +080062import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_DELETE_BEFORE_UPDATE;
63import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_READ_COUNTERS_WITH_TABLE_ENTRIES;
64import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_READ_FROM_MIRROR;
65import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_SUPPORT_DEFAULT_TABLE_ENTRY;
66import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DEFAULT_SUPPORT_TABLE_COUNTERS;
67import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.DELETE_BEFORE_UPDATE;
68import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.READ_COUNTERS_WITH_TABLE_ENTRIES;
69import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.READ_FROM_MIRROR;
70import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.SUPPORT_DEFAULT_TABLE_ENTRY;
71import static org.onosproject.drivers.p4runtime.P4RuntimeDriverProperties.SUPPORT_TABLE_COUNTERS;
Andrea Campanella0288c872017-08-07 18:32:51 +020072import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.APPLY;
73import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.REMOVE;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020074import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080075import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType.DELETE;
76import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType.INSERT;
77import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType.MODIFY;
Frank Wang0e805082017-07-21 14:37:35 +080078
79/**
Carmelo Casconee3a7c742017-09-01 01:25:52 +020080 * Implementation of the flow rule programmable behaviour for P4Runtime.
Frank Wang0e805082017-07-21 14:37:35 +080081 */
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080082public class P4RuntimeFlowRuleProgrammable
83 extends AbstractP4RuntimeHandlerBehaviour
84 implements FlowRuleProgrammable {
Frank Wang0e805082017-07-21 14:37:35 +080085
Carmelo Cascone61469462019-03-05 23:59:11 -080086 // Used to make sure concurrent calls to write flow rules are serialized so
87 // that each request gets consistent access to mirror state.
88 private static final Striped<Lock> WRITE_LOCKS = Striped.lock(30);
89
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020090 private PiPipelineModel pipelineModel;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080091 private P4RuntimeTableMirror tableMirror;
92 private PiFlowRuleTranslator translator;
CyberHasH39b808b2019-10-11 05:59:12 +080093 private P4RuntimeDefaultEntryMirror defaultEntryMirror;
Frank Wang0e805082017-07-21 14:37:35 +080094
Carmelo Casconee3a7c742017-09-01 01:25:52 +020095 @Override
Carmelo Casconec32976e2019-04-08 14:50:52 -070096 protected boolean setupBehaviour(String opName) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020097
Carmelo Casconec32976e2019-04-08 14:50:52 -070098 if (!super.setupBehaviour(opName)) {
Frank Wang0e805082017-07-21 14:37:35 +080099 return false;
100 }
101
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200102 pipelineModel = pipeconf.pipelineModel();
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800103 tableMirror = handler().get(P4RuntimeTableMirror.class);
Yi Tsengd7716482018-10-31 15:34:30 -0700104 translator = translationService.flowRuleTranslator();
CyberHasH39b808b2019-10-11 05:59:12 +0800105 defaultEntryMirror = handler().get(P4RuntimeDefaultEntryMirror.class);
Frank Wang0e805082017-07-21 14:37:35 +0800106 return true;
107 }
108
109 @Override
110 public Collection<FlowEntry> getFlowEntries() {
111
Carmelo Casconec32976e2019-04-08 14:50:52 -0700112 if (!setupBehaviour("getFlowEntries()")) {
Frank Wang0e805082017-07-21 14:37:35 +0800113 return Collections.emptyList();
114 }
115
CyberHasH39b808b2019-10-11 05:59:12 +0800116 if (driverBoolProperty(READ_FROM_MIRROR,
Carmelo Cascone5e1ae252019-11-21 14:42:38 -0800117 DEFAULT_READ_FROM_MIRROR)) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800118 return getFlowEntriesFromMirror();
Carmelo Casconefe99be92017-09-11 21:55:54 +0200119 }
120
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800121 final ImmutableList.Builder<FlowEntry> result = ImmutableList.builder();
122 final List<PiTableEntry> inconsistentEntries = Lists.newArrayList();
Frank Wang0e805082017-07-21 14:37:35 +0800123
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800124 // Read table entries from device.
125 final Collection<PiTableEntry> deviceEntries = getAllTableEntriesFromDevice();
126 if (deviceEntries == null) {
127 // Potential error at the client level.
Carmelo Casconee5b28722018-06-22 17:28:28 +0200128 return Collections.emptyList();
129 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200130
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700131 // Synchronize mirror with the device state.
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800132 tableMirror.sync(deviceId, deviceEntries);
133
134 if (deviceEntries.isEmpty()) {
135 // Nothing to do.
136 return Collections.emptyList();
137 }
138
Carmelo Casconec32976e2019-04-08 14:50:52 -0700139 final Map<PiTableEntryHandle, PiCounterCellData> counterCellMap =
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700140 readEntryCounters(deviceEntries);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200141 // Forge flow entries with counter values.
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700142 for (PiTableEntry entry : deviceEntries) {
Carmelo Casconec32976e2019-04-08 14:50:52 -0700143 final PiTableEntryHandle handle = entry.handle(deviceId);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200144 final FlowEntry flowEntry = forgeFlowEntry(
Carmelo Casconec32976e2019-04-08 14:50:52 -0700145 entry, handle, counterCellMap.get(handle));
Carmelo Casconee5b28722018-06-22 17:28:28 +0200146 if (flowEntry == null) {
147 // Entry is on device but unknown to translation service or
Carmelo Cascone5e1ae252019-11-21 14:42:38 -0800148 // device mirror. Inconsistent. Mark for removal if this is not
149 // an original default entry (i.e, the same defined in the P4
150 // program via default_action, which cannot be removed.)
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700151 if (!isOriginalDefaultEntry(entry)) {
152 inconsistentEntries.add(entry);
153 }
Carmelo Casconee5b28722018-06-22 17:28:28 +0200154 } else {
155 result.add(flowEntry);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200156 }
157 }
158
pierventreb1769a92021-02-12 17:22:38 +0100159 // Default entries need to be treated in a different way according to the spec:
160 // the client can modify (reset or update) them but cannot remove the entries
161 List<PiTableEntry> inconsistentDefaultEntries = Lists.newArrayList();
162 List<PiTableEntry> tempDefaultEntries = inconsistentEntries.stream()
163 .filter(PiTableEntry::isDefaultAction)
164 .collect(Collectors.toList());
165 inconsistentEntries.removeAll(tempDefaultEntries);
166 // Once we have removed the default entry from inconsistentEntries we need to
167 // craft for each default entry a copy without the action field. According to
168 // the spec leaving the action field unset will reset the original default entry.
169 tempDefaultEntries.forEach(piTableEntry -> {
170 PiTableEntry resetEntry = PiTableEntry.builder()
171 .forTable(piTableEntry.table()).build();
172 inconsistentDefaultEntries.add(resetEntry);
173 });
174
175 // Clean up of inconsistent entries.
176 if (!inconsistentEntries.isEmpty() || !inconsistentDefaultEntries.isEmpty()) {
177 WriteRequest writeRequest = client.write(p4DeviceId, pipeconf);
178 // Trigger remove of inconsistent entries.
179 if (!inconsistentEntries.isEmpty()) {
180 log.warn("Found {} inconsistent table entries on {}, removing them...",
181 inconsistentEntries.size(), deviceId);
182 writeRequest = writeRequest.entities(inconsistentEntries, DELETE);
183 }
184
185 // Trigger reset of inconsistent default entries.
186 if (!inconsistentDefaultEntries.isEmpty()) {
187 log.warn("Found {} inconsistent default table entries on {}, resetting them...",
188 inconsistentDefaultEntries.size(), deviceId);
189 writeRequest = writeRequest.entities(inconsistentDefaultEntries, MODIFY);
190 }
191
192 // Submit delete request for non-default entries and modify request
193 // for default entries. Updates mirror when done.
194 writeRequest.submit().whenComplete((response, ex) -> {
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700195 if (ex != null) {
196 log.error("Exception removing inconsistent table entries", ex);
197 } else {
198 log.debug("Successfully removed {} out of {} inconsistent entries",
pierventreb1769a92021-02-12 17:22:38 +0100199 response.success().size(), response.all().size());
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700200 }
pierventreb1769a92021-02-12 17:22:38 +0100201 // We can use the entity as the handle does not contain the action field
202 // so the key will be removed even if the table entry is different
203 response.success().forEach(entity -> tableMirror.remove((PiTableEntryHandle) entity.handle()));
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700204 });
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200205 }
206
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800207 return result.build();
Frank Wang0e805082017-07-21 14:37:35 +0800208 }
209
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800210 private Collection<PiTableEntry> getAllTableEntriesFromDevice() {
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700211 final P4RuntimeReadClient.ReadRequest request = client.read(
212 p4DeviceId, pipeconf);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800213 // Read entries from all non-constant tables, including default ones.
214 pipelineModel.tables().stream()
215 .filter(t -> !t.isConstantTable())
216 .forEach(t -> {
217 request.tableEntries(t.id());
Carmelo Cascone1ae25062019-03-07 16:02:22 -0800218 if (driverBoolProperty(SUPPORT_DEFAULT_TABLE_ENTRY,
219 DEFAULT_SUPPORT_DEFAULT_TABLE_ENTRY) &&
Carmelo Cascone5e1ae252019-11-21 14:42:38 -0800220 t.constDefaultAction().isEmpty()) {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800221 request.defaultTableEntry(t.id());
222 }
223 });
224 final P4RuntimeReadClient.ReadResponse response = request.submitSync();
225 if (!response.isSuccess()) {
226 return null;
227 }
228 return response.all(PiTableEntry.class).stream()
229 // Device implementation might return duplicate entries. For
230 // example if reading only default ones is not supported and
231 // non-default entries are returned, by using distinct() we
232 // are robust against that possibility.
233 .distinct()
234 .collect(Collectors.toList());
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700235 }
236
Frank Wang0e805082017-07-21 14:37:35 +0800237 @Override
238 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200239 return processFlowRules(rules, APPLY);
Frank Wang0e805082017-07-21 14:37:35 +0800240 }
241
242 @Override
243 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200244 return processFlowRules(rules, REMOVE);
Frank Wang0e805082017-07-21 14:37:35 +0800245 }
246
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800247 private FlowEntry forgeFlowEntry(PiTableEntry entry,
Carmelo Casconec32976e2019-04-08 14:50:52 -0700248 PiTableEntryHandle handle,
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800249 PiCounterCellData cellData) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800250 final Optional<PiTranslatedEntity<FlowRule, PiTableEntry>>
251 translatedEntity = translator.lookup(handle);
252 final TimedEntry<PiTableEntry> timedEntry = tableMirror.get(handle);
253
CyberHasH39b808b2019-10-11 05:59:12 +0800254 // A default entry might not be present in the translation store if it
255 // was not inserted by an app. No need to log.
Carmelo Cascone5e1ae252019-11-21 14:42:38 -0800256 if (translatedEntity.isEmpty()) {
CyberHasH39b808b2019-10-11 05:59:12 +0800257 if (!isOriginalDefaultEntry(entry)) {
258 log.warn("Table entry handle not found in translation store: {}", handle);
259 }
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800260 return null;
261 }
Carmelo Casconee44592f2018-09-12 02:24:47 -0700262 if (!translatedEntity.get().translated().equals(entry)) {
263 log.warn("Table entry obtained from device {} is different from " +
264 "one in in translation store: device={}, store={}",
265 deviceId, entry, translatedEntity.get().translated());
266 return null;
267 }
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800268 if (timedEntry == null) {
Carmelo Cascone26600972018-09-10 00:23:20 -0700269 log.warn("Table entry handle not found in device mirror: {}", handle);
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800270 return null;
271 }
272
273 if (cellData != null) {
274 return new DefaultFlowEntry(translatedEntity.get().original(),
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700275 ADDED, timedEntry.lifeSec(), cellData.packets(),
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800276 cellData.bytes());
277 } else {
278 return new DefaultFlowEntry(translatedEntity.get().original(),
279 ADDED, timedEntry.lifeSec(), 0, 0);
280 }
281 }
282
283 private Collection<FlowEntry> getFlowEntriesFromMirror() {
284 return tableMirror.getAll(deviceId).stream()
285 .map(timedEntry -> forgeFlowEntry(
Carmelo Casconec32976e2019-04-08 14:50:52 -0700286 timedEntry.entry(), timedEntry.entry().handle(deviceId), null))
Carmelo Cascone26600972018-09-10 00:23:20 -0700287 .filter(Objects::nonNull)
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800288 .collect(Collectors.toList());
289 }
290
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800291 private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules,
292 Operation driverOperation) {
Carmelo Casconec32976e2019-04-08 14:50:52 -0700293 if (!setupBehaviour("processFlowRules()") || rules.isEmpty()) {
Frank Wang0e805082017-07-21 14:37:35 +0800294 return Collections.emptyList();
295 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800296 // Created batched write request.
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700297 final WriteRequest request = client.write(p4DeviceId, pipeconf);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800298 // For each rule, translate to PI and append to write request.
299 final Map<PiHandle, FlowRule> handleToRuleMap = Maps.newHashMap();
300 final List<FlowRule> skippedRules = Lists.newArrayList();
Carmelo Cascone61469462019-03-05 23:59:11 -0800301 final CompletableFuture<WriteResponse> futureResponse;
302 WRITE_LOCKS.get(deviceId).lock();
303 try {
304 for (FlowRule rule : rules) {
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700305 // Translate.
Carmelo Cascone61469462019-03-05 23:59:11 -0800306 final PiTableEntry entry;
307 try {
308 entry = translator.translate(rule, pipeconf);
309 } catch (PiTranslationException e) {
310 log.warn("Unable to translate flow rule for pipeconf '{}': {} [{}]",
311 pipeconf.id(), e.getMessage(), rule);
312 // Next rule.
313 continue;
314 }
315 final PiTableEntryHandle handle = entry.handle(deviceId);
316 handleToRuleMap.put(handle, rule);
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700317 // Update translation store.
318 if (driverOperation.equals(APPLY)) {
319 translator.learn(handle, new PiTranslatedEntity<>(
320 rule, entry, handle));
321 } else {
322 translator.forget(handle);
323 }
Carmelo Cascone61469462019-03-05 23:59:11 -0800324 // Append entry to batched write request (returns false), or skip (true)
325 if (appendEntryToWriteRequestOrSkip(
326 request, handle, entry, driverOperation)) {
327 skippedRules.add(rule);
Carmelo Cascone61469462019-03-05 23:59:11 -0800328 }
Frank Wang0e805082017-07-21 14:37:35 +0800329 }
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700330 if (request.pendingUpdates().isEmpty()) {
331 // All good. No need to write on device.
332 return rules;
333 }
Carmelo Cascone61469462019-03-05 23:59:11 -0800334 // Update mirror.
335 tableMirror.applyWriteRequest(request);
336 // Async submit request to server.
337 futureResponse = request.submit();
338 } finally {
339 WRITE_LOCKS.get(deviceId).unlock();
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200340 }
Carmelo Cascone61469462019-03-05 23:59:11 -0800341 // Wait for response.
342 final WriteResponse response = Futures.getUnchecked(futureResponse);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800343 // Derive successfully applied flow rule from response.
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700344 final List<FlowRule> appliedRules = getAppliedFlowRules(
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800345 response, handleToRuleMap, driverOperation);
346 // Return skipped and applied rules.
347 return ImmutableList.<FlowRule>builder()
348 .addAll(skippedRules).addAll(appliedRules).build();
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800349 }
350
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700351 private List<FlowRule> getAppliedFlowRules(
Carmelo Cascone61469462019-03-05 23:59:11 -0800352 WriteResponse response,
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800353 Map<PiHandle, FlowRule> handleToFlowRuleMap,
354 Operation driverOperation) {
355 // Returns a list of flow rules that were successfully written on the
356 // server according to the given write response and operation.
357 return response.success().stream()
358 .filter(r -> r.entityType().equals(PiEntityType.TABLE_ENTRY))
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700359 .filter(r -> {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800360 // Filter intermediate responses (e.g. P4Runtime DELETE
361 // during FlowRule APPLY because we are performing
362 // delete-before-update)
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700363 return isUpdateTypeRelevant(r.updateType(), driverOperation);
364 })
365 .map(r -> {
366 final FlowRule rule = handleToFlowRuleMap.get(r.handle());
367 if (rule == null) {
368 log.warn("Server returned unrecognized table entry " +
369 "handle in write response: {}", r.handle());
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800370 }
Carmelo Casconeb4863b32019-03-13 18:54:34 -0700371 return rule;
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800372 })
373 .filter(Objects::nonNull)
374 .collect(Collectors.toList());
375 }
376
377 private boolean isUpdateTypeRelevant(UpdateType p4UpdateType, Operation driverOperation) {
378 switch (p4UpdateType) {
379 case INSERT:
380 case MODIFY:
381 if (!driverOperation.equals(APPLY)) {
382 return false;
383 }
384 break;
385 case DELETE:
386 if (!driverOperation.equals(REMOVE)) {
387 return false;
388 }
389 break;
390 default:
391 log.error("Unknown update type {}", p4UpdateType);
392 return false;
393 }
394 return true;
395 }
396
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800397 private boolean appendEntryToWriteRequestOrSkip(
Carmelo Cascone61469462019-03-05 23:59:11 -0800398 final WriteRequest writeRequest,
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800399 final PiTableEntryHandle handle,
400 PiTableEntry piEntryToApply,
401 final Operation driverOperation) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800402 // Depending on the driver operation, and if a matching rule exists on
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800403 // the device/mirror, decide which P4Runtime update operation to perform
404 // for this entry. In some cases, the entry is skipped from the write
405 // request but we want to return the corresponding flow rule as
406 // successfully written. In this case, we return true.
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800407 final TimedEntry<PiTableEntry> piEntryOnDevice = tableMirror.get(handle);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800408 final UpdateType updateType;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700409
Carmelo Cascone1ae25062019-03-07 16:02:22 -0800410 final boolean supportDefaultEntry = driverBoolProperty(
CyberHasH39b808b2019-10-11 05:59:12 +0800411 SUPPORT_DEFAULT_TABLE_ENTRY,
412 DEFAULT_SUPPORT_DEFAULT_TABLE_ENTRY);
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700413 final boolean deleteBeforeUpdate = driverBoolProperty(
CyberHasH39b808b2019-10-11 05:59:12 +0800414 DELETE_BEFORE_UPDATE,
415 DEFAULT_DELETE_BEFORE_UPDATE);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800416
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800417 if (driverOperation == APPLY) {
418 if (piEntryOnDevice == null) {
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700419 // Entry is first-timer, INSERT or MODIFY if default action.
Carmelo Cascone1ae25062019-03-07 16:02:22 -0800420 updateType = !piEntryToApply.isDefaultAction() || !supportDefaultEntry
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700421 ? INSERT : MODIFY;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800422 } else {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800423 if (piEntryToApply.action().equals(piEntryOnDevice.entry().action())) {
424 // FIXME: should we check for other attributes of the table
425 // entry? For example can we modify the priority?
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800426 log.debug("Ignoring re-apply of existing entry: {}", piEntryToApply);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800427 return true;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700428 } else if (deleteBeforeUpdate && !piEntryToApply.isDefaultAction()) {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800429 // Some devices return error when updating existing entries.
430 // If requested, remove entry before re-inserting the
431 // modified one, except the default action entry, that
432 // cannot be removed.
433 writeRequest.delete(handle);
434 updateType = INSERT;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800435 } else {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800436 updateType = MODIFY;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800437 }
438 }
439 } else {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800440 // REMOVE.
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700441 if (piEntryToApply.isDefaultAction()) {
CyberHasH39b808b2019-10-11 05:59:12 +0800442 // Cannot remove default action. Instead we should modify it to
443 // use the original one as specified in the P4 program.
Carmelo Casconeaf877e92019-04-28 13:44:01 -0700444 final PiTableEntry originalDefaultEntry = getOriginalDefaultEntry(
445 piEntryToApply.table());
446 if (originalDefaultEntry == null) {
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700447 return false;
448 }
Carmelo Casconeaf877e92019-04-28 13:44:01 -0700449 return appendEntryToWriteRequestOrSkip(
450 writeRequest, originalDefaultEntry.handle(deviceId),
451 originalDefaultEntry, APPLY);
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700452 } else {
Carmelo Casconeaf877e92019-04-28 13:44:01 -0700453 if (piEntryOnDevice == null) {
454 log.debug("Ignoring delete of missing entry: {}",
455 piEntryToApply);
456 return true;
457 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800458 updateType = DELETE;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700459 }
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800460 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800461 writeRequest.entity(piEntryToApply, updateType);
462 return false;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800463 }
464
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700465 private PiTableEntry getOriginalDefaultEntry(PiTableId tableId) {
CyberHasH39b808b2019-10-11 05:59:12 +0800466 final PiTableEntryHandle handle = PiTableEntry.builder()
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700467 .forTable(tableId)
CyberHasH39b808b2019-10-11 05:59:12 +0800468 .withMatchKey(PiMatchKey.EMPTY)
469 .build()
470 .handle(deviceId);
471 final TimedEntry<PiTableEntry> originalDefaultEntry = defaultEntryMirror.get(handle);
472 if (originalDefaultEntry != null) {
473 return originalDefaultEntry.entry();
474 }
475 return null;
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700476 }
477
478 private boolean isOriginalDefaultEntry(PiTableEntry entry) {
479 if (!entry.isDefaultAction()) {
480 return false;
481 }
482 final PiTableEntry originalDefaultEntry = getOriginalDefaultEntry(entry.table());
CyberHasH39b808b2019-10-11 05:59:12 +0800483 if (originalDefaultEntry == null) {
484 return false;
485 }
486 // Sometimes the default action may be null
487 // e.g. In basic pipeline, the default action in wcmp_table is null
488 if (originalDefaultEntry.action() == null) {
489 return entry.action() == null;
490 }
491 return originalDefaultEntry.action().equals(entry.action());
Carmelo Cascone50d195f2018-09-11 13:26:38 -0700492 }
493
Carmelo Casconec32976e2019-04-08 14:50:52 -0700494 private Map<PiTableEntryHandle, PiCounterCellData> readEntryCounters(
Carmelo Cascone255125d2018-04-11 14:03:22 -0700495 Collection<PiTableEntry> tableEntries) {
Carmelo Casconec32976e2019-04-08 14:50:52 -0700496
Carmelo Cascone255125d2018-04-11 14:03:22 -0700497 if (!driverBoolProperty(SUPPORT_TABLE_COUNTERS,
Carmelo Casconee5b28722018-06-22 17:28:28 +0200498 DEFAULT_SUPPORT_TABLE_COUNTERS)
499 || tableEntries.isEmpty()) {
Carmelo Cascone255125d2018-04-11 14:03:22 -0700500 return Collections.emptyMap();
501 }
502
Carmelo Casconec32976e2019-04-08 14:50:52 -0700503 if (driverBoolProperty(READ_COUNTERS_WITH_TABLE_ENTRIES,
504 DEFAULT_READ_COUNTERS_WITH_TABLE_ENTRIES)) {
505 return tableEntries.stream()
506 .filter(t -> t.counter() != null)
507 .collect(Collectors.toMap(
508 t -> t.handle(deviceId), PiTableEntry::counter));
509 } else {
510 final Set<PiHandle> cellHandles = tableEntries.stream()
511 .filter(e -> !e.isDefaultAction())
512 .filter(e -> tableHasCounter(e.table()))
513 .map(PiCounterCellId::ofDirect)
514 .map(id -> PiCounterCellHandle.of(deviceId, id))
515 .collect(Collectors.toSet());
516 // FIXME: We might be sending a very large read request...
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700517 return client.read(p4DeviceId, pipeconf)
Carmelo Casconec32976e2019-04-08 14:50:52 -0700518 .handles(cellHandles)
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800519 .submitSync()
520 .all(PiCounterCell.class).stream()
521 .filter(c -> c.cellId().counterType().equals(PiCounterType.DIRECT))
Carmelo Casconec32976e2019-04-08 14:50:52 -0700522 .collect(Collectors.toMap(
523 c -> c.cellId().tableEntry().handle(deviceId),
524 PiCounterCell::data));
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800525 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200526 }
527
Carmelo Cascone255125d2018-04-11 14:03:22 -0700528 private boolean tableHasCounter(PiTableId tableId) {
529 return pipelineModel.table(tableId).isPresent() &&
530 !pipelineModel.table(tableId).get().counters().isEmpty();
531 }
532
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200533 enum Operation {
534 APPLY, REMOVE
Frank Wang0e805082017-07-21 14:37:35 +0800535 }
Carmelo Cascone87892e22017-11-13 16:01:29 -0800536}