blob: 6c445f3749fcf9f706738be0c8c4dc88e4ea7e75 [file] [log] [blame]
Frank Wang0e805082017-07-21 14:37:35 +08001/*
2 * Copyright 2017-present Open Networking Laboratory
3 *
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
17package org.onosproject.drivers.bmv2;
18
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;
41import org.slf4j.Logger;
42import org.slf4j.LoggerFactory;
43
44import java.util.Collection;
45import java.util.Collections;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020046import java.util.List;
47import java.util.concurrent.ConcurrentMap;
48import java.util.concurrent.ExecutionException;
49import java.util.concurrent.locks.Lock;
50import java.util.concurrent.locks.ReentrantLock;
51
52import static com.google.common.collect.Lists.newArrayList;
53import static org.onosproject.drivers.bmv2.Bmv2FlowRuleProgrammable.Operation.APPLY;
54import static org.onosproject.drivers.bmv2.Bmv2FlowRuleProgrammable.Operation.REMOVE;
55import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
56import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.DELETE;
57import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.INSERT;
Frank Wang0e805082017-07-21 14:37:35 +080058
59/**
60 * Implementation of the flow rule programmable behaviour for BMv2.
61 */
62public class Bmv2FlowRuleProgrammable extends AbstractHandlerBehaviour implements FlowRuleProgrammable {
63
64 private final Logger log = LoggerFactory.getLogger(getClass());
Frank Wang0e805082017-07-21 14:37:35 +080065
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020066 // Needed to synchronize operations over the same table entry.
67 private static final ConcurrentMap<Bmv2TableEntryReference, Lock> ENTRY_LOCKS = Maps.newConcurrentMap();
68
69 // TODO: replace with distributed store.
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +020070 // Can reuse old BMv2TableEntryService from ONOS 1.6
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020071 private static final ConcurrentMap<Bmv2TableEntryReference, Bmv2FlowRuleWrapper> ENTRY_STORE =
72 Maps.newConcurrentMap();
73
74 private DeviceId deviceId;
75 private P4RuntimeClient client;
76 private PiPipeconf pipeconf;
77 private PiPipelineModel pipelineModel;
78 private PiPipelineInterpreter interpreter;
79 private PiFlowRuleTranslationService piFlowRuleTranslationService;
Frank Wang0e805082017-07-21 14:37:35 +080080
81 private boolean init() {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020082
Frank Wang0e805082017-07-21 14:37:35 +080083 deviceId = handler().data().deviceId();
84
85 P4RuntimeController controller = handler().get(P4RuntimeController.class);
86 if (!controller.hasClient(deviceId)) {
87 log.warn("Unable to find client for {}, aborting flow rule operation", deviceId);
88 return false;
89 }
90
Frank Wang0e805082017-07-21 14:37:35 +080091 PiPipeconfService piPipeconfService = handler().get(PiPipeconfService.class);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020092 if (!piPipeconfService.ofDevice(deviceId).isPresent() ||
93 !piPipeconfService.getPipeconf(piPipeconfService.ofDevice(deviceId).get()).isPresent()) {
Frank Wang0e805082017-07-21 14:37:35 +080094 log.warn("Unable to get the pipeconf of {}", deviceId);
95 return false;
96 }
97
98 DeviceService deviceService = handler().get(DeviceService.class);
99 Device device = deviceService.getDevice(deviceId);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200100 if (!device.is(PiPipelineInterpreter.class)) {
101 log.warn("Unable to get interpreter of {}", deviceId);
Frank Wang0e805082017-07-21 14:37:35 +0800102 return false;
103 }
104
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200105 client = controller.getClient(deviceId);
106 pipeconf = piPipeconfService.getPipeconf(piPipeconfService.ofDevice(deviceId).get()).get();
107 pipelineModel = pipeconf.pipelineModel();
108 interpreter = device.as(PiPipelineInterpreter.class);
109 piFlowRuleTranslationService = handler().get(PiFlowRuleTranslationService.class);
110
Frank Wang0e805082017-07-21 14:37:35 +0800111 return true;
112 }
113
114 @Override
115 public Collection<FlowEntry> getFlowEntries() {
116
117 if (!init()) {
118 return Collections.emptyList();
119 }
120
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200121 ImmutableList.Builder<FlowEntry> resultBuilder = ImmutableList.builder();
122 List<PiTableEntry> inconsistentEntries = Lists.newArrayList();
Frank Wang0e805082017-07-21 14:37:35 +0800123
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200124 for (PiTableModel tableModel : pipelineModel.tables()) {
125
126 PiTableId piTableId = PiTableId.of(tableModel.name());
127
128 // Only dump tables that are exposed by the interpreter.
129 // The reason is that some P4 targets (e.g. BMv2's simple_switch) use more table than those defined in the
130 // P4 program, to implement other capabilities, e.g. action execution in control flow.
131 if (!interpreter.mapPiTableId(piTableId).isPresent()) {
132 continue; // next table
133 }
134
135 Collection<PiTableEntry> installedEntries;
136 try {
137 installedEntries = client.dumpTable(piTableId, pipeconf).get();
138 } catch (InterruptedException | ExecutionException e) {
139 log.error("Exception while dumping table {} of {}", piTableId, deviceId, e);
140 return Collections.emptyList();
141 }
142
143 for (PiTableEntry installedEntry : installedEntries) {
144
145 Bmv2TableEntryReference entryRef = new Bmv2TableEntryReference(deviceId, piTableId,
146 installedEntry.matchKey());
147
148 Bmv2FlowRuleWrapper frWrapper = ENTRY_STORE.get(entryRef);
149
150 if (frWrapper == null) {
151 // Inconsistent entry
152 inconsistentEntries.add(installedEntry);
153 continue; // next one.
154 }
155
156 // TODO: implement table entry counter retrieval.
157 long bytes = 0L;
158 long packets = 0L;
159
160 FlowEntry entry = new DefaultFlowEntry(frWrapper.rule(), ADDED, frWrapper.lifeInSeconds(),
161 packets, bytes);
162 resultBuilder.add(entry);
163 }
164 }
165
166 if (inconsistentEntries.size() > 0) {
167 log.warn("Found {} entries in {} that are not known by table entry service," +
168 " removing them", inconsistentEntries.size(), deviceId);
169 inconsistentEntries.forEach(entry -> log.debug(entry.toString()));
170 // Async remove them.
171 client.writeTableEntries(inconsistentEntries, DELETE, pipeconf);
172 }
173
174 return resultBuilder.build();
Frank Wang0e805082017-07-21 14:37:35 +0800175 }
176
177 @Override
178 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200179 return processFlowRules(rules, APPLY);
Frank Wang0e805082017-07-21 14:37:35 +0800180 }
181
182 @Override
183 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200184 return processFlowRules(rules, REMOVE);
Frank Wang0e805082017-07-21 14:37:35 +0800185 }
186
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200187 private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules, Operation operation) {
Frank Wang0e805082017-07-21 14:37:35 +0800188
189 if (!init()) {
190 return Collections.emptyList();
191 }
192
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200193 ImmutableList.Builder<FlowRule> processedFlowRuleListBuilder = ImmutableList.builder();
194
195 // TODO: send write operations in bulk (e.g. all entries to insert, modify or delete).
196 // Instead of calling the client for each one of them.
197
198 for (FlowRule rule : rules) {
199
200 PiTableEntry piTableEntry;
201
Frank Wang0e805082017-07-21 14:37:35 +0800202 try {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200203 piTableEntry = piFlowRuleTranslationService.translate(rule, pipeconf);
Frank Wang0e805082017-07-21 14:37:35 +0800204 } catch (PiFlowRuleTranslationService.PiFlowRuleTranslationException e) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200205 log.warn("Unable to translate flow rule: {} - {}", e.getMessage(), rule);
206 continue; // next rule
Frank Wang0e805082017-07-21 14:37:35 +0800207 }
Frank Wang0e805082017-07-21 14:37:35 +0800208
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200209 PiTableId tableId = piTableEntry.table();
210 Bmv2TableEntryReference entryRef = new Bmv2TableEntryReference(deviceId, 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
217 Bmv2FlowRuleWrapper frWrapper = ENTRY_STORE.get(entryRef);
218
219 WriteOperationType opType;
220 if (operation == Operation.APPLY) {
221 opType = INSERT;
222 if (frWrapper != null) {
223 // We've seen some strange error when trying to modify existing flow rules.
224 // Remove before re-adding the modified one.
225 try {
226 if (client.writeTableEntries(newArrayList(piTableEntry), DELETE, pipeconf).get()) {
227 frWrapper = null;
228 } else {
229 log.warn("Unable to DELETE table entry (before re-adding) in {}: {}",
230 deviceId, piTableEntry);
231 }
232 } catch (InterruptedException | ExecutionException e) {
233 log.warn("Exception while deleting table entry:", operation.name(), e);
234 }
235 }
236 } else {
237 opType = DELETE;
Frank Wang0e805082017-07-21 14:37:35 +0800238 }
Frank Wang0e805082017-07-21 14:37:35 +0800239
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200240 try {
241 if (client.writeTableEntries(newArrayList(piTableEntry), opType, pipeconf).get()) {
242 processedFlowRuleListBuilder.add(rule);
243 frWrapper = new Bmv2FlowRuleWrapper(rule, System.currentTimeMillis());
244 } else {
245 log.warn("Unable to {} table entry in {}: {}", opType.name(), deviceId, piTableEntry);
246 }
247 } catch (InterruptedException | ExecutionException e) {
248 log.warn("Exception while performing {} table entry operation:", operation.name(), e);
249 }
250
251 // Update entryRef binding in table entry service.
252 if (frWrapper != null) {
253 ENTRY_STORE.put(entryRef, frWrapper);
254 } else {
255 ENTRY_STORE.remove(entryRef);
256 }
257
258 } finally {
259 lock.unlock();
260 }
261 }
262
263 return processedFlowRuleListBuilder.build();
264 }
265
266 enum Operation {
267 APPLY, REMOVE
Frank Wang0e805082017-07-21 14:37:35 +0800268 }
269}