blob: 403f68c5c86dd803f9df066105a39fcca4d49d33 [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 Cascone2ea177b2016-02-25 18:38:42 -080052
Carmelo Cascone0831efb2016-05-31 14:50:19 -070053import static org.onosproject.bmv2.api.runtime.Bmv2RuntimeException.Code.*;
Carmelo Casconef8cf2882016-05-04 14:06:17 -070054import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
55
Carmelo Casconee9121642016-04-27 17:02:38 -070056/**
Carmelo Cascone0831efb2016-05-31 14:50:19 -070057 * Implementation of the flow rule programmable behaviour for BMv2.
Carmelo Casconee9121642016-04-27 17:02:38 -070058 */
Carmelo Cascone0831efb2016-05-31 14:50:19 -070059public class Bmv2FlowRuleProgrammable extends AbstractHandlerBehaviour implements FlowRuleProgrammable {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080060
Carmelo Cascone0831efb2016-05-31 14:50:19 -070061 private final Logger log = LoggerFactory.getLogger(this.getClass());
Carmelo Casconed4e7a772016-05-03 11:21:29 -070062
Carmelo Cascone0831efb2016-05-31 14:50:19 -070063 // Needed to synchronize operations over the same table entry.
64 private static final ConcurrentMap<Bmv2TableEntryReference, Boolean> ENTRY_LOCKS = Maps.newConcurrentMap();
Carmelo Casconed4e7a772016-05-03 11:21:29 -070065
Carmelo Cascone0831efb2016-05-31 14:50:19 -070066 private Bmv2Controller controller;
67 private Bmv2TableEntryService tableEntryService;
68 private Bmv2DeviceContextService contextService;
69
70 private boolean init() {
71 controller = handler().get(Bmv2Controller.class);
72 tableEntryService = handler().get(Bmv2TableEntryService.class);
73 contextService = handler().get(Bmv2DeviceContextService.class);
74 if (controller == null) {
75 log.warn("Failed to get a BMv2 controller");
76 return false;
77 }
78 if (tableEntryService == null) {
79 log.warn("Failed to get a BMv2 table entry service");
80 return false;
81 }
82 if (contextService == null) {
83 log.warn("Failed to get a BMv2 device context service");
84 return false;
85 }
86 return true;
87 }
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080088
89 @Override
90 public Collection<FlowEntry> getFlowEntries() {
Carmelo Cascone2954f132016-04-15 10:26:40 -070091
Carmelo Cascone0831efb2016-05-31 14:50:19 -070092 if (!init()) {
Carmelo Casconea2f510e2016-05-03 18:36:45 -070093 return Collections.emptyList();
94 }
95
Carmelo Cascone0831efb2016-05-31 14:50:19 -070096 DeviceId deviceId = handler().data().deviceId();
97
98 Bmv2DeviceAgent deviceAgent;
99 try {
100 deviceAgent = controller.getAgent(deviceId);
101 } catch (Bmv2RuntimeException e) {
102 log.error("Failed to get BMv2 device agent: {}", e.explain());
103 return Collections.emptyList();
104 }
105
106 Bmv2DeviceContext context = contextService.getContext(deviceId);
107 if (context == null) {
108 log.warn("Unable to get device context for {}", deviceId);
109 }
110
111 Bmv2Interpreter interpreter = context.interpreter();
112 Bmv2Configuration configuration = context.configuration();
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700113
Carmelo Cascone2954f132016-04-15 10:26:40 -0700114 List<FlowEntry> entryList = Lists.newArrayList();
115
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700116 for (Bmv2TableModel table : configuration.tables()) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700117 // For each table in the configuration AND exposed by the interpreter.
118 if (!interpreter.tableIdMap().inverse().containsKey(table.name())) {
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700119 continue; // next table
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700120 }
121
Carmelo Cascone25f18882016-06-14 19:16:50 -0700122 List<Bmv2ParsedTableEntry> installedEntries;
123 try {
124 installedEntries = deviceAgent.getTableEntries(table.name());
125 } catch (Bmv2RuntimeException e) {
126 log.warn("Failed to get table entries of table {} of {}: {}", table.name(), deviceId, e.explain());
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700127 continue; // next table
Carmelo Cascone25f18882016-06-14 19:16:50 -0700128 }
129
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700130 for (Bmv2ParsedTableEntry parsedEntry : installedEntries) {
131 Bmv2TableEntryReference entryRef = new Bmv2TableEntryReference(deviceId, table.name(),
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700132 parsedEntry.matchKey());
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700133
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700134 ENTRY_LOCKS.putIfAbsent(entryRef, true);
135 synchronized (ENTRY_LOCKS.get(entryRef)) {
136
137 Bmv2FlowRuleWrapper frWrapper = tableEntryService.lookup(entryRef);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700138
139 if (frWrapper == null) {
140 log.warn("missing reference from table entry service, BUG? " +
141 "deviceId={}, tableName={}, matchKey={}",
142 deviceId, table.name(), entryRef.matchKey());
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700143 continue; // next entry
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700144 }
145
146 long remoteEntryId = parsedEntry.entryId();
147 long localEntryId = frWrapper.entryId();
148
149 if (remoteEntryId != localEntryId) {
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700150 log.debug("getFlowEntries(): inconsistent entry id! BUG? Updating it... remote={}, local={}",
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700151 remoteEntryId, localEntryId);
152 frWrapper = new Bmv2FlowRuleWrapper(frWrapper.rule(), remoteEntryId,
153 frWrapper.creationDate());
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700154 tableEntryService.bind(entryRef, frWrapper);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700155 }
156
157 long bytes = 0L;
158 long packets = 0L;
159
160 if (table.hasCounters()) {
161 // Read counter values from device.
162 try {
163 Pair<Long, Long> counterValue = deviceAgent.readTableEntryCounter(table.name(),
164 remoteEntryId);
165 bytes = counterValue.getLeft();
166 packets = counterValue.getRight();
167 } catch (Bmv2RuntimeException e) {
168 log.warn("Unable to get counters for entry {}/{} of device {}: {}",
169 table.name(), remoteEntryId, deviceId, e.explain());
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700170 }
171 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700172
173 FlowEntry entry = new DefaultFlowEntry(frWrapper.rule(), ADDED, frWrapper.lifeInSeconds(),
174 packets, bytes);
175 entryList.add(entry);
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700176 }
177 }
178 }
Carmelo Cascone2954f132016-04-15 10:26:40 -0700179
180 return Collections.unmodifiableCollection(entryList);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800181 }
182
183 @Override
184 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800185
Carmelo Cascone2954f132016-04-15 10:26:40 -0700186 return processFlowRules(rules, Operation.APPLY);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800187 }
188
189 @Override
190 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone2954f132016-04-15 10:26:40 -0700191
192 return processFlowRules(rules, Operation.REMOVE);
193 }
194
195 private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules, Operation operation) {
196
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700197 if (!init()) {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800198 return Collections.emptyList();
199 }
200
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700201 DeviceId deviceId = handler().data().deviceId();
202
203 Bmv2DeviceAgent deviceAgent;
204 try {
205 deviceAgent = controller.getAgent(deviceId);
206 } catch (Bmv2RuntimeException e) {
207 log.error("Failed to get BMv2 device agent: {}", e.explain());
208 return Collections.emptyList();
209 }
210
211 Bmv2DeviceContext context = contextService.getContext(deviceId);
212 if (context == null) {
213 log.error("Unable to get device context for {}", deviceId);
214 return Collections.emptyList();
215 }
216
217 Bmv2FlowRuleTranslator translator = tableEntryService.getFlowRuleTranslator();
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700218
Carmelo Cascone2954f132016-04-15 10:26:40 -0700219 List<FlowRule> processedFlowRules = Lists.newArrayList();
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800220
221 for (FlowRule rule : rules) {
222
Carmelo Cascone2954f132016-04-15 10:26:40 -0700223 Bmv2TableEntry bmv2Entry;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800224
Carmelo Cascone2954f132016-04-15 10:26:40 -0700225 try {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700226 bmv2Entry = translator.translate(rule, context);
Carmelo Cascone2954f132016-04-15 10:26:40 -0700227 } catch (Bmv2FlowRuleTranslatorException e) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700228 log.warn("Unable to translate flow rule: {} - {}", e.getMessage(), rule);
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700229 continue; // next rule
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800230 }
Carmelo Cascone2954f132016-04-15 10:26:40 -0700231
232 String tableName = bmv2Entry.tableName();
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700233 Bmv2TableEntryReference entryRef = new Bmv2TableEntryReference(deviceId, tableName, bmv2Entry.matchKey());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700234
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700235 ENTRY_LOCKS.putIfAbsent(entryRef, true);
236 synchronized (ENTRY_LOCKS.get(entryRef)) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700237 // Get from store
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700238 Bmv2FlowRuleWrapper frWrapper = tableEntryService.lookup(entryRef);
Carmelo Cascone2954f132016-04-15 10:26:40 -0700239 try {
240 if (operation == Operation.APPLY) {
241 // Apply entry
242 long entryId;
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700243 if (frWrapper != null) {
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700244 // Existing entry.
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700245 entryId = frWrapper.entryId();
246 // Tentatively delete entry before re-adding.
247 // It might not exist on device due to inconsistencies.
248 silentlyRemove(deviceAgent, entryRef.tableName(), entryId);
Carmelo Cascone2954f132016-04-15 10:26:40 -0700249 }
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700250 // Add entry.
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700251 entryId = doAddEntry(deviceAgent, bmv2Entry);
252 frWrapper = new Bmv2FlowRuleWrapper(rule, entryId, new Date());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700253 } else {
254 // Remove entry
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700255 if (frWrapper == null) {
Carmelo Cascone2954f132016-04-15 10:26:40 -0700256 // Entry not found in map, how come?
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700257 forceRemove(deviceAgent, entryRef.tableName(), entryRef.matchKey());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700258 } else {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700259 long entryId = frWrapper.entryId();
260 doRemove(deviceAgent, entryRef.tableName(), entryId, entryRef.matchKey());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700261 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700262 frWrapper = null;
Carmelo Cascone2954f132016-04-15 10:26:40 -0700263 }
264 // If here, no exceptions... things went well :)
265 processedFlowRules.add(rule);
266 } catch (Bmv2RuntimeException e) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700267 log.warn("Unable to {} flow rule: {}", operation.name(), e.explain());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700268 }
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700269
270 // Update entryRef binding in table entry service.
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700271 if (frWrapper != null) {
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700272 tableEntryService.bind(entryRef, frWrapper);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700273 } else {
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700274 tableEntryService.unbind(entryRef);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700275 }
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700276 }
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800277 }
278
Carmelo Cascone2954f132016-04-15 10:26:40 -0700279 return processedFlowRules;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800280 }
281
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700282 private long doAddEntry(Bmv2DeviceAgent agent, Bmv2TableEntry entry) throws Bmv2RuntimeException {
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700283 try {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700284 return agent.addTableEntry(entry);
285 } catch (Bmv2RuntimeException e) {
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700286 if (e.getCode().equals(TABLE_DUPLICATE_ENTRY)) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700287 forceRemove(agent, entry.tableName(), entry.matchKey());
288 return agent.addTableEntry(entry);
289 } else {
290 throw e;
291 }
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700292 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700293 }
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700294
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700295 private void doRemove(Bmv2DeviceAgent agent, String tableName, long entryId, Bmv2MatchKey matchKey)
296 throws Bmv2RuntimeException {
297 try {
298 agent.deleteTableEntry(tableName, entryId);
299 } catch (Bmv2RuntimeException e) {
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700300 if (e.getCode().equals(TABLE_INVALID_HANDLE) || e.getCode().equals(TABLE_EXPIRED_HANDLE)) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700301 // entry is not there with the declared ID, try with a forced remove.
302 forceRemove(agent, tableName, matchKey);
303 } else {
304 throw e;
305 }
306 }
307 }
308
309 private void forceRemove(Bmv2DeviceAgent agent, String tableName, Bmv2MatchKey matchKey)
310 throws Bmv2RuntimeException {
311 // Find the entryID (expensive call!)
Carmelo Cascone25f18882016-06-14 19:16:50 -0700312 for (Bmv2ParsedTableEntry pEntry : agent.getTableEntries(tableName)) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700313 if (pEntry.matchKey().equals(matchKey)) {
314 // Remove entry and drop exceptions.
315 silentlyRemove(agent, tableName, pEntry.entryId());
316 break;
317 }
318 }
319 }
320
321 private void silentlyRemove(Bmv2DeviceAgent agent, String tableName, long entryId) {
322 try {
323 agent.deleteTableEntry(tableName, entryId);
324 } catch (Bmv2RuntimeException e) {
325 // do nothing
326 }
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700327 }
328
Carmelo Cascone2954f132016-04-15 10:26:40 -0700329 private enum Operation {
330 APPLY, REMOVE
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800331 }
Carmelo Cascone2954f132016-04-15 10:26:40 -0700332}