blob: 5fc9908707a07556e1fd9b456a0c1cfbe8f434d2 [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 Casconefbc577b2016-06-17 23:19:09 -070022import org.onlab.osgi.ServiceNotFoundException;
Carmelo Cascone0831efb2016-05-31 14:50:19 -070023import org.onosproject.bmv2.api.context.Bmv2Configuration;
24import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
25import org.onosproject.bmv2.api.context.Bmv2FlowRuleTranslator;
26import org.onosproject.bmv2.api.context.Bmv2FlowRuleTranslatorException;
27import org.onosproject.bmv2.api.context.Bmv2Interpreter;
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -070028import org.onosproject.bmv2.api.context.Bmv2TableModel;
Carmelo Cascone0831efb2016-05-31 14:50:19 -070029import org.onosproject.bmv2.api.runtime.Bmv2DeviceAgent;
30import org.onosproject.bmv2.api.runtime.Bmv2FlowRuleWrapper;
Carmelo Cascone2954f132016-04-15 10:26:40 -070031import org.onosproject.bmv2.api.runtime.Bmv2MatchKey;
Carmelo Cascone0831efb2016-05-31 14:50:19 -070032import org.onosproject.bmv2.api.runtime.Bmv2ParsedTableEntry;
Carmelo Casconeaa8b6292016-04-13 14:27:06 -070033import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
Carmelo Cascone2954f132016-04-15 10:26:40 -070034import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
Carmelo Cascone0831efb2016-05-31 14:50:19 -070035import org.onosproject.bmv2.api.runtime.Bmv2TableEntryReference;
36import org.onosproject.bmv2.api.service.Bmv2Controller;
37import org.onosproject.bmv2.api.service.Bmv2DeviceContextService;
38import org.onosproject.bmv2.api.service.Bmv2TableEntryService;
Carmelo Cascone2954f132016-04-15 10:26:40 -070039import org.onosproject.net.DeviceId;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080040import org.onosproject.net.driver.AbstractHandlerBehaviour;
41import org.onosproject.net.flow.DefaultFlowEntry;
42import org.onosproject.net.flow.FlowEntry;
43import org.onosproject.net.flow.FlowRule;
44import org.onosproject.net.flow.FlowRuleProgrammable;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080045import org.slf4j.Logger;
46import org.slf4j.LoggerFactory;
47
48import java.util.Collection;
49import java.util.Collections;
50import 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() {
Carmelo Casconefbc577b2016-06-17 23:19:09 -070073 try {
74 controller = handler().get(Bmv2Controller.class);
75 tableEntryService = handler().get(Bmv2TableEntryService.class);
76 contextService = handler().get(Bmv2DeviceContextService.class);
77 return true;
78 } catch (ServiceNotFoundException e) {
79 log.warn(e.getMessage());
Carmelo Cascone0831efb2016-05-31 14:50:19 -070080 return false;
81 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -070082 }
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080083
84 @Override
85 public Collection<FlowEntry> getFlowEntries() {
Carmelo Cascone2954f132016-04-15 10:26:40 -070086
Carmelo Cascone0831efb2016-05-31 14:50:19 -070087 if (!init()) {
Carmelo Casconea2f510e2016-05-03 18:36:45 -070088 return Collections.emptyList();
89 }
90
Carmelo Cascone0831efb2016-05-31 14:50:19 -070091 DeviceId deviceId = handler().data().deviceId();
92
93 Bmv2DeviceAgent deviceAgent;
94 try {
95 deviceAgent = controller.getAgent(deviceId);
96 } catch (Bmv2RuntimeException e) {
97 log.error("Failed to get BMv2 device agent: {}", e.explain());
98 return Collections.emptyList();
99 }
100
101 Bmv2DeviceContext context = contextService.getContext(deviceId);
102 if (context == null) {
103 log.warn("Unable to get device context for {}", deviceId);
104 }
105
106 Bmv2Interpreter interpreter = context.interpreter();
107 Bmv2Configuration configuration = context.configuration();
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700108
Carmelo Cascone2954f132016-04-15 10:26:40 -0700109 List<FlowEntry> entryList = Lists.newArrayList();
110
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700111 for (Bmv2TableModel table : configuration.tables()) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700112 // For each table in the configuration AND exposed by the interpreter.
113 if (!interpreter.tableIdMap().inverse().containsKey(table.name())) {
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700114 continue; // next table
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700115 }
116
Carmelo Cascone25f18882016-06-14 19:16:50 -0700117 List<Bmv2ParsedTableEntry> installedEntries;
118 try {
119 installedEntries = deviceAgent.getTableEntries(table.name());
120 } catch (Bmv2RuntimeException e) {
121 log.warn("Failed to get table entries of table {} of {}: {}", table.name(), deviceId, e.explain());
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700122 continue; // next table
Carmelo Cascone25f18882016-06-14 19:16:50 -0700123 }
124
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700125 for (Bmv2ParsedTableEntry parsedEntry : installedEntries) {
126 Bmv2TableEntryReference entryRef = new Bmv2TableEntryReference(deviceId, table.name(),
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700127 parsedEntry.matchKey());
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700128
Carmelo Cascone6256d012016-06-17 13:49:52 -0700129 Lock lock = ENTRY_LOCKS.computeIfAbsent(entryRef, key -> new ReentrantLock());
130 lock.lock();
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700131
Carmelo Cascone6256d012016-06-17 13:49:52 -0700132 try {
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700133 Bmv2FlowRuleWrapper frWrapper = tableEntryService.lookup(entryRef);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700134
135 if (frWrapper == null) {
Carmelo Casconefbc577b2016-06-17 23:19:09 -0700136 log.debug("Missing reference from table entry service. Deleting it. BUG? " +
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700137 "deviceId={}, tableName={}, matchKey={}",
138 deviceId, table.name(), entryRef.matchKey());
Carmelo Casconefbc577b2016-06-17 23:19:09 -0700139 try {
140 doRemove(deviceAgent, table.name(), parsedEntry.entryId(), parsedEntry.matchKey());
141 } catch (Bmv2RuntimeException e) {
142 log.warn("Unable to remove inconsistent flow rule: {}", e.explain());
143 }
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700144 continue; // next entry
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700145 }
146
147 long remoteEntryId = parsedEntry.entryId();
148 long localEntryId = frWrapper.entryId();
149
150 if (remoteEntryId != localEntryId) {
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700151 log.debug("getFlowEntries(): inconsistent entry id! BUG? Updating it... remote={}, local={}",
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700152 remoteEntryId, localEntryId);
153 frWrapper = new Bmv2FlowRuleWrapper(frWrapper.rule(), remoteEntryId,
Carmelo Cascone728b1cd2016-06-24 15:21:38 -0700154 frWrapper.installedOnMillis());
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700155 tableEntryService.bind(entryRef, frWrapper);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700156 }
157
158 long bytes = 0L;
159 long packets = 0L;
160
161 if (table.hasCounters()) {
162 // Read counter values from device.
163 try {
164 Pair<Long, Long> counterValue = deviceAgent.readTableEntryCounter(table.name(),
165 remoteEntryId);
166 bytes = counterValue.getLeft();
167 packets = counterValue.getRight();
168 } catch (Bmv2RuntimeException e) {
169 log.warn("Unable to get counters for entry {}/{} of device {}: {}",
170 table.name(), remoteEntryId, deviceId, e.explain());
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700171 }
172 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700173
174 FlowEntry entry = new DefaultFlowEntry(frWrapper.rule(), ADDED, frWrapper.lifeInSeconds(),
175 packets, bytes);
176 entryList.add(entry);
Carmelo Cascone6256d012016-06-17 13:49:52 -0700177
178 } finally {
179 lock.unlock();
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700180 }
181 }
182 }
Carmelo Cascone2954f132016-04-15 10:26:40 -0700183
184 return Collections.unmodifiableCollection(entryList);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800185 }
186
187 @Override
188 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800189
Carmelo Cascone2954f132016-04-15 10:26:40 -0700190 return processFlowRules(rules, Operation.APPLY);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800191 }
192
193 @Override
194 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone2954f132016-04-15 10:26:40 -0700195
196 return processFlowRules(rules, Operation.REMOVE);
197 }
198
199 private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules, Operation operation) {
200
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700201 if (!init()) {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800202 return Collections.emptyList();
203 }
204
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700205 DeviceId deviceId = handler().data().deviceId();
206
207 Bmv2DeviceAgent deviceAgent;
208 try {
209 deviceAgent = controller.getAgent(deviceId);
210 } catch (Bmv2RuntimeException e) {
211 log.error("Failed to get BMv2 device agent: {}", e.explain());
212 return Collections.emptyList();
213 }
214
215 Bmv2DeviceContext context = contextService.getContext(deviceId);
216 if (context == null) {
217 log.error("Unable to get device context for {}", deviceId);
218 return Collections.emptyList();
219 }
220
221 Bmv2FlowRuleTranslator translator = tableEntryService.getFlowRuleTranslator();
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700222
Carmelo Cascone2954f132016-04-15 10:26:40 -0700223 List<FlowRule> processedFlowRules = Lists.newArrayList();
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800224
225 for (FlowRule rule : rules) {
226
Carmelo Cascone2954f132016-04-15 10:26:40 -0700227 Bmv2TableEntry bmv2Entry;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800228
Carmelo Cascone2954f132016-04-15 10:26:40 -0700229 try {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700230 bmv2Entry = translator.translate(rule, context);
Carmelo Cascone2954f132016-04-15 10:26:40 -0700231 } catch (Bmv2FlowRuleTranslatorException e) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700232 log.warn("Unable to translate flow rule: {} - {}", e.getMessage(), rule);
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700233 continue; // next rule
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800234 }
Carmelo Cascone2954f132016-04-15 10:26:40 -0700235
236 String tableName = bmv2Entry.tableName();
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700237 Bmv2TableEntryReference entryRef = new Bmv2TableEntryReference(deviceId, tableName, bmv2Entry.matchKey());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700238
Carmelo Cascone6256d012016-06-17 13:49:52 -0700239 Lock lock = ENTRY_LOCKS.computeIfAbsent(entryRef, k -> new ReentrantLock());
240 lock.lock();
241 try {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700242 // Get from store
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700243 Bmv2FlowRuleWrapper frWrapper = tableEntryService.lookup(entryRef);
Carmelo Cascone2954f132016-04-15 10:26:40 -0700244 try {
245 if (operation == Operation.APPLY) {
246 // Apply entry
247 long entryId;
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700248 if (frWrapper != null) {
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700249 // Existing entry.
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700250 entryId = frWrapper.entryId();
251 // Tentatively delete entry before re-adding.
252 // It might not exist on device due to inconsistencies.
253 silentlyRemove(deviceAgent, entryRef.tableName(), entryId);
Carmelo Cascone2954f132016-04-15 10:26:40 -0700254 }
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700255 // Add entry.
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700256 entryId = doAddEntry(deviceAgent, bmv2Entry);
Carmelo Cascone728b1cd2016-06-24 15:21:38 -0700257 frWrapper = new Bmv2FlowRuleWrapper(rule, entryId, System.currentTimeMillis());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700258 } else {
259 // Remove entry
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700260 if (frWrapper == null) {
Carmelo Cascone2954f132016-04-15 10:26:40 -0700261 // Entry not found in map, how come?
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700262 forceRemove(deviceAgent, entryRef.tableName(), entryRef.matchKey());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700263 } else {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700264 long entryId = frWrapper.entryId();
265 doRemove(deviceAgent, entryRef.tableName(), entryId, entryRef.matchKey());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700266 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700267 frWrapper = null;
Carmelo Cascone2954f132016-04-15 10:26:40 -0700268 }
269 // If here, no exceptions... things went well :)
270 processedFlowRules.add(rule);
271 } catch (Bmv2RuntimeException e) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700272 log.warn("Unable to {} flow rule: {}", operation.name(), e.explain());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700273 }
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700274
275 // Update entryRef binding in table entry service.
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700276 if (frWrapper != null) {
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700277 tableEntryService.bind(entryRef, frWrapper);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700278 } else {
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700279 tableEntryService.unbind(entryRef);
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700280 }
Carmelo Cascone6256d012016-06-17 13:49:52 -0700281 } finally {
282 lock.unlock();
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700283 }
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800284 }
285
Carmelo Cascone2954f132016-04-15 10:26:40 -0700286 return processedFlowRules;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800287 }
288
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700289 private long doAddEntry(Bmv2DeviceAgent agent, Bmv2TableEntry entry) throws Bmv2RuntimeException {
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700290 try {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700291 return agent.addTableEntry(entry);
292 } catch (Bmv2RuntimeException e) {
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700293 if (e.getCode().equals(TABLE_DUPLICATE_ENTRY)) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700294 forceRemove(agent, entry.tableName(), entry.matchKey());
295 return agent.addTableEntry(entry);
296 } else {
297 throw e;
298 }
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700299 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700300 }
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700301
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700302 private void doRemove(Bmv2DeviceAgent agent, String tableName, long entryId, Bmv2MatchKey matchKey)
303 throws Bmv2RuntimeException {
304 try {
305 agent.deleteTableEntry(tableName, entryId);
306 } catch (Bmv2RuntimeException e) {
Carmelo Casconeee4cd7e2016-06-16 18:28:43 -0700307 if (e.getCode().equals(TABLE_INVALID_HANDLE) || e.getCode().equals(TABLE_EXPIRED_HANDLE)) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700308 // entry is not there with the declared ID, try with a forced remove.
309 forceRemove(agent, tableName, matchKey);
310 } else {
311 throw e;
312 }
313 }
314 }
315
316 private void forceRemove(Bmv2DeviceAgent agent, String tableName, Bmv2MatchKey matchKey)
317 throws Bmv2RuntimeException {
318 // Find the entryID (expensive call!)
Carmelo Cascone25f18882016-06-14 19:16:50 -0700319 for (Bmv2ParsedTableEntry pEntry : agent.getTableEntries(tableName)) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700320 if (pEntry.matchKey().equals(matchKey)) {
321 // Remove entry and drop exceptions.
322 silentlyRemove(agent, tableName, pEntry.entryId());
323 break;
324 }
325 }
326 }
327
328 private void silentlyRemove(Bmv2DeviceAgent agent, String tableName, long entryId) {
329 try {
330 agent.deleteTableEntry(tableName, entryId);
331 } catch (Bmv2RuntimeException e) {
332 // do nothing
333 }
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700334 }
335
Carmelo Cascone2954f132016-04-15 10:26:40 -0700336 private enum Operation {
337 APPLY, REMOVE
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800338 }
Carmelo Cascone2954f132016-04-15 10:26:40 -0700339}