blob: 09f5aa99be0d401457d6535b741f5a656a522b29 [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;
Frank Wang0e805082017-07-21 14:37:35 +080021import com.google.common.collect.Maps;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020022import io.grpc.StatusRuntimeException;
Frank Wang0e805082017-07-21 14:37:35 +080023import org.onosproject.net.flow.DefaultFlowEntry;
24import org.onosproject.net.flow.FlowEntry;
25import org.onosproject.net.flow.FlowRule;
26import org.onosproject.net.flow.FlowRuleProgrammable;
Frank Wang0e805082017-07-21 14:37:35 +080027import org.onosproject.net.pi.model.PiPipelineInterpreter;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020028import org.onosproject.net.pi.model.PiPipelineModel;
29import org.onosproject.net.pi.model.PiTableModel;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020030import org.onosproject.net.pi.runtime.PiCounterCellData;
31import org.onosproject.net.pi.runtime.PiCounterCellId;
32import org.onosproject.net.pi.runtime.PiCounterId;
33import org.onosproject.net.pi.runtime.PiDirectCounterCellId;
Frank Wang0e805082017-07-21 14:37:35 +080034import org.onosproject.net.pi.runtime.PiFlowRuleTranslationService;
Frank Wang0e805082017-07-21 14:37:35 +080035import org.onosproject.net.pi.runtime.PiTableEntry;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020036import org.onosproject.net.pi.runtime.PiTableId;
Frank Wang0e805082017-07-21 14:37:35 +080037import org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType;
Andrea Campanella0288c872017-08-07 18:32:51 +020038import org.onosproject.p4runtime.api.P4RuntimeFlowRuleWrapper;
39import org.onosproject.p4runtime.api.P4RuntimeTableEntryReference;
Frank Wang0e805082017-07-21 14:37:35 +080040
41import java.util.Collection;
42import java.util.Collections;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020043import java.util.List;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020044import java.util.Map;
45import java.util.Set;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020046import java.util.concurrent.ConcurrentMap;
47import java.util.concurrent.ExecutionException;
48import java.util.concurrent.locks.Lock;
49import java.util.concurrent.locks.ReentrantLock;
Carmelo Casconefe99be92017-09-11 21:55:54 +020050import java.util.stream.Collectors;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020051
52import static com.google.common.collect.Lists.newArrayList;
Andrea Campanella0288c872017-08-07 18:32:51 +020053import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.APPLY;
54import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.REMOVE;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020055import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
Carmelo Cascone2308e522017-08-25 02:35:12 +020056import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.*;
Frank Wang0e805082017-07-21 14:37:35 +080057
58/**
Carmelo Casconee3a7c742017-09-01 01:25:52 +020059 * Implementation of the flow rule programmable behaviour for P4Runtime.
Frank Wang0e805082017-07-21 14:37:35 +080060 */
Carmelo Casconee3a7c742017-09-01 01:25:52 +020061public class P4RuntimeFlowRuleProgrammable extends AbstractP4RuntimeHandlerBehaviour implements FlowRuleProgrammable {
Frank Wang0e805082017-07-21 14:37:35 +080062
Carmelo Cascone2308e522017-08-25 02:35:12 +020063 /*
64 When updating an existing rule, if true, we issue a DELETE operation before inserting the new one, otherwise we
65 issue a MODIFY operation. This is useful fore devices that do not support MODIFY operations for table entries.
66 */
Carmelo Casconefe99be92017-09-11 21:55:54 +020067 // TODO: make this attribute configurable by child drivers (e.g. BMv2 or Tofino)
Carmelo Cascone2308e522017-08-25 02:35:12 +020068 private boolean deleteEntryBeforeUpdate = true;
69
Carmelo Cascone2308e522017-08-25 02:35:12 +020070 /*
71 If true, we ignore re-installing rules that are already known in the ENTRY_STORE, i.e. same match key and action.
72 */
Carmelo Casconefe99be92017-09-11 21:55:54 +020073 // TODO: can remove this check as soon as the multi-apply-per-same-flow rule bug is fixed.
Carmelo Cascone2308e522017-08-25 02:35:12 +020074 private boolean checkEntryStoreBeforeUpdate = true;
75
Carmelo Casconefe99be92017-09-11 21:55:54 +020076 /*
77 If true, we avoid querying the device and return the content of the ENTRY_STORE.
78 */
Carmelo Casconef2a5ea62017-09-15 01:19:28 +020079 private boolean ignoreDeviceWhenGet = false;
Carmelo Casconefe99be92017-09-11 21:55:54 +020080
Carmelo Cascone7f75be42017-09-07 14:37:02 +020081 /*
82 If true, we read all direct counters of a table with one request. Otherwise, send as many request as the number of
83 table entries.
84 */
85 // TODO: set to true as soon as the feature is implemented in P4Runtime.
86 private boolean readAllDirectCounters = false;
87
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020088 // Needed to synchronize operations over the same table entry.
Andrea Campanella0288c872017-08-07 18:32:51 +020089 private static final ConcurrentMap<P4RuntimeTableEntryReference, Lock> ENTRY_LOCKS = Maps.newConcurrentMap();
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020090
91 // TODO: replace with distributed store.
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +020092 // Can reuse old BMv2TableEntryService from ONOS 1.6
Andrea Campanella0288c872017-08-07 18:32:51 +020093 private static final ConcurrentMap<P4RuntimeTableEntryReference, P4RuntimeFlowRuleWrapper> ENTRY_STORE =
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020094 Maps.newConcurrentMap();
95
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020096 private PiPipelineModel pipelineModel;
97 private PiPipelineInterpreter interpreter;
98 private PiFlowRuleTranslationService piFlowRuleTranslationService;
Frank Wang0e805082017-07-21 14:37:35 +080099
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200100 @Override
101 protected boolean setupBehaviour() {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200102
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200103 if (!super.setupBehaviour()) {
Frank Wang0e805082017-07-21 14:37:35 +0800104 return false;
105 }
106
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200107 if (!device.is(PiPipelineInterpreter.class)) {
108 log.warn("Unable to get interpreter of {}", deviceId);
Frank Wang0e805082017-07-21 14:37:35 +0800109 return false;
110 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200111 interpreter = device.as(PiPipelineInterpreter.class);
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200112 pipelineModel = pipeconf.pipelineModel();
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200113 piFlowRuleTranslationService = handler().get(PiFlowRuleTranslationService.class);
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 Casconefe99be92017-09-11 21:55:54 +0200124 if (ignoreDeviceWhenGet) {
125 return ENTRY_STORE.values().stream()
126 .filter(frWrapper -> frWrapper.rule().deviceId().equals(this.deviceId))
127 .map(frWrapper -> new DefaultFlowEntry(frWrapper.rule(), ADDED, frWrapper.lifeInSeconds(),
128 0, 0))
129 .collect(Collectors.toList());
130 }
131
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200132 ImmutableList.Builder<FlowEntry> resultBuilder = ImmutableList.builder();
133 List<PiTableEntry> inconsistentEntries = Lists.newArrayList();
Frank Wang0e805082017-07-21 14:37:35 +0800134
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200135 for (PiTableModel tableModel : pipelineModel.tables()) {
136
137 PiTableId piTableId = PiTableId.of(tableModel.name());
138
139 // Only dump tables that are exposed by the interpreter.
140 // The reason is that some P4 targets (e.g. BMv2's simple_switch) use more table than those defined in the
141 // P4 program, to implement other capabilities, e.g. action execution in control flow.
142 if (!interpreter.mapPiTableId(piTableId).isPresent()) {
143 continue; // next table
144 }
145
146 Collection<PiTableEntry> installedEntries;
147 try {
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200148 // TODO: optimize by dumping entries and counters in parallel, from ALL tables with the same request.
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200149 installedEntries = client.dumpTable(piTableId, pipeconf).get();
150 } catch (InterruptedException | ExecutionException e) {
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200151 if (!(e.getCause() instanceof StatusRuntimeException)) {
152 // gRPC errors are logged in the client.
153 log.error("Exception while dumping table {} of {}", piTableId, deviceId, e);
154 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200155 return Collections.emptyList();
156 }
157
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200158 Map<PiTableEntry, PiCounterCellData> counterCellMap;
159 try {
160 if (interpreter.mapTableCounter(piTableId).isPresent()) {
161 PiCounterId piCounterId = interpreter.mapTableCounter(piTableId).get();
162 Collection<PiCounterCellData> cellDatas;
163 if (readAllDirectCounters) {
164 cellDatas = client.readAllCounterCells(Collections.singleton(piCounterId), pipeconf).get();
165 } else {
166 Set<PiCounterCellId> cellIds = installedEntries.stream()
167 .map(entry -> PiDirectCounterCellId.of(piCounterId, entry))
168 .collect(Collectors.toSet());
169 cellDatas = client.readCounterCells(cellIds, pipeconf).get();
170 }
171 counterCellMap = cellDatas.stream()
172 .collect(Collectors.toMap(c -> ((PiDirectCounterCellId) c.cellId()).tableEntry(), c -> c));
173 } else {
174 counterCellMap = Collections.emptyMap();
175 }
176 installedEntries = client.dumpTable(piTableId, pipeconf).get();
177 } catch (InterruptedException | ExecutionException e) {
178 if (!(e.getCause() instanceof StatusRuntimeException)) {
179 // gRPC errors are logged in the client.
180 log.error("Exception while reading counters of table {} of {}", piTableId, deviceId, e);
181 }
182 counterCellMap = Collections.emptyMap();
183 }
184
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200185 for (PiTableEntry installedEntry : installedEntries) {
186
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200187 P4RuntimeTableEntryReference entryRef = new P4RuntimeTableEntryReference(deviceId,
188 piTableId,
Carmelo Cascone2308e522017-08-25 02:35:12 +0200189 installedEntry.matchKey());
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200190
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200191 if (!ENTRY_STORE.containsKey(entryRef)) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200192 // Inconsistent entry
193 inconsistentEntries.add(installedEntry);
194 continue; // next one.
195 }
196
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200197 P4RuntimeFlowRuleWrapper frWrapper = ENTRY_STORE.get(entryRef);
198
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200199 long bytes = 0L;
200 long packets = 0L;
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200201 if (counterCellMap.containsKey(installedEntry)) {
202 PiCounterCellData counterCellData = counterCellMap.get(installedEntry);
203 bytes = counterCellData.bytes();
204 packets = counterCellData.packets();
205 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200206
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200207 resultBuilder.add(new DefaultFlowEntry(frWrapper.rule(),
208 ADDED,
209 frWrapper.lifeInSeconds(),
210 packets,
211 bytes));
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200212 }
213 }
214
215 if (inconsistentEntries.size() > 0) {
Carmelo Cascone6f83978d2017-09-28 14:39:54 +0200216 // FIXME: uncomment as soon as we fix the bmv2 pipeline model bug
217 // log.warn("Found {} entries in {} that are not known by table entry service," +
218 // " removing them", inconsistentEntries.size(), deviceId);
219 // inconsistentEntries.forEach(entry -> log.debug(entry.toString()));
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200220 // Async remove them.
Carmelo Cascone6f83978d2017-09-28 14:39:54 +0200221 // client.writeTableEntries(inconsistentEntries, DELETE, pipeconf);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200222 }
223
224 return resultBuilder.build();
Frank Wang0e805082017-07-21 14:37:35 +0800225 }
226
227 @Override
228 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200229 return processFlowRules(rules, APPLY);
Frank Wang0e805082017-07-21 14:37:35 +0800230 }
231
232 @Override
233 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200234 return processFlowRules(rules, REMOVE);
Frank Wang0e805082017-07-21 14:37:35 +0800235 }
236
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200237 private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules, Operation operation) {
Frank Wang0e805082017-07-21 14:37:35 +0800238
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200239 if (!setupBehaviour()) {
Frank Wang0e805082017-07-21 14:37:35 +0800240 return Collections.emptyList();
241 }
242
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200243 ImmutableList.Builder<FlowRule> processedFlowRuleListBuilder = ImmutableList.builder();
244
245 // TODO: send write operations in bulk (e.g. all entries to insert, modify or delete).
246 // Instead of calling the client for each one of them.
247
248 for (FlowRule rule : rules) {
249
250 PiTableEntry piTableEntry;
251
Frank Wang0e805082017-07-21 14:37:35 +0800252 try {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200253 piTableEntry = piFlowRuleTranslationService.translate(rule, pipeconf);
Frank Wang0e805082017-07-21 14:37:35 +0800254 } catch (PiFlowRuleTranslationService.PiFlowRuleTranslationException e) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200255 log.warn("Unable to translate flow rule: {} - {}", e.getMessage(), rule);
256 continue; // next rule
Frank Wang0e805082017-07-21 14:37:35 +0800257 }
Frank Wang0e805082017-07-21 14:37:35 +0800258
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200259 PiTableId tableId = piTableEntry.table();
Andrea Campanella0288c872017-08-07 18:32:51 +0200260 P4RuntimeTableEntryReference entryRef = new P4RuntimeTableEntryReference(deviceId,
Carmelo Cascone2308e522017-08-25 02:35:12 +0200261 tableId, piTableEntry.matchKey());
Frank Wang0e805082017-07-21 14:37:35 +0800262
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200263 Lock lock = ENTRY_LOCKS.computeIfAbsent(entryRef, k -> new ReentrantLock());
264 lock.lock();
Frank Wang0e805082017-07-21 14:37:35 +0800265
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200266 try {
267
Andrea Campanella0288c872017-08-07 18:32:51 +0200268 P4RuntimeFlowRuleWrapper frWrapper = ENTRY_STORE.get(entryRef);
Carmelo Cascone2308e522017-08-25 02:35:12 +0200269 WriteOperationType opType = null;
270 boolean doApply = true;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200271
Andrea Campanella0288c872017-08-07 18:32:51 +0200272 if (operation == APPLY) {
Carmelo Cascone2308e522017-08-25 02:35:12 +0200273 if (frWrapper == null) {
274 // Entry is first-timer.
275 opType = INSERT;
276 } else {
277 // This match key already exists in the device.
278 if (checkEntryStoreBeforeUpdate &&
279 piTableEntry.action().equals(frWrapper.piTableEntry().action())) {
280 doApply = false;
281 log.debug("Ignoring re-apply of existing entry: {}", piTableEntry);
282 }
283 if (doApply) {
284 if (deleteEntryBeforeUpdate) {
285 // We've seen some strange error when trying to modify existing flow rules.
286 // Remove before re-adding the modified one.
287 try {
288 if (client.writeTableEntries(newArrayList(piTableEntry), DELETE, pipeconf).get()) {
289 frWrapper = null;
290 } else {
291 log.warn("Unable to DELETE table entry (before re-adding) in {}: {}",
292 deviceId, piTableEntry);
293 }
294 } catch (InterruptedException | ExecutionException e) {
295 log.warn("Exception while deleting table entry:", operation.name(), e);
296 }
297 opType = INSERT;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200298 } else {
Carmelo Cascone2308e522017-08-25 02:35:12 +0200299 opType = MODIFY;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200300 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200301 }
302 }
303 } else {
304 opType = DELETE;
Frank Wang0e805082017-07-21 14:37:35 +0800305 }
Frank Wang0e805082017-07-21 14:37:35 +0800306
Carmelo Cascone2308e522017-08-25 02:35:12 +0200307 if (doApply) {
308 try {
309 if (client.writeTableEntries(newArrayList(piTableEntry), opType, pipeconf).get()) {
310 processedFlowRuleListBuilder.add(rule);
311 if (operation == APPLY) {
312 frWrapper = new P4RuntimeFlowRuleWrapper(rule, piTableEntry,
313 System.currentTimeMillis());
314 } else {
315 frWrapper = null;
316 }
317 } else {
318 log.warn("Unable to {} table entry in {}: {}", opType.name(), deviceId, piTableEntry);
319 }
320 } catch (InterruptedException | ExecutionException e) {
321 log.warn("Exception while performing {} table entry operation:", operation.name(), e);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200322 }
Carmelo Cascone2308e522017-08-25 02:35:12 +0200323 } else {
324 processedFlowRuleListBuilder.add(rule);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200325 }
326
327 // Update entryRef binding in table entry service.
328 if (frWrapper != null) {
329 ENTRY_STORE.put(entryRef, frWrapper);
330 } else {
331 ENTRY_STORE.remove(entryRef);
332 }
333
334 } finally {
335 lock.unlock();
336 }
337 }
338
339 return processedFlowRuleListBuilder.build();
340 }
341
342 enum Operation {
343 APPLY, REMOVE
Frank Wang0e805082017-07-21 14:37:35 +0800344 }
345}