blob: 515640b1e291f02b86b0e530de1514017f544ed8 [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;
Frank Wang0e805082017-07-21 14:37:35 +080022import org.onosproject.net.flow.DefaultFlowEntry;
23import org.onosproject.net.flow.FlowEntry;
24import org.onosproject.net.flow.FlowRule;
25import org.onosproject.net.flow.FlowRuleProgrammable;
Frank Wang0e805082017-07-21 14:37:35 +080026import org.onosproject.net.pi.model.PiPipelineInterpreter;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020027import org.onosproject.net.pi.model.PiPipelineModel;
28import org.onosproject.net.pi.model.PiTableModel;
Frank Wang0e805082017-07-21 14:37:35 +080029import org.onosproject.net.pi.runtime.PiFlowRuleTranslationService;
Frank Wang0e805082017-07-21 14:37:35 +080030import org.onosproject.net.pi.runtime.PiTableEntry;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020031import org.onosproject.net.pi.runtime.PiTableId;
Frank Wang0e805082017-07-21 14:37:35 +080032import org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType;
Andrea Campanella0288c872017-08-07 18:32:51 +020033import org.onosproject.p4runtime.api.P4RuntimeFlowRuleWrapper;
34import org.onosproject.p4runtime.api.P4RuntimeTableEntryReference;
Frank Wang0e805082017-07-21 14:37:35 +080035
36import java.util.Collection;
37import java.util.Collections;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020038import java.util.List;
39import java.util.concurrent.ConcurrentMap;
40import java.util.concurrent.ExecutionException;
41import java.util.concurrent.locks.Lock;
42import java.util.concurrent.locks.ReentrantLock;
43
44import static com.google.common.collect.Lists.newArrayList;
Andrea Campanella0288c872017-08-07 18:32:51 +020045import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.APPLY;
46import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.REMOVE;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020047import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
Carmelo Cascone2308e522017-08-25 02:35:12 +020048import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.*;
Frank Wang0e805082017-07-21 14:37:35 +080049
50/**
Carmelo Casconee3a7c742017-09-01 01:25:52 +020051 * Implementation of the flow rule programmable behaviour for P4Runtime.
Frank Wang0e805082017-07-21 14:37:35 +080052 */
Carmelo Casconee3a7c742017-09-01 01:25:52 +020053public class P4RuntimeFlowRuleProgrammable extends AbstractP4RuntimeHandlerBehaviour implements FlowRuleProgrammable {
Frank Wang0e805082017-07-21 14:37:35 +080054
Carmelo Cascone2308e522017-08-25 02:35:12 +020055 // TODO: make this attribute configurable by child drivers (e.g. BMv2 or Tofino)
56 /*
57 When updating an existing rule, if true, we issue a DELETE operation before inserting the new one, otherwise we
58 issue a MODIFY operation. This is useful fore devices that do not support MODIFY operations for table entries.
59 */
60 private boolean deleteEntryBeforeUpdate = true;
61
62 // TODO: can remove this check as soon as the multi-apply-per-same-flow rule bug is fixed.
63 /*
64 If true, we ignore re-installing rules that are already known in the ENTRY_STORE, i.e. same match key and action.
65 */
66 private boolean checkEntryStoreBeforeUpdate = true;
67
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020068 // Needed to synchronize operations over the same table entry.
Andrea Campanella0288c872017-08-07 18:32:51 +020069 private static final ConcurrentMap<P4RuntimeTableEntryReference, Lock> ENTRY_LOCKS = Maps.newConcurrentMap();
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020070
71 // TODO: replace with distributed store.
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +020072 // Can reuse old BMv2TableEntryService from ONOS 1.6
Andrea Campanella0288c872017-08-07 18:32:51 +020073 private static final ConcurrentMap<P4RuntimeTableEntryReference, P4RuntimeFlowRuleWrapper> ENTRY_STORE =
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020074 Maps.newConcurrentMap();
75
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020076 private PiPipelineModel pipelineModel;
77 private PiPipelineInterpreter interpreter;
78 private PiFlowRuleTranslationService piFlowRuleTranslationService;
Frank Wang0e805082017-07-21 14:37:35 +080079
Carmelo Casconee3a7c742017-09-01 01:25:52 +020080 @Override
81 protected boolean setupBehaviour() {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020082
Carmelo Casconee3a7c742017-09-01 01:25:52 +020083 if (!super.setupBehaviour()) {
Frank Wang0e805082017-07-21 14:37:35 +080084 return false;
85 }
86
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020087 if (!device.is(PiPipelineInterpreter.class)) {
88 log.warn("Unable to get interpreter of {}", deviceId);
Frank Wang0e805082017-07-21 14:37:35 +080089 return false;
90 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020091 interpreter = device.as(PiPipelineInterpreter.class);
Carmelo Casconee3a7c742017-09-01 01:25:52 +020092 pipelineModel = pipeconf.pipelineModel();
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020093 piFlowRuleTranslationService = handler().get(PiFlowRuleTranslationService.class);
Frank Wang0e805082017-07-21 14:37:35 +080094 return true;
95 }
96
97 @Override
98 public Collection<FlowEntry> getFlowEntries() {
99
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200100 if (!setupBehaviour()) {
Frank Wang0e805082017-07-21 14:37:35 +0800101 return Collections.emptyList();
102 }
103
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200104 ImmutableList.Builder<FlowEntry> resultBuilder = ImmutableList.builder();
105 List<PiTableEntry> inconsistentEntries = Lists.newArrayList();
Frank Wang0e805082017-07-21 14:37:35 +0800106
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200107 for (PiTableModel tableModel : pipelineModel.tables()) {
108
109 PiTableId piTableId = PiTableId.of(tableModel.name());
110
111 // Only dump tables that are exposed by the interpreter.
112 // The reason is that some P4 targets (e.g. BMv2's simple_switch) use more table than those defined in the
113 // P4 program, to implement other capabilities, e.g. action execution in control flow.
114 if (!interpreter.mapPiTableId(piTableId).isPresent()) {
115 continue; // next table
116 }
117
118 Collection<PiTableEntry> installedEntries;
119 try {
120 installedEntries = client.dumpTable(piTableId, pipeconf).get();
121 } catch (InterruptedException | ExecutionException e) {
122 log.error("Exception while dumping table {} of {}", piTableId, deviceId, e);
123 return Collections.emptyList();
124 }
125
126 for (PiTableEntry installedEntry : installedEntries) {
127
Andrea Campanella0288c872017-08-07 18:32:51 +0200128 P4RuntimeTableEntryReference entryRef = new P4RuntimeTableEntryReference(deviceId, piTableId,
Carmelo Cascone2308e522017-08-25 02:35:12 +0200129 installedEntry.matchKey());
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200130
Andrea Campanella0288c872017-08-07 18:32:51 +0200131 P4RuntimeFlowRuleWrapper frWrapper = ENTRY_STORE.get(entryRef);
132
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200133
134 if (frWrapper == null) {
135 // Inconsistent entry
136 inconsistentEntries.add(installedEntry);
137 continue; // next one.
138 }
139
140 // TODO: implement table entry counter retrieval.
141 long bytes = 0L;
142 long packets = 0L;
143
144 FlowEntry entry = new DefaultFlowEntry(frWrapper.rule(), ADDED, frWrapper.lifeInSeconds(),
Carmelo Cascone2308e522017-08-25 02:35:12 +0200145 packets, bytes);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200146 resultBuilder.add(entry);
147 }
148 }
149
150 if (inconsistentEntries.size() > 0) {
151 log.warn("Found {} entries in {} that are not known by table entry service," +
Carmelo Cascone2308e522017-08-25 02:35:12 +0200152 " removing them", inconsistentEntries.size(), deviceId);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200153 inconsistentEntries.forEach(entry -> log.debug(entry.toString()));
154 // Async remove them.
155 client.writeTableEntries(inconsistentEntries, DELETE, pipeconf);
156 }
157
158 return resultBuilder.build();
Frank Wang0e805082017-07-21 14:37:35 +0800159 }
160
161 @Override
162 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200163 return processFlowRules(rules, APPLY);
Frank Wang0e805082017-07-21 14:37:35 +0800164 }
165
166 @Override
167 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200168 return processFlowRules(rules, REMOVE);
Frank Wang0e805082017-07-21 14:37:35 +0800169 }
170
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200171 private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules, Operation operation) {
Frank Wang0e805082017-07-21 14:37:35 +0800172
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200173 if (!setupBehaviour()) {
Frank Wang0e805082017-07-21 14:37:35 +0800174 return Collections.emptyList();
175 }
176
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200177 ImmutableList.Builder<FlowRule> processedFlowRuleListBuilder = ImmutableList.builder();
178
179 // TODO: send write operations in bulk (e.g. all entries to insert, modify or delete).
180 // Instead of calling the client for each one of them.
181
182 for (FlowRule rule : rules) {
183
184 PiTableEntry piTableEntry;
185
Frank Wang0e805082017-07-21 14:37:35 +0800186 try {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200187 piTableEntry = piFlowRuleTranslationService.translate(rule, pipeconf);
Frank Wang0e805082017-07-21 14:37:35 +0800188 } catch (PiFlowRuleTranslationService.PiFlowRuleTranslationException e) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200189 log.warn("Unable to translate flow rule: {} - {}", e.getMessage(), rule);
190 continue; // next rule
Frank Wang0e805082017-07-21 14:37:35 +0800191 }
Frank Wang0e805082017-07-21 14:37:35 +0800192
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200193 PiTableId tableId = piTableEntry.table();
Andrea Campanella0288c872017-08-07 18:32:51 +0200194 P4RuntimeTableEntryReference entryRef = new P4RuntimeTableEntryReference(deviceId,
Carmelo Cascone2308e522017-08-25 02:35:12 +0200195 tableId, piTableEntry.matchKey());
Frank Wang0e805082017-07-21 14:37:35 +0800196
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200197 Lock lock = ENTRY_LOCKS.computeIfAbsent(entryRef, k -> new ReentrantLock());
198 lock.lock();
Frank Wang0e805082017-07-21 14:37:35 +0800199
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200200 try {
201
Andrea Campanella0288c872017-08-07 18:32:51 +0200202 P4RuntimeFlowRuleWrapper frWrapper = ENTRY_STORE.get(entryRef);
Carmelo Cascone2308e522017-08-25 02:35:12 +0200203 WriteOperationType opType = null;
204 boolean doApply = true;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200205
Andrea Campanella0288c872017-08-07 18:32:51 +0200206 if (operation == APPLY) {
Carmelo Cascone2308e522017-08-25 02:35:12 +0200207 if (frWrapper == null) {
208 // Entry is first-timer.
209 opType = INSERT;
210 } else {
211 // This match key already exists in the device.
212 if (checkEntryStoreBeforeUpdate &&
213 piTableEntry.action().equals(frWrapper.piTableEntry().action())) {
214 doApply = false;
215 log.debug("Ignoring re-apply of existing entry: {}", piTableEntry);
216 }
217 if (doApply) {
218 if (deleteEntryBeforeUpdate) {
219 // We've seen some strange error when trying to modify existing flow rules.
220 // Remove before re-adding the modified one.
221 try {
222 if (client.writeTableEntries(newArrayList(piTableEntry), DELETE, pipeconf).get()) {
223 frWrapper = null;
224 } else {
225 log.warn("Unable to DELETE table entry (before re-adding) in {}: {}",
226 deviceId, piTableEntry);
227 }
228 } catch (InterruptedException | ExecutionException e) {
229 log.warn("Exception while deleting table entry:", operation.name(), e);
230 }
231 opType = INSERT;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200232 } else {
Carmelo Cascone2308e522017-08-25 02:35:12 +0200233 opType = MODIFY;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200234 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200235 }
236 }
237 } else {
238 opType = DELETE;
Frank Wang0e805082017-07-21 14:37:35 +0800239 }
Frank Wang0e805082017-07-21 14:37:35 +0800240
Carmelo Cascone2308e522017-08-25 02:35:12 +0200241 if (doApply) {
242 try {
243 if (client.writeTableEntries(newArrayList(piTableEntry), opType, pipeconf).get()) {
244 processedFlowRuleListBuilder.add(rule);
245 if (operation == APPLY) {
246 frWrapper = new P4RuntimeFlowRuleWrapper(rule, piTableEntry,
247 System.currentTimeMillis());
248 } else {
249 frWrapper = null;
250 }
251 } else {
252 log.warn("Unable to {} table entry in {}: {}", opType.name(), deviceId, piTableEntry);
253 }
254 } catch (InterruptedException | ExecutionException e) {
255 log.warn("Exception while performing {} table entry operation:", operation.name(), e);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200256 }
Carmelo Cascone2308e522017-08-25 02:35:12 +0200257 } else {
258 processedFlowRuleListBuilder.add(rule);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200259 }
260
261 // Update entryRef binding in table entry service.
262 if (frWrapper != null) {
263 ENTRY_STORE.put(entryRef, frWrapper);
264 } else {
265 ENTRY_STORE.remove(entryRef);
266 }
267
268 } finally {
269 lock.unlock();
270 }
271 }
272
273 return processedFlowRuleListBuilder.build();
274 }
275
276 enum Operation {
277 APPLY, REMOVE
Frank Wang0e805082017-07-21 14:37:35 +0800278 }
279}