blob: 053dd7c98d40f01f81a374172cb18b72f737d1f7 [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;
Carmelo Cascone87892e22017-11-13 16:01:29 -080027import org.onosproject.net.pi.model.PiCounterId;
Frank Wang0e805082017-07-21 14:37:35 +080028import org.onosproject.net.pi.model.PiPipelineInterpreter;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020029import org.onosproject.net.pi.model.PiPipelineModel;
Carmelo Cascone87892e22017-11-13 16:01:29 -080030import org.onosproject.net.pi.model.PiTableId;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020031import org.onosproject.net.pi.model.PiTableModel;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020032import org.onosproject.net.pi.runtime.PiCounterCellData;
33import org.onosproject.net.pi.runtime.PiCounterCellId;
Frank Wang0e805082017-07-21 14:37:35 +080034import org.onosproject.net.pi.runtime.PiTableEntry;
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080035import org.onosproject.net.pi.service.PiTranslationException;
Frank Wang0e805082017-07-21 14:37:35 +080036import org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType;
Andrea Campanella0288c872017-08-07 18:32:51 +020037import org.onosproject.p4runtime.api.P4RuntimeFlowRuleWrapper;
38import org.onosproject.p4runtime.api.P4RuntimeTableEntryReference;
Frank Wang0e805082017-07-21 14:37:35 +080039
40import java.util.Collection;
41import java.util.Collections;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020042import java.util.List;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020043import java.util.Map;
44import java.util.Set;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020045import java.util.concurrent.ConcurrentMap;
46import java.util.concurrent.ExecutionException;
47import java.util.concurrent.locks.Lock;
48import java.util.concurrent.locks.ReentrantLock;
Carmelo Casconefe99be92017-09-11 21:55:54 +020049import java.util.stream.Collectors;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020050
51import static com.google.common.collect.Lists.newArrayList;
Andrea Campanella0288c872017-08-07 18:32:51 +020052import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.APPLY;
53import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.REMOVE;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020054import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
Carmelo Cascone87892e22017-11-13 16:01:29 -080055import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.DELETE;
56import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.INSERT;
57import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.MODIFY;
Frank Wang0e805082017-07-21 14:37:35 +080058
59/**
Carmelo Casconee3a7c742017-09-01 01:25:52 +020060 * Implementation of the flow rule programmable behaviour for P4Runtime.
Frank Wang0e805082017-07-21 14:37:35 +080061 */
Carmelo Casconee3a7c742017-09-01 01:25:52 +020062public class P4RuntimeFlowRuleProgrammable extends AbstractP4RuntimeHandlerBehaviour implements FlowRuleProgrammable {
Frank Wang0e805082017-07-21 14:37:35 +080063
Carmelo Cascone2308e522017-08-25 02:35:12 +020064 /*
65 When updating an existing rule, if true, we issue a DELETE operation before inserting the new one, otherwise we
66 issue a MODIFY operation. This is useful fore devices that do not support MODIFY operations for table entries.
67 */
Carmelo Casconefe99be92017-09-11 21:55:54 +020068 // TODO: make this attribute configurable by child drivers (e.g. BMv2 or Tofino)
Carmelo Cascone2308e522017-08-25 02:35:12 +020069 private boolean deleteEntryBeforeUpdate = true;
70
Carmelo Cascone2308e522017-08-25 02:35:12 +020071 /*
72 If true, we ignore re-installing rules that are already known in the ENTRY_STORE, i.e. same match key and action.
73 */
Carmelo Casconefe99be92017-09-11 21:55:54 +020074 // 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 +020075 private boolean checkEntryStoreBeforeUpdate = true;
76
Carmelo Casconefe99be92017-09-11 21:55:54 +020077 /*
78 If true, we avoid querying the device and return the content of the ENTRY_STORE.
79 */
Yi Tseng63a82df2017-11-17 16:41:40 -080080 // TODO: set to false after bmv2/PI bug fixed
81 private boolean ignoreDeviceWhenGet = true;
Carmelo Casconefe99be92017-09-11 21:55:54 +020082
Carmelo Cascone7f75be42017-09-07 14:37:02 +020083 /*
84 If true, we read all direct counters of a table with one request. Otherwise, send as many request as the number of
85 table entries.
86 */
87 // TODO: set to true as soon as the feature is implemented in P4Runtime.
88 private boolean readAllDirectCounters = false;
89
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020090 // Needed to synchronize operations over the same table entry.
Andrea Campanella0288c872017-08-07 18:32:51 +020091 private static final ConcurrentMap<P4RuntimeTableEntryReference, Lock> ENTRY_LOCKS = Maps.newConcurrentMap();
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020092
93 // TODO: replace with distributed store.
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +020094 // Can reuse old BMv2TableEntryService from ONOS 1.6
Andrea Campanella0288c872017-08-07 18:32:51 +020095 private static final ConcurrentMap<P4RuntimeTableEntryReference, P4RuntimeFlowRuleWrapper> ENTRY_STORE =
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020096 Maps.newConcurrentMap();
97
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020098 private PiPipelineModel pipelineModel;
99 private PiPipelineInterpreter interpreter;
Frank Wang0e805082017-07-21 14:37:35 +0800100
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200101 @Override
102 protected boolean setupBehaviour() {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200103
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200104 if (!super.setupBehaviour()) {
Frank Wang0e805082017-07-21 14:37:35 +0800105 return false;
106 }
107
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200108 if (!device.is(PiPipelineInterpreter.class)) {
109 log.warn("Unable to get interpreter of {}", deviceId);
Frank Wang0e805082017-07-21 14:37:35 +0800110 return false;
111 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200112 interpreter = device.as(PiPipelineInterpreter.class);
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200113 pipelineModel = pipeconf.pipelineModel();
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
Carmelo Cascone87892e22017-11-13 16:01:29 -0800137 PiTableId piTableId = tableModel.id();
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200138
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200139 Collection<PiTableEntry> installedEntries;
140 try {
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200141 // TODO: optimize by dumping entries and counters in parallel, from ALL tables with the same request.
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200142 installedEntries = client.dumpTable(piTableId, pipeconf).get();
143 } catch (InterruptedException | ExecutionException e) {
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200144 if (!(e.getCause() instanceof StatusRuntimeException)) {
145 // gRPC errors are logged in the client.
146 log.error("Exception while dumping table {} of {}", piTableId, deviceId, e);
147 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200148 return Collections.emptyList();
149 }
150
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200151 Map<PiTableEntry, PiCounterCellData> counterCellMap;
152 try {
153 if (interpreter.mapTableCounter(piTableId).isPresent()) {
154 PiCounterId piCounterId = interpreter.mapTableCounter(piTableId).get();
155 Collection<PiCounterCellData> cellDatas;
156 if (readAllDirectCounters) {
157 cellDatas = client.readAllCounterCells(Collections.singleton(piCounterId), pipeconf).get();
158 } else {
159 Set<PiCounterCellId> cellIds = installedEntries.stream()
Carmelo Cascone87892e22017-11-13 16:01:29 -0800160 .map(entry -> PiCounterCellId.ofDirect(piCounterId, entry))
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200161 .collect(Collectors.toSet());
162 cellDatas = client.readCounterCells(cellIds, pipeconf).get();
163 }
164 counterCellMap = cellDatas.stream()
Carmelo Cascone87892e22017-11-13 16:01:29 -0800165 .collect(Collectors.toMap(c -> (c.cellId()).tableEntry(), c -> c));
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200166 } else {
167 counterCellMap = Collections.emptyMap();
168 }
169 installedEntries = client.dumpTable(piTableId, pipeconf).get();
170 } catch (InterruptedException | ExecutionException e) {
171 if (!(e.getCause() instanceof StatusRuntimeException)) {
172 // gRPC errors are logged in the client.
173 log.error("Exception while reading counters of table {} of {}", piTableId, deviceId, e);
174 }
175 counterCellMap = Collections.emptyMap();
176 }
177
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200178 for (PiTableEntry installedEntry : installedEntries) {
179
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200180 P4RuntimeTableEntryReference entryRef = new P4RuntimeTableEntryReference(deviceId,
181 piTableId,
Carmelo Cascone2308e522017-08-25 02:35:12 +0200182 installedEntry.matchKey());
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200183
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200184 if (!ENTRY_STORE.containsKey(entryRef)) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200185 // Inconsistent entry
186 inconsistentEntries.add(installedEntry);
187 continue; // next one.
188 }
189
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200190 P4RuntimeFlowRuleWrapper frWrapper = ENTRY_STORE.get(entryRef);
191
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200192 long bytes = 0L;
193 long packets = 0L;
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200194 if (counterCellMap.containsKey(installedEntry)) {
195 PiCounterCellData counterCellData = counterCellMap.get(installedEntry);
196 bytes = counterCellData.bytes();
197 packets = counterCellData.packets();
198 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200199
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200200 resultBuilder.add(new DefaultFlowEntry(frWrapper.rule(),
201 ADDED,
202 frWrapper.lifeInSeconds(),
203 packets,
204 bytes));
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200205 }
206 }
207
208 if (inconsistentEntries.size() > 0) {
209 log.warn("Found {} entries in {} that are not known by table entry service," +
Carmelo Cascone2308e522017-08-25 02:35:12 +0200210 " removing them", inconsistentEntries.size(), deviceId);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200211 inconsistentEntries.forEach(entry -> log.debug(entry.toString()));
212 // Async remove them.
213 client.writeTableEntries(inconsistentEntries, DELETE, pipeconf);
214 }
215
216 return resultBuilder.build();
Frank Wang0e805082017-07-21 14:37:35 +0800217 }
218
219 @Override
220 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200221 return processFlowRules(rules, APPLY);
Frank Wang0e805082017-07-21 14:37:35 +0800222 }
223
224 @Override
225 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200226 return processFlowRules(rules, REMOVE);
Frank Wang0e805082017-07-21 14:37:35 +0800227 }
228
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200229 private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules, Operation operation) {
Frank Wang0e805082017-07-21 14:37:35 +0800230
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200231 if (!setupBehaviour()) {
Frank Wang0e805082017-07-21 14:37:35 +0800232 return Collections.emptyList();
233 }
234
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200235 ImmutableList.Builder<FlowRule> processedFlowRuleListBuilder = ImmutableList.builder();
236
237 // TODO: send write operations in bulk (e.g. all entries to insert, modify or delete).
238 // Instead of calling the client for each one of them.
239
240 for (FlowRule rule : rules) {
241
242 PiTableEntry piTableEntry;
243
Frank Wang0e805082017-07-21 14:37:35 +0800244 try {
Carmelo Cascone326ad2d2017-11-28 18:09:13 -0800245 piTableEntry = piTranslationService.flowRuleTranslator().translate(rule, pipeconf);
246 } catch (PiTranslationException e) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200247 log.warn("Unable to translate flow rule: {} - {}", e.getMessage(), rule);
248 continue; // next rule
Frank Wang0e805082017-07-21 14:37:35 +0800249 }
Frank Wang0e805082017-07-21 14:37:35 +0800250
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200251 PiTableId tableId = piTableEntry.table();
Andrea Campanella0288c872017-08-07 18:32:51 +0200252 P4RuntimeTableEntryReference entryRef = new P4RuntimeTableEntryReference(deviceId,
Carmelo Cascone2308e522017-08-25 02:35:12 +0200253 tableId, piTableEntry.matchKey());
Frank Wang0e805082017-07-21 14:37:35 +0800254
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200255 Lock lock = ENTRY_LOCKS.computeIfAbsent(entryRef, k -> new ReentrantLock());
256 lock.lock();
Frank Wang0e805082017-07-21 14:37:35 +0800257
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200258 try {
259
Andrea Campanella0288c872017-08-07 18:32:51 +0200260 P4RuntimeFlowRuleWrapper frWrapper = ENTRY_STORE.get(entryRef);
Carmelo Cascone2308e522017-08-25 02:35:12 +0200261 WriteOperationType opType = null;
262 boolean doApply = true;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200263
Andrea Campanella0288c872017-08-07 18:32:51 +0200264 if (operation == APPLY) {
Carmelo Cascone2308e522017-08-25 02:35:12 +0200265 if (frWrapper == null) {
266 // Entry is first-timer.
267 opType = INSERT;
268 } else {
269 // This match key already exists in the device.
270 if (checkEntryStoreBeforeUpdate &&
271 piTableEntry.action().equals(frWrapper.piTableEntry().action())) {
272 doApply = false;
273 log.debug("Ignoring re-apply of existing entry: {}", piTableEntry);
274 }
275 if (doApply) {
276 if (deleteEntryBeforeUpdate) {
277 // We've seen some strange error when trying to modify existing flow rules.
278 // Remove before re-adding the modified one.
279 try {
280 if (client.writeTableEntries(newArrayList(piTableEntry), DELETE, pipeconf).get()) {
281 frWrapper = null;
282 } else {
283 log.warn("Unable to DELETE table entry (before re-adding) in {}: {}",
284 deviceId, piTableEntry);
285 }
286 } catch (InterruptedException | ExecutionException e) {
287 log.warn("Exception while deleting table entry:", operation.name(), e);
288 }
289 opType = INSERT;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200290 } else {
Carmelo Cascone2308e522017-08-25 02:35:12 +0200291 opType = MODIFY;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200292 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200293 }
294 }
295 } else {
296 opType = DELETE;
Frank Wang0e805082017-07-21 14:37:35 +0800297 }
Frank Wang0e805082017-07-21 14:37:35 +0800298
Carmelo Cascone2308e522017-08-25 02:35:12 +0200299 if (doApply) {
300 try {
301 if (client.writeTableEntries(newArrayList(piTableEntry), opType, pipeconf).get()) {
302 processedFlowRuleListBuilder.add(rule);
303 if (operation == APPLY) {
304 frWrapper = new P4RuntimeFlowRuleWrapper(rule, piTableEntry,
305 System.currentTimeMillis());
306 } else {
307 frWrapper = null;
308 }
309 } else {
310 log.warn("Unable to {} table entry in {}: {}", opType.name(), deviceId, piTableEntry);
311 }
312 } catch (InterruptedException | ExecutionException e) {
313 log.warn("Exception while performing {} table entry operation:", operation.name(), e);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200314 }
Carmelo Cascone2308e522017-08-25 02:35:12 +0200315 } else {
316 processedFlowRuleListBuilder.add(rule);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200317 }
318
319 // Update entryRef binding in table entry service.
320 if (frWrapper != null) {
321 ENTRY_STORE.put(entryRef, frWrapper);
322 } else {
323 ENTRY_STORE.remove(entryRef);
324 }
325
326 } finally {
327 lock.unlock();
328 }
329 }
330
331 return processedFlowRuleListBuilder.build();
332 }
333
334 enum Operation {
335 APPLY, REMOVE
Frank Wang0e805082017-07-21 14:37:35 +0800336 }
Carmelo Cascone87892e22017-11-13 16:01:29 -0800337}