blob: 1a0b755186e235c24483f2d0007cfda5ec4d680e [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;
22import org.onosproject.net.Device;
23import org.onosproject.net.DeviceId;
24import org.onosproject.net.device.DeviceService;
25import org.onosproject.net.driver.AbstractHandlerBehaviour;
26import org.onosproject.net.flow.DefaultFlowEntry;
27import org.onosproject.net.flow.FlowEntry;
28import org.onosproject.net.flow.FlowRule;
29import org.onosproject.net.flow.FlowRuleProgrammable;
30import org.onosproject.net.pi.model.PiPipeconf;
31import org.onosproject.net.pi.model.PiPipelineInterpreter;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020032import org.onosproject.net.pi.model.PiPipelineModel;
33import org.onosproject.net.pi.model.PiTableModel;
Frank Wang0e805082017-07-21 14:37:35 +080034import org.onosproject.net.pi.runtime.PiFlowRuleTranslationService;
35import org.onosproject.net.pi.runtime.PiPipeconfService;
36import org.onosproject.net.pi.runtime.PiTableEntry;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020037import org.onosproject.net.pi.runtime.PiTableId;
Frank Wang0e805082017-07-21 14:37:35 +080038import org.onosproject.p4runtime.api.P4RuntimeClient;
39import org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType;
40import org.onosproject.p4runtime.api.P4RuntimeController;
Andrea Campanella0288c872017-08-07 18:32:51 +020041import org.onosproject.p4runtime.api.P4RuntimeFlowRuleWrapper;
42import org.onosproject.p4runtime.api.P4RuntimeTableEntryReference;
Frank Wang0e805082017-07-21 14:37:35 +080043import org.slf4j.Logger;
44import org.slf4j.LoggerFactory;
45
46import java.util.Collection;
47import java.util.Collections;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020048import java.util.List;
49import java.util.concurrent.ConcurrentMap;
50import java.util.concurrent.ExecutionException;
51import java.util.concurrent.locks.Lock;
52import java.util.concurrent.locks.ReentrantLock;
53
54import static com.google.common.collect.Lists.newArrayList;
Andrea Campanella0288c872017-08-07 18:32:51 +020055import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.APPLY;
56import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.REMOVE;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020057import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
Carmelo Cascone2308e522017-08-25 02:35:12 +020058import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.*;
Frank Wang0e805082017-07-21 14:37:35 +080059
60/**
61 * Implementation of the flow rule programmable behaviour for BMv2.
62 */
Andrea Campanella0288c872017-08-07 18:32:51 +020063public class P4RuntimeFlowRuleProgrammable extends AbstractHandlerBehaviour implements FlowRuleProgrammable {
Frank Wang0e805082017-07-21 14:37:35 +080064
Carmelo Cascone2308e522017-08-25 02:35:12 +020065 // TODO: make this attribute configurable by child drivers (e.g. BMv2 or Tofino)
66 /*
67 When updating an existing rule, if true, we issue a DELETE operation before inserting the new one, otherwise we
68 issue a MODIFY operation. This is useful fore devices that do not support MODIFY operations for table entries.
69 */
70 private boolean deleteEntryBeforeUpdate = true;
71
72 // TODO: can remove this check as soon as the multi-apply-per-same-flow rule bug is fixed.
73 /*
74 If true, we ignore re-installing rules that are already known in the ENTRY_STORE, i.e. same match key and action.
75 */
76 private boolean checkEntryStoreBeforeUpdate = true;
77
Frank Wang0e805082017-07-21 14:37:35 +080078 private final Logger log = LoggerFactory.getLogger(getClass());
Frank Wang0e805082017-07-21 14:37:35 +080079
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020080 // Needed to synchronize operations over the same table entry.
Andrea Campanella0288c872017-08-07 18:32:51 +020081 private static final ConcurrentMap<P4RuntimeTableEntryReference, Lock> ENTRY_LOCKS = Maps.newConcurrentMap();
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020082
83 // TODO: replace with distributed store.
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +020084 // Can reuse old BMv2TableEntryService from ONOS 1.6
Andrea Campanella0288c872017-08-07 18:32:51 +020085 private static final ConcurrentMap<P4RuntimeTableEntryReference, P4RuntimeFlowRuleWrapper> ENTRY_STORE =
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020086 Maps.newConcurrentMap();
87
88 private DeviceId deviceId;
89 private P4RuntimeClient client;
90 private PiPipeconf pipeconf;
91 private PiPipelineModel pipelineModel;
92 private PiPipelineInterpreter interpreter;
93 private PiFlowRuleTranslationService piFlowRuleTranslationService;
Frank Wang0e805082017-07-21 14:37:35 +080094
95 private boolean init() {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020096
Frank Wang0e805082017-07-21 14:37:35 +080097 deviceId = handler().data().deviceId();
98
99 P4RuntimeController controller = handler().get(P4RuntimeController.class);
100 if (!controller.hasClient(deviceId)) {
101 log.warn("Unable to find client for {}, aborting flow rule operation", deviceId);
102 return false;
103 }
104
Frank Wang0e805082017-07-21 14:37:35 +0800105 PiPipeconfService piPipeconfService = handler().get(PiPipeconfService.class);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200106 if (!piPipeconfService.ofDevice(deviceId).isPresent() ||
107 !piPipeconfService.getPipeconf(piPipeconfService.ofDevice(deviceId).get()).isPresent()) {
Frank Wang0e805082017-07-21 14:37:35 +0800108 log.warn("Unable to get the pipeconf of {}", deviceId);
109 return false;
110 }
111
112 DeviceService deviceService = handler().get(DeviceService.class);
113 Device device = deviceService.getDevice(deviceId);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200114 if (!device.is(PiPipelineInterpreter.class)) {
115 log.warn("Unable to get interpreter of {}", deviceId);
Frank Wang0e805082017-07-21 14:37:35 +0800116 return false;
117 }
118
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200119 client = controller.getClient(deviceId);
120 pipeconf = piPipeconfService.getPipeconf(piPipeconfService.ofDevice(deviceId).get()).get();
121 pipelineModel = pipeconf.pipelineModel();
122 interpreter = device.as(PiPipelineInterpreter.class);
123 piFlowRuleTranslationService = handler().get(PiFlowRuleTranslationService.class);
124
Frank Wang0e805082017-07-21 14:37:35 +0800125 return true;
126 }
127
128 @Override
129 public Collection<FlowEntry> getFlowEntries() {
130
131 if (!init()) {
132 return Collections.emptyList();
133 }
134
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200135 ImmutableList.Builder<FlowEntry> resultBuilder = ImmutableList.builder();
136 List<PiTableEntry> inconsistentEntries = Lists.newArrayList();
Frank Wang0e805082017-07-21 14:37:35 +0800137
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200138 for (PiTableModel tableModel : pipelineModel.tables()) {
139
140 PiTableId piTableId = PiTableId.of(tableModel.name());
141
142 // Only dump tables that are exposed by the interpreter.
143 // The reason is that some P4 targets (e.g. BMv2's simple_switch) use more table than those defined in the
144 // P4 program, to implement other capabilities, e.g. action execution in control flow.
145 if (!interpreter.mapPiTableId(piTableId).isPresent()) {
146 continue; // next table
147 }
148
149 Collection<PiTableEntry> installedEntries;
150 try {
151 installedEntries = client.dumpTable(piTableId, pipeconf).get();
152 } catch (InterruptedException | ExecutionException e) {
153 log.error("Exception while dumping table {} of {}", piTableId, deviceId, e);
154 return Collections.emptyList();
155 }
156
157 for (PiTableEntry installedEntry : installedEntries) {
158
Andrea Campanella0288c872017-08-07 18:32:51 +0200159 P4RuntimeTableEntryReference entryRef = new P4RuntimeTableEntryReference(deviceId, piTableId,
Carmelo Cascone2308e522017-08-25 02:35:12 +0200160 installedEntry.matchKey());
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200161
Andrea Campanella0288c872017-08-07 18:32:51 +0200162 P4RuntimeFlowRuleWrapper frWrapper = ENTRY_STORE.get(entryRef);
163
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200164
165 if (frWrapper == null) {
166 // Inconsistent entry
167 inconsistentEntries.add(installedEntry);
168 continue; // next one.
169 }
170
171 // TODO: implement table entry counter retrieval.
172 long bytes = 0L;
173 long packets = 0L;
174
175 FlowEntry entry = new DefaultFlowEntry(frWrapper.rule(), ADDED, frWrapper.lifeInSeconds(),
Carmelo Cascone2308e522017-08-25 02:35:12 +0200176 packets, bytes);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200177 resultBuilder.add(entry);
178 }
179 }
180
181 if (inconsistentEntries.size() > 0) {
182 log.warn("Found {} entries in {} that are not known by table entry service," +
Carmelo Cascone2308e522017-08-25 02:35:12 +0200183 " removing them", inconsistentEntries.size(), deviceId);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200184 inconsistentEntries.forEach(entry -> log.debug(entry.toString()));
185 // Async remove them.
186 client.writeTableEntries(inconsistentEntries, DELETE, pipeconf);
187 }
188
189 return resultBuilder.build();
Frank Wang0e805082017-07-21 14:37:35 +0800190 }
191
192 @Override
193 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200194 return processFlowRules(rules, APPLY);
Frank Wang0e805082017-07-21 14:37:35 +0800195 }
196
197 @Override
198 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200199 return processFlowRules(rules, REMOVE);
Frank Wang0e805082017-07-21 14:37:35 +0800200 }
201
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200202 private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules, Operation operation) {
Frank Wang0e805082017-07-21 14:37:35 +0800203
204 if (!init()) {
205 return Collections.emptyList();
206 }
207
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200208 ImmutableList.Builder<FlowRule> processedFlowRuleListBuilder = ImmutableList.builder();
209
210 // TODO: send write operations in bulk (e.g. all entries to insert, modify or delete).
211 // Instead of calling the client for each one of them.
212
213 for (FlowRule rule : rules) {
214
215 PiTableEntry piTableEntry;
216
Frank Wang0e805082017-07-21 14:37:35 +0800217 try {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200218 piTableEntry = piFlowRuleTranslationService.translate(rule, pipeconf);
Frank Wang0e805082017-07-21 14:37:35 +0800219 } catch (PiFlowRuleTranslationService.PiFlowRuleTranslationException e) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200220 log.warn("Unable to translate flow rule: {} - {}", e.getMessage(), rule);
221 continue; // next rule
Frank Wang0e805082017-07-21 14:37:35 +0800222 }
Frank Wang0e805082017-07-21 14:37:35 +0800223
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200224 PiTableId tableId = piTableEntry.table();
Andrea Campanella0288c872017-08-07 18:32:51 +0200225 P4RuntimeTableEntryReference entryRef = new P4RuntimeTableEntryReference(deviceId,
Carmelo Cascone2308e522017-08-25 02:35:12 +0200226 tableId, piTableEntry.matchKey());
Frank Wang0e805082017-07-21 14:37:35 +0800227
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200228 Lock lock = ENTRY_LOCKS.computeIfAbsent(entryRef, k -> new ReentrantLock());
229 lock.lock();
Frank Wang0e805082017-07-21 14:37:35 +0800230
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200231 try {
232
Andrea Campanella0288c872017-08-07 18:32:51 +0200233 P4RuntimeFlowRuleWrapper frWrapper = ENTRY_STORE.get(entryRef);
Carmelo Cascone2308e522017-08-25 02:35:12 +0200234 WriteOperationType opType = null;
235 boolean doApply = true;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200236
Andrea Campanella0288c872017-08-07 18:32:51 +0200237 if (operation == APPLY) {
Carmelo Cascone2308e522017-08-25 02:35:12 +0200238 if (frWrapper == null) {
239 // Entry is first-timer.
240 opType = INSERT;
241 } else {
242 // This match key already exists in the device.
243 if (checkEntryStoreBeforeUpdate &&
244 piTableEntry.action().equals(frWrapper.piTableEntry().action())) {
245 doApply = false;
246 log.debug("Ignoring re-apply of existing entry: {}", piTableEntry);
247 }
248 if (doApply) {
249 if (deleteEntryBeforeUpdate) {
250 // We've seen some strange error when trying to modify existing flow rules.
251 // Remove before re-adding the modified one.
252 try {
253 if (client.writeTableEntries(newArrayList(piTableEntry), DELETE, pipeconf).get()) {
254 frWrapper = null;
255 } else {
256 log.warn("Unable to DELETE table entry (before re-adding) in {}: {}",
257 deviceId, piTableEntry);
258 }
259 } catch (InterruptedException | ExecutionException e) {
260 log.warn("Exception while deleting table entry:", operation.name(), e);
261 }
262 opType = INSERT;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200263 } else {
Carmelo Cascone2308e522017-08-25 02:35:12 +0200264 opType = MODIFY;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200265 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200266 }
267 }
268 } else {
269 opType = DELETE;
Frank Wang0e805082017-07-21 14:37:35 +0800270 }
Frank Wang0e805082017-07-21 14:37:35 +0800271
Carmelo Cascone2308e522017-08-25 02:35:12 +0200272 if (doApply) {
273 try {
274 if (client.writeTableEntries(newArrayList(piTableEntry), opType, pipeconf).get()) {
275 processedFlowRuleListBuilder.add(rule);
276 if (operation == APPLY) {
277 frWrapper = new P4RuntimeFlowRuleWrapper(rule, piTableEntry,
278 System.currentTimeMillis());
279 } else {
280 frWrapper = null;
281 }
282 } else {
283 log.warn("Unable to {} table entry in {}: {}", opType.name(), deviceId, piTableEntry);
284 }
285 } catch (InterruptedException | ExecutionException e) {
286 log.warn("Exception while performing {} table entry operation:", operation.name(), e);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200287 }
Carmelo Cascone2308e522017-08-25 02:35:12 +0200288 } else {
289 processedFlowRuleListBuilder.add(rule);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200290 }
291
292 // Update entryRef binding in table entry service.
293 if (frWrapper != null) {
294 ENTRY_STORE.put(entryRef, frWrapper);
295 } else {
296 ENTRY_STORE.remove(entryRef);
297 }
298
299 } finally {
300 lock.unlock();
301 }
302 }
303
304 return processedFlowRuleListBuilder.build();
305 }
306
307 enum Operation {
308 APPLY, REMOVE
Frank Wang0e805082017-07-21 14:37:35 +0800309 }
310}