blob: ce9342232744fb2f5fceed2903b4e3ee2bc25e72 [file] [log] [blame]
Carmelo Cascone2ea177b2016-02-25 18:38:42 -08001/*
Carmelo Cascone2954f132016-04-15 10:26:40 -07002 * Copyright 2016-present Open Networking Laboratory
Carmelo Cascone2ea177b2016-02-25 18:38:42 -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
17package org.onosproject.drivers.bmv2;
18
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080019import com.google.common.collect.Lists;
20import com.google.common.collect.Maps;
Carmelo Cascone2954f132016-04-15 10:26:40 -070021import org.apache.commons.lang3.tuple.Pair;
Carmelo Cascone0831efb2016-05-31 14:50:19 -070022import org.onosproject.bmv2.api.context.Bmv2Configuration;
23import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
24import org.onosproject.bmv2.api.context.Bmv2FlowRuleTranslator;
25import org.onosproject.bmv2.api.context.Bmv2FlowRuleTranslatorException;
26import org.onosproject.bmv2.api.context.Bmv2Interpreter;
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -070027import org.onosproject.bmv2.api.context.Bmv2TableModel;
Carmelo Cascone0831efb2016-05-31 14:50:19 -070028import org.onosproject.bmv2.api.runtime.Bmv2DeviceAgent;
29import org.onosproject.bmv2.api.runtime.Bmv2FlowRuleWrapper;
Carmelo Cascone2954f132016-04-15 10:26:40 -070030import org.onosproject.bmv2.api.runtime.Bmv2MatchKey;
Carmelo Cascone0831efb2016-05-31 14:50:19 -070031import org.onosproject.bmv2.api.runtime.Bmv2ParsedTableEntry;
Carmelo Casconeaa8b6292016-04-13 14:27:06 -070032import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
Carmelo Cascone2954f132016-04-15 10:26:40 -070033import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
Carmelo Cascone0831efb2016-05-31 14:50:19 -070034import org.onosproject.bmv2.api.runtime.Bmv2TableEntryReference;
35import org.onosproject.bmv2.api.service.Bmv2Controller;
36import org.onosproject.bmv2.api.service.Bmv2DeviceContextService;
37import org.onosproject.bmv2.api.service.Bmv2TableEntryService;
Carmelo Cascone2954f132016-04-15 10:26:40 -070038import org.onosproject.net.DeviceId;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080039import org.onosproject.net.driver.AbstractHandlerBehaviour;
40import org.onosproject.net.flow.DefaultFlowEntry;
41import org.onosproject.net.flow.FlowEntry;
42import org.onosproject.net.flow.FlowRule;
43import org.onosproject.net.flow.FlowRuleProgrammable;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080044import org.slf4j.Logger;
45import org.slf4j.LoggerFactory;
46
47import java.util.Collection;
48import java.util.Collections;
Carmelo Casconef8cf2882016-05-04 14:06:17 -070049import java.util.Date;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080050import java.util.List;
Carmelo Cascone2954f132016-04-15 10:26:40 -070051import java.util.concurrent.ConcurrentMap;
Carmelo Cascone6256d012016-06-17 13:49:52 -070052import java.util.concurrent.locks.Lock;
53import java.util.concurrent.locks.ReentrantLock;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080054
Carmelo Cascone0831efb2016-05-31 14:50:19 -070055import static org.onosproject.bmv2.api.runtime.Bmv2RuntimeException.Code.*;
Carmelo Casconef8cf2882016-05-04 14:06:17 -070056import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
57
Carmelo Casconee9121642016-04-27 17:02:38 -070058/**
Carmelo Cascone0831efb2016-05-31 14:50:19 -070059 * Implementation of the flow rule programmable behaviour for BMv2.
Carmelo Casconee9121642016-04-27 17:02:38 -070060 */
Carmelo Cascone0831efb2016-05-31 14:50:19 -070061public class Bmv2FlowRuleProgrammable extends AbstractHandlerBehaviour implements FlowRuleProgrammable {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080062
Carmelo Cascone0831efb2016-05-31 14:50:19 -070063 private final Logger log = LoggerFactory.getLogger(this.getClass());
Carmelo Casconed4e7a772016-05-03 11:21:29 -070064
Carmelo Cascone0831efb2016-05-31 14:50:19 -070065 // Needed to synchronize operations over the same table entry.
Carmelo Cascone6256d012016-06-17 13:49:52 -070066 private static final ConcurrentMap<Bmv2TableEntryReference, Lock> ENTRY_LOCKS = Maps.newConcurrentMap();
Carmelo Casconed4e7a772016-05-03 11:21:29 -070067
Carmelo Cascone0831efb2016-05-31 14:50:19 -070068 private Bmv2Controller controller;
69 private Bmv2TableEntryService tableEntryService;
70 private Bmv2DeviceContextService contextService;
71
72 private boolean init() {
73 controller = handler().get(Bmv2Controller.class);
74 tableEntryService = handler().get(Bmv2TableEntryService.class);
75 contextService = handler().get(Bmv2DeviceContextService.class);
76 if (controller == null) {
77 log.warn("Failed to get a BMv2 controller");
78 return false;
79 }
80 if (tableEntryService == null) {
81 log.warn("Failed to get a BMv2 table entry service");
82 return false;
83 }
84 if (contextService == null) {
85 log.warn("Failed to get a BMv2 device context service");
86 return false;
87 }
88 return true;
89 }
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080090
91 @Override
92 public Collection<FlowEntry> getFlowEntries() {
Carmelo Cascone2954f132016-04-15 10:26:40 -070093
Carmelo Cascone0831efb2016-05-31 14:50:19 -070094 if (!init()) {
Carmelo Casconea2f510e2016-05-03 18:36:45 -070095 return Collections.emptyList();
96 }
97
Carmelo Cascone0831efb2016-05-31 14:50:19 -070098 DeviceId deviceId = handler().data().deviceId();
99
100 Bmv2DeviceAgent deviceAgent;
101 try {
102 deviceAgent = controller.getAgent(deviceId);
103 } catch (Bmv2RuntimeException e) {
104 log.error("Failed to get BMv2 device agent: {}", e.explain());
105 return Collections.emptyList();
106 }
107
108 Bmv2DeviceContext context = contextService.getContext(deviceId);
109 if (context == null) {
110 log.warn("Unable to get device context for {}", deviceId);
111 }
112
113 Bmv2Interpreter interpreter = context.interpreter();
114 Bmv2Configuration configuration = context.configuration();
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700115
Carmelo Cascone2954f132016-04-15 10:26:40 -0700116 List<FlowEntry> entryList = Lists.newArrayList();
117
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700118 for (Bmv2TableModel table : configuration.tables()) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700119 // For each table in the configuration AND exposed by the interpreter.
120 if (!interpreter.tableIdMap().inverse().containsKey(table.name())) {
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700121 continue; // next table
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700122 }
123
Carmelo Cascone25f18882016-06-14 19:16:50 -0700124 List<Bmv2ParsedTableEntry> installedEntries;
125 try {
126 installedEntries = deviceAgent.getTableEntries(table.name());
127 } catch (Bmv2RuntimeException e) {
128 log.warn("Failed to get table entries of table {} of {}: {}", table.name(), deviceId, e.explain());
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700129 continue; // next table
Carmelo Cascone25f18882016-06-14 19:16:50 -0700130 }
131
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700132 for (Bmv2ParsedTableEntry parsedEntry : installedEntries) {
133 Bmv2TableEntryReference entryRef = new Bmv2TableEntryReference(deviceId, table.name(),
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700134 parsedEntry.matchKey());
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700135
Carmelo Cascone6256d012016-06-17 13:49:52 -0700136 Lock lock = ENTRY_LOCKS.computeIfAbsent(entryRef, key -> new ReentrantLock());
137 lock.lock();
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700138
Carmelo Cascone6256d012016-06-17 13:49:52 -0700139 try {
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700140 Bmv2FlowRuleWrapper frWrapper = tableEntryService.lookup(entryRef);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700141
142 if (frWrapper == null) {
143 log.warn("missing reference from table entry service, BUG? " +
144 "deviceId={}, tableName={}, matchKey={}",
145 deviceId, table.name(), entryRef.matchKey());
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700146 continue; // next entry
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700147 }
148
149 long remoteEntryId = parsedEntry.entryId();
150 long localEntryId = frWrapper.entryId();
151
152 if (remoteEntryId != localEntryId) {
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700153 log.debug("getFlowEntries(): inconsistent entry id! BUG? Updating it... remote={}, local={}",
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700154 remoteEntryId, localEntryId);
155 frWrapper = new Bmv2FlowRuleWrapper(frWrapper.rule(), remoteEntryId,
156 frWrapper.creationDate());
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700157 tableEntryService.bind(entryRef, frWrapper);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700158 }
159
160 long bytes = 0L;
161 long packets = 0L;
162
163 if (table.hasCounters()) {
164 // Read counter values from device.
165 try {
166 Pair<Long, Long> counterValue = deviceAgent.readTableEntryCounter(table.name(),
167 remoteEntryId);
168 bytes = counterValue.getLeft();
169 packets = counterValue.getRight();
170 } catch (Bmv2RuntimeException e) {
171 log.warn("Unable to get counters for entry {}/{} of device {}: {}",
172 table.name(), remoteEntryId, deviceId, e.explain());
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700173 }
174 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700175
176 FlowEntry entry = new DefaultFlowEntry(frWrapper.rule(), ADDED, frWrapper.lifeInSeconds(),
177 packets, bytes);
178 entryList.add(entry);
Carmelo Cascone6256d012016-06-17 13:49:52 -0700179
180 } finally {
181 lock.unlock();
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700182 }
183 }
184 }
Carmelo Cascone2954f132016-04-15 10:26:40 -0700185
186 return Collections.unmodifiableCollection(entryList);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800187 }
188
189 @Override
190 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800191
Carmelo Cascone2954f132016-04-15 10:26:40 -0700192 return processFlowRules(rules, Operation.APPLY);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800193 }
194
195 @Override
196 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone2954f132016-04-15 10:26:40 -0700197
198 return processFlowRules(rules, Operation.REMOVE);
199 }
200
201 private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules, Operation operation) {
202
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700203 if (!init()) {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800204 return Collections.emptyList();
205 }
206
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700207 DeviceId deviceId = handler().data().deviceId();
208
209 Bmv2DeviceAgent deviceAgent;
210 try {
211 deviceAgent = controller.getAgent(deviceId);
212 } catch (Bmv2RuntimeException e) {
213 log.error("Failed to get BMv2 device agent: {}", e.explain());
214 return Collections.emptyList();
215 }
216
217 Bmv2DeviceContext context = contextService.getContext(deviceId);
218 if (context == null) {
219 log.error("Unable to get device context for {}", deviceId);
220 return Collections.emptyList();
221 }
222
223 Bmv2FlowRuleTranslator translator = tableEntryService.getFlowRuleTranslator();
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700224
Carmelo Cascone2954f132016-04-15 10:26:40 -0700225 List<FlowRule> processedFlowRules = Lists.newArrayList();
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800226
227 for (FlowRule rule : rules) {
228
Carmelo Cascone2954f132016-04-15 10:26:40 -0700229 Bmv2TableEntry bmv2Entry;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800230
Carmelo Cascone2954f132016-04-15 10:26:40 -0700231 try {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700232 bmv2Entry = translator.translate(rule, context);
Carmelo Cascone2954f132016-04-15 10:26:40 -0700233 } catch (Bmv2FlowRuleTranslatorException e) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700234 log.warn("Unable to translate flow rule: {} - {}", e.getMessage(), rule);
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700235 continue; // next rule
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800236 }
Carmelo Cascone2954f132016-04-15 10:26:40 -0700237
238 String tableName = bmv2Entry.tableName();
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700239 Bmv2TableEntryReference entryRef = new Bmv2TableEntryReference(deviceId, tableName, bmv2Entry.matchKey());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700240
Carmelo Cascone6256d012016-06-17 13:49:52 -0700241 Lock lock = ENTRY_LOCKS.computeIfAbsent(entryRef, k -> new ReentrantLock());
242 lock.lock();
243 try {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700244 // Get from store
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700245 Bmv2FlowRuleWrapper frWrapper = tableEntryService.lookup(entryRef);
Carmelo Cascone2954f132016-04-15 10:26:40 -0700246 try {
247 if (operation == Operation.APPLY) {
248 // Apply entry
249 long entryId;
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700250 if (frWrapper != null) {
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700251 // Existing entry.
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700252 entryId = frWrapper.entryId();
253 // Tentatively delete entry before re-adding.
254 // It might not exist on device due to inconsistencies.
255 silentlyRemove(deviceAgent, entryRef.tableName(), entryId);
Carmelo Cascone2954f132016-04-15 10:26:40 -0700256 }
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700257 // Add entry.
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700258 entryId = doAddEntry(deviceAgent, bmv2Entry);
259 frWrapper = new Bmv2FlowRuleWrapper(rule, entryId, new Date());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700260 } else {
261 // Remove entry
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700262 if (frWrapper == null) {
Carmelo Cascone2954f132016-04-15 10:26:40 -0700263 // Entry not found in map, how come?
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700264 forceRemove(deviceAgent, entryRef.tableName(), entryRef.matchKey());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700265 } else {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700266 long entryId = frWrapper.entryId();
267 doRemove(deviceAgent, entryRef.tableName(), entryId, entryRef.matchKey());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700268 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700269 frWrapper = null;
Carmelo Cascone2954f132016-04-15 10:26:40 -0700270 }
271 // If here, no exceptions... things went well :)
272 processedFlowRules.add(rule);
273 } catch (Bmv2RuntimeException e) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700274 log.warn("Unable to {} flow rule: {}", operation.name(), e.explain());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700275 }
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700276
277 // Update entryRef binding in table entry service.
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700278 if (frWrapper != null) {
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700279 tableEntryService.bind(entryRef, frWrapper);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700280 } else {
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700281 tableEntryService.unbind(entryRef);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700282 }
Carmelo Cascone6256d012016-06-17 13:49:52 -0700283 } finally {
284 lock.unlock();
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700285 }
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800286 }
287
Carmelo Cascone2954f132016-04-15 10:26:40 -0700288 return processedFlowRules;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800289 }
290
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700291 private long doAddEntry(Bmv2DeviceAgent agent, Bmv2TableEntry entry) throws Bmv2RuntimeException {
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700292 try {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700293 return agent.addTableEntry(entry);
294 } catch (Bmv2RuntimeException e) {
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700295 if (e.getCode().equals(TABLE_DUPLICATE_ENTRY)) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700296 forceRemove(agent, entry.tableName(), entry.matchKey());
297 return agent.addTableEntry(entry);
298 } else {
299 throw e;
300 }
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700301 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700302 }
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700303
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700304 private void doRemove(Bmv2DeviceAgent agent, String tableName, long entryId, Bmv2MatchKey matchKey)
305 throws Bmv2RuntimeException {
306 try {
307 agent.deleteTableEntry(tableName, entryId);
308 } catch (Bmv2RuntimeException e) {
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700309 if (e.getCode().equals(TABLE_INVALID_HANDLE) || e.getCode().equals(TABLE_EXPIRED_HANDLE)) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700310 // entry is not there with the declared ID, try with a forced remove.
311 forceRemove(agent, tableName, matchKey);
312 } else {
313 throw e;
314 }
315 }
316 }
317
318 private void forceRemove(Bmv2DeviceAgent agent, String tableName, Bmv2MatchKey matchKey)
319 throws Bmv2RuntimeException {
320 // Find the entryID (expensive call!)
Carmelo Cascone25f18882016-06-14 19:16:50 -0700321 for (Bmv2ParsedTableEntry pEntry : agent.getTableEntries(tableName)) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700322 if (pEntry.matchKey().equals(matchKey)) {
323 // Remove entry and drop exceptions.
324 silentlyRemove(agent, tableName, pEntry.entryId());
325 break;
326 }
327 }
328 }
329
330 private void silentlyRemove(Bmv2DeviceAgent agent, String tableName, long entryId) {
331 try {
332 agent.deleteTableEntry(tableName, entryId);
333 } catch (Bmv2RuntimeException e) {
334 // do nothing
335 }
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700336 }
337
Carmelo Cascone2954f132016-04-15 10:26:40 -0700338 private enum Operation {
339 APPLY, REMOVE
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800340 }
Carmelo Cascone2954f132016-04-15 10:26:40 -0700341}