blob: ad9329ab0915a7c884b7488855ca961a3f3fac4a [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;
Carmelo Casconefe99be92017-09-11 21:55:54 +020043import java.util.stream.Collectors;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020044
45import static com.google.common.collect.Lists.newArrayList;
Andrea Campanella0288c872017-08-07 18:32:51 +020046import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.APPLY;
47import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.REMOVE;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020048import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
Carmelo Cascone2308e522017-08-25 02:35:12 +020049import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.*;
Frank Wang0e805082017-07-21 14:37:35 +080050
51/**
Carmelo Casconee3a7c742017-09-01 01:25:52 +020052 * Implementation of the flow rule programmable behaviour for P4Runtime.
Frank Wang0e805082017-07-21 14:37:35 +080053 */
Carmelo Casconee3a7c742017-09-01 01:25:52 +020054public class P4RuntimeFlowRuleProgrammable extends AbstractP4RuntimeHandlerBehaviour implements FlowRuleProgrammable {
Frank Wang0e805082017-07-21 14:37:35 +080055
Carmelo Cascone2308e522017-08-25 02:35:12 +020056 /*
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 */
Carmelo Casconefe99be92017-09-11 21:55:54 +020060 // TODO: make this attribute configurable by child drivers (e.g. BMv2 or Tofino)
Carmelo Cascone2308e522017-08-25 02:35:12 +020061 private boolean deleteEntryBeforeUpdate = true;
62
Carmelo Cascone2308e522017-08-25 02:35:12 +020063 /*
64 If true, we ignore re-installing rules that are already known in the ENTRY_STORE, i.e. same match key and action.
65 */
Carmelo Casconefe99be92017-09-11 21:55:54 +020066 // 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 +020067 private boolean checkEntryStoreBeforeUpdate = true;
68
Carmelo Casconefe99be92017-09-11 21:55:54 +020069 /*
70 If true, we avoid querying the device and return the content of the ENTRY_STORE.
71 */
72 // TODO: can remove this check as soon as the BMv2 bug when reading ECMP entries is fixed.
73 private boolean ignoreDeviceWhenGet = true;
74
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020075 // Needed to synchronize operations over the same table entry.
Andrea Campanella0288c872017-08-07 18:32:51 +020076 private static final ConcurrentMap<P4RuntimeTableEntryReference, Lock> ENTRY_LOCKS = Maps.newConcurrentMap();
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020077
78 // TODO: replace with distributed store.
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +020079 // Can reuse old BMv2TableEntryService from ONOS 1.6
Andrea Campanella0288c872017-08-07 18:32:51 +020080 private static final ConcurrentMap<P4RuntimeTableEntryReference, P4RuntimeFlowRuleWrapper> ENTRY_STORE =
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020081 Maps.newConcurrentMap();
82
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020083 private PiPipelineModel pipelineModel;
84 private PiPipelineInterpreter interpreter;
85 private PiFlowRuleTranslationService piFlowRuleTranslationService;
Frank Wang0e805082017-07-21 14:37:35 +080086
Carmelo Casconee3a7c742017-09-01 01:25:52 +020087 @Override
88 protected boolean setupBehaviour() {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020089
Carmelo Casconee3a7c742017-09-01 01:25:52 +020090 if (!super.setupBehaviour()) {
Frank Wang0e805082017-07-21 14:37:35 +080091 return false;
92 }
93
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020094 if (!device.is(PiPipelineInterpreter.class)) {
95 log.warn("Unable to get interpreter of {}", deviceId);
Frank Wang0e805082017-07-21 14:37:35 +080096 return false;
97 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020098 interpreter = device.as(PiPipelineInterpreter.class);
Carmelo Casconee3a7c742017-09-01 01:25:52 +020099 pipelineModel = pipeconf.pipelineModel();
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200100 piFlowRuleTranslationService = handler().get(PiFlowRuleTranslationService.class);
Frank Wang0e805082017-07-21 14:37:35 +0800101 return true;
102 }
103
104 @Override
105 public Collection<FlowEntry> getFlowEntries() {
106
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200107 if (!setupBehaviour()) {
Frank Wang0e805082017-07-21 14:37:35 +0800108 return Collections.emptyList();
109 }
110
Carmelo Casconefe99be92017-09-11 21:55:54 +0200111 if (ignoreDeviceWhenGet) {
112 return ENTRY_STORE.values().stream()
113 .filter(frWrapper -> frWrapper.rule().deviceId().equals(this.deviceId))
114 .map(frWrapper -> new DefaultFlowEntry(frWrapper.rule(), ADDED, frWrapper.lifeInSeconds(),
115 0, 0))
116 .collect(Collectors.toList());
117 }
118
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200119 ImmutableList.Builder<FlowEntry> resultBuilder = ImmutableList.builder();
120 List<PiTableEntry> inconsistentEntries = Lists.newArrayList();
Frank Wang0e805082017-07-21 14:37:35 +0800121
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200122 for (PiTableModel tableModel : pipelineModel.tables()) {
123
124 PiTableId piTableId = PiTableId.of(tableModel.name());
125
126 // Only dump tables that are exposed by the interpreter.
127 // The reason is that some P4 targets (e.g. BMv2's simple_switch) use more table than those defined in the
128 // P4 program, to implement other capabilities, e.g. action execution in control flow.
129 if (!interpreter.mapPiTableId(piTableId).isPresent()) {
130 continue; // next table
131 }
132
133 Collection<PiTableEntry> installedEntries;
134 try {
135 installedEntries = client.dumpTable(piTableId, pipeconf).get();
136 } catch (InterruptedException | ExecutionException e) {
137 log.error("Exception while dumping table {} of {}", piTableId, deviceId, e);
138 return Collections.emptyList();
139 }
140
141 for (PiTableEntry installedEntry : installedEntries) {
142
Andrea Campanella0288c872017-08-07 18:32:51 +0200143 P4RuntimeTableEntryReference entryRef = new P4RuntimeTableEntryReference(deviceId, piTableId,
Carmelo Cascone2308e522017-08-25 02:35:12 +0200144 installedEntry.matchKey());
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200145
Andrea Campanella0288c872017-08-07 18:32:51 +0200146 P4RuntimeFlowRuleWrapper frWrapper = ENTRY_STORE.get(entryRef);
147
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200148
149 if (frWrapper == null) {
150 // Inconsistent entry
151 inconsistentEntries.add(installedEntry);
152 continue; // next one.
153 }
154
155 // TODO: implement table entry counter retrieval.
156 long bytes = 0L;
157 long packets = 0L;
158
159 FlowEntry entry = new DefaultFlowEntry(frWrapper.rule(), ADDED, frWrapper.lifeInSeconds(),
Carmelo Cascone2308e522017-08-25 02:35:12 +0200160 packets, bytes);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200161 resultBuilder.add(entry);
162 }
163 }
164
165 if (inconsistentEntries.size() > 0) {
166 log.warn("Found {} entries in {} that are not known by table entry service," +
Carmelo Cascone2308e522017-08-25 02:35:12 +0200167 " removing them", inconsistentEntries.size(), deviceId);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200168 inconsistentEntries.forEach(entry -> log.debug(entry.toString()));
169 // Async remove them.
170 client.writeTableEntries(inconsistentEntries, DELETE, pipeconf);
171 }
172
173 return resultBuilder.build();
Frank Wang0e805082017-07-21 14:37:35 +0800174 }
175
176 @Override
177 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200178 return processFlowRules(rules, APPLY);
Frank Wang0e805082017-07-21 14:37:35 +0800179 }
180
181 @Override
182 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200183 return processFlowRules(rules, REMOVE);
Frank Wang0e805082017-07-21 14:37:35 +0800184 }
185
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200186 private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules, Operation operation) {
Frank Wang0e805082017-07-21 14:37:35 +0800187
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200188 if (!setupBehaviour()) {
Frank Wang0e805082017-07-21 14:37:35 +0800189 return Collections.emptyList();
190 }
191
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200192 ImmutableList.Builder<FlowRule> processedFlowRuleListBuilder = ImmutableList.builder();
193
194 // TODO: send write operations in bulk (e.g. all entries to insert, modify or delete).
195 // Instead of calling the client for each one of them.
196
197 for (FlowRule rule : rules) {
198
199 PiTableEntry piTableEntry;
200
Frank Wang0e805082017-07-21 14:37:35 +0800201 try {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200202 piTableEntry = piFlowRuleTranslationService.translate(rule, pipeconf);
Frank Wang0e805082017-07-21 14:37:35 +0800203 } catch (PiFlowRuleTranslationService.PiFlowRuleTranslationException e) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200204 log.warn("Unable to translate flow rule: {} - {}", e.getMessage(), rule);
205 continue; // next rule
Frank Wang0e805082017-07-21 14:37:35 +0800206 }
Frank Wang0e805082017-07-21 14:37:35 +0800207
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200208 PiTableId tableId = piTableEntry.table();
Andrea Campanella0288c872017-08-07 18:32:51 +0200209 P4RuntimeTableEntryReference entryRef = new P4RuntimeTableEntryReference(deviceId,
Carmelo Cascone2308e522017-08-25 02:35:12 +0200210 tableId, piTableEntry.matchKey());
Frank Wang0e805082017-07-21 14:37:35 +0800211
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200212 Lock lock = ENTRY_LOCKS.computeIfAbsent(entryRef, k -> new ReentrantLock());
213 lock.lock();
Frank Wang0e805082017-07-21 14:37:35 +0800214
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200215 try {
216
Andrea Campanella0288c872017-08-07 18:32:51 +0200217 P4RuntimeFlowRuleWrapper frWrapper = ENTRY_STORE.get(entryRef);
Carmelo Cascone2308e522017-08-25 02:35:12 +0200218 WriteOperationType opType = null;
219 boolean doApply = true;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200220
Andrea Campanella0288c872017-08-07 18:32:51 +0200221 if (operation == APPLY) {
Carmelo Cascone2308e522017-08-25 02:35:12 +0200222 if (frWrapper == null) {
223 // Entry is first-timer.
224 opType = INSERT;
225 } else {
226 // This match key already exists in the device.
227 if (checkEntryStoreBeforeUpdate &&
228 piTableEntry.action().equals(frWrapper.piTableEntry().action())) {
229 doApply = false;
230 log.debug("Ignoring re-apply of existing entry: {}", piTableEntry);
231 }
232 if (doApply) {
233 if (deleteEntryBeforeUpdate) {
234 // We've seen some strange error when trying to modify existing flow rules.
235 // Remove before re-adding the modified one.
236 try {
237 if (client.writeTableEntries(newArrayList(piTableEntry), DELETE, pipeconf).get()) {
238 frWrapper = null;
239 } else {
240 log.warn("Unable to DELETE table entry (before re-adding) in {}: {}",
241 deviceId, piTableEntry);
242 }
243 } catch (InterruptedException | ExecutionException e) {
244 log.warn("Exception while deleting table entry:", operation.name(), e);
245 }
246 opType = INSERT;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200247 } else {
Carmelo Cascone2308e522017-08-25 02:35:12 +0200248 opType = MODIFY;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200249 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200250 }
251 }
252 } else {
253 opType = DELETE;
Frank Wang0e805082017-07-21 14:37:35 +0800254 }
Frank Wang0e805082017-07-21 14:37:35 +0800255
Carmelo Cascone2308e522017-08-25 02:35:12 +0200256 if (doApply) {
257 try {
258 if (client.writeTableEntries(newArrayList(piTableEntry), opType, pipeconf).get()) {
259 processedFlowRuleListBuilder.add(rule);
260 if (operation == APPLY) {
261 frWrapper = new P4RuntimeFlowRuleWrapper(rule, piTableEntry,
262 System.currentTimeMillis());
263 } else {
264 frWrapper = null;
265 }
266 } else {
267 log.warn("Unable to {} table entry in {}: {}", opType.name(), deviceId, piTableEntry);
268 }
269 } catch (InterruptedException | ExecutionException e) {
270 log.warn("Exception while performing {} table entry operation:", operation.name(), e);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200271 }
Carmelo Cascone2308e522017-08-25 02:35:12 +0200272 } else {
273 processedFlowRuleListBuilder.add(rule);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200274 }
275
276 // Update entryRef binding in table entry service.
277 if (frWrapper != null) {
278 ENTRY_STORE.put(entryRef, frWrapper);
279 } else {
280 ENTRY_STORE.remove(entryRef);
281 }
282
283 } finally {
284 lock.unlock();
285 }
286 }
287
288 return processedFlowRuleListBuilder.build();
289 }
290
291 enum Operation {
292 APPLY, REMOVE
Frank Wang0e805082017-07-21 14:37:35 +0800293 }
294}