blob: 27c3fc24b31ddbeeb5aba49c2ff0d0c9ae2cfbb5 [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;
27import org.onosproject.bmv2.api.runtime.Bmv2DeviceAgent;
28import org.onosproject.bmv2.api.runtime.Bmv2FlowRuleWrapper;
Carmelo Cascone2954f132016-04-15 10:26:40 -070029import org.onosproject.bmv2.api.runtime.Bmv2MatchKey;
Carmelo Cascone0831efb2016-05-31 14:50:19 -070030import org.onosproject.bmv2.api.runtime.Bmv2ParsedTableEntry;
Carmelo Casconeaa8b6292016-04-13 14:27:06 -070031import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
Carmelo Cascone2954f132016-04-15 10:26:40 -070032import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
Carmelo Cascone0831efb2016-05-31 14:50:19 -070033import org.onosproject.bmv2.api.runtime.Bmv2TableEntryReference;
34import org.onosproject.bmv2.api.service.Bmv2Controller;
35import org.onosproject.bmv2.api.service.Bmv2DeviceContextService;
36import org.onosproject.bmv2.api.service.Bmv2TableEntryService;
Carmelo Cascone2954f132016-04-15 10:26:40 -070037import org.onosproject.net.DeviceId;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080038import org.onosproject.net.driver.AbstractHandlerBehaviour;
39import org.onosproject.net.flow.DefaultFlowEntry;
40import org.onosproject.net.flow.FlowEntry;
41import org.onosproject.net.flow.FlowRule;
42import org.onosproject.net.flow.FlowRuleProgrammable;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080043import org.slf4j.Logger;
44import org.slf4j.LoggerFactory;
45
46import java.util.Collection;
47import java.util.Collections;
Carmelo Casconef8cf2882016-05-04 14:06:17 -070048import java.util.Date;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080049import java.util.List;
Carmelo Cascone2954f132016-04-15 10:26:40 -070050import java.util.concurrent.ConcurrentMap;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080051
Carmelo Cascone0831efb2016-05-31 14:50:19 -070052import static org.onosproject.bmv2.api.runtime.Bmv2RuntimeException.Code.*;
Carmelo Casconef8cf2882016-05-04 14:06:17 -070053import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
54
Carmelo Casconee9121642016-04-27 17:02:38 -070055/**
Carmelo Cascone0831efb2016-05-31 14:50:19 -070056 * Implementation of the flow rule programmable behaviour for BMv2.
Carmelo Casconee9121642016-04-27 17:02:38 -070057 */
Carmelo Cascone0831efb2016-05-31 14:50:19 -070058public class Bmv2FlowRuleProgrammable extends AbstractHandlerBehaviour implements FlowRuleProgrammable {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080059
Carmelo Cascone0831efb2016-05-31 14:50:19 -070060 private final Logger log = LoggerFactory.getLogger(this.getClass());
Carmelo Casconed4e7a772016-05-03 11:21:29 -070061
Carmelo Cascone0831efb2016-05-31 14:50:19 -070062 // Needed to synchronize operations over the same table entry.
63 private static final ConcurrentMap<Bmv2TableEntryReference, Boolean> ENTRY_LOCKS = Maps.newConcurrentMap();
Carmelo Casconed4e7a772016-05-03 11:21:29 -070064
Carmelo Cascone0831efb2016-05-31 14:50:19 -070065 private Bmv2Controller controller;
66 private Bmv2TableEntryService tableEntryService;
67 private Bmv2DeviceContextService contextService;
68
69 private boolean init() {
70 controller = handler().get(Bmv2Controller.class);
71 tableEntryService = handler().get(Bmv2TableEntryService.class);
72 contextService = handler().get(Bmv2DeviceContextService.class);
73 if (controller == null) {
74 log.warn("Failed to get a BMv2 controller");
75 return false;
76 }
77 if (tableEntryService == null) {
78 log.warn("Failed to get a BMv2 table entry service");
79 return false;
80 }
81 if (contextService == null) {
82 log.warn("Failed to get a BMv2 device context service");
83 return false;
84 }
85 return true;
86 }
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080087
88 @Override
89 public Collection<FlowEntry> getFlowEntries() {
Carmelo Cascone2954f132016-04-15 10:26:40 -070090
Carmelo Cascone0831efb2016-05-31 14:50:19 -070091 if (!init()) {
Carmelo Casconea2f510e2016-05-03 18:36:45 -070092 return Collections.emptyList();
93 }
94
Carmelo Cascone0831efb2016-05-31 14:50:19 -070095 DeviceId deviceId = handler().data().deviceId();
96
97 Bmv2DeviceAgent deviceAgent;
98 try {
99 deviceAgent = controller.getAgent(deviceId);
100 } catch (Bmv2RuntimeException e) {
101 log.error("Failed to get BMv2 device agent: {}", e.explain());
102 return Collections.emptyList();
103 }
104
105 Bmv2DeviceContext context = contextService.getContext(deviceId);
106 if (context == null) {
107 log.warn("Unable to get device context for {}", deviceId);
108 }
109
110 Bmv2Interpreter interpreter = context.interpreter();
111 Bmv2Configuration configuration = context.configuration();
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700112
Carmelo Cascone2954f132016-04-15 10:26:40 -0700113 List<FlowEntry> entryList = Lists.newArrayList();
114
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700115 configuration.tables().forEach(table -> {
116 // For each table in the configuration AND exposed by the interpreter.
117 if (!interpreter.tableIdMap().inverse().containsKey(table.name())) {
118 return;
119 }
120
Carmelo Cascone25f18882016-06-14 19:16:50 -0700121 List<Bmv2ParsedTableEntry> installedEntries;
122 try {
123 installedEntries = deviceAgent.getTableEntries(table.name());
124 } catch (Bmv2RuntimeException e) {
125 log.warn("Failed to get table entries of table {} of {}: {}", table.name(), deviceId, e.explain());
126 return;
127 }
128
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700129 installedEntries.forEach(parsedEntry -> {
130 Bmv2TableEntryReference entryRef = new Bmv2TableEntryReference(deviceId,
131 table.name(),
132 parsedEntry.matchKey());
133 ENTRY_LOCKS.compute(entryRef, (key, value) -> {
134
135 Bmv2FlowRuleWrapper frWrapper = tableEntryService.lookupEntryReference(entryRef);
136
137 if (frWrapper == null) {
138 log.warn("missing reference from table entry service, BUG? " +
139 "deviceId={}, tableName={}, matchKey={}",
140 deviceId, table.name(), entryRef.matchKey());
141 return null;
142 }
143
144 long remoteEntryId = parsedEntry.entryId();
145 long localEntryId = frWrapper.entryId();
146
147 if (remoteEntryId != localEntryId) {
148 log.warn("getFlowEntries(): inconsistent entry id! BUG? Updating it... remote={}, local={}",
149 remoteEntryId, localEntryId);
150 frWrapper = new Bmv2FlowRuleWrapper(frWrapper.rule(), remoteEntryId,
151 frWrapper.creationDate());
152 tableEntryService.bindEntryReference(entryRef, frWrapper);
153 }
154
155 long bytes = 0L;
156 long packets = 0L;
157
158 if (table.hasCounters()) {
159 // Read counter values from device.
160 try {
161 Pair<Long, Long> counterValue = deviceAgent.readTableEntryCounter(table.name(),
162 remoteEntryId);
163 bytes = counterValue.getLeft();
164 packets = counterValue.getRight();
165 } catch (Bmv2RuntimeException e) {
166 log.warn("Unable to get counters for entry {}/{} of device {}: {}",
167 table.name(), remoteEntryId, deviceId, e.explain());
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700168 }
169 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700170
171 FlowEntry entry = new DefaultFlowEntry(frWrapper.rule(), ADDED, frWrapper.lifeInSeconds(),
172 packets, bytes);
173 entryList.add(entry);
174 return true;
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700175 });
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700176
177 });
Carmelo Cascone2954f132016-04-15 10:26:40 -0700178 });
179
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 Cascone2954f132016-04-15 10:26:40 -0700229 continue;
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
235 /*
236 From here on threads are synchronized over entryKey, i.e. serialize operations
237 over the same matchKey of a specific table and device.
238 */
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700239 ENTRY_LOCKS.compute(entryRef, (key, value) -> {
240 // Get from store
241 Bmv2FlowRuleWrapper frWrapper = tableEntryService.lookupEntryReference(entryRef);
Carmelo Cascone2954f132016-04-15 10:26:40 -0700242 try {
243 if (operation == Operation.APPLY) {
244 // Apply entry
245 long entryId;
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700246 if (frWrapper != null) {
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700247 // Existing entry.
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700248 entryId = frWrapper.entryId();
249 // Tentatively delete entry before re-adding.
250 // It might not exist on device due to inconsistencies.
251 silentlyRemove(deviceAgent, entryRef.tableName(), entryId);
Carmelo Cascone2954f132016-04-15 10:26:40 -0700252 }
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700253 // Add entry.
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700254 entryId = doAddEntry(deviceAgent, bmv2Entry);
255 frWrapper = new Bmv2FlowRuleWrapper(rule, entryId, new Date());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700256 } else {
257 // Remove entry
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700258 if (frWrapper == null) {
Carmelo Cascone2954f132016-04-15 10:26:40 -0700259 // Entry not found in map, how come?
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700260 forceRemove(deviceAgent, entryRef.tableName(), entryRef.matchKey());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700261 } else {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700262 long entryId = frWrapper.entryId();
263 doRemove(deviceAgent, entryRef.tableName(), entryId, entryRef.matchKey());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700264 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700265 frWrapper = null;
Carmelo Cascone2954f132016-04-15 10:26:40 -0700266 }
267 // If here, no exceptions... things went well :)
268 processedFlowRules.add(rule);
269 } catch (Bmv2RuntimeException e) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700270 log.warn("Unable to {} flow rule: {}", operation.name(), e.explain());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700271 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700272 // Update binding in table entry service.
273 if (frWrapper != null) {
274 tableEntryService.bindEntryReference(entryRef, frWrapper);
275 return true;
276 } else {
277 tableEntryService.unbindEntryReference(entryRef);
278 return null;
279 }
Carmelo Cascone2954f132016-04-15 10:26:40 -0700280 });
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800281 }
282
Carmelo Cascone2954f132016-04-15 10:26:40 -0700283 return processedFlowRules;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800284 }
285
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700286 private long doAddEntry(Bmv2DeviceAgent agent, Bmv2TableEntry entry) throws Bmv2RuntimeException {
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700287 try {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700288 return agent.addTableEntry(entry);
289 } catch (Bmv2RuntimeException e) {
290 if (e.getCode() != TABLE_DUPLICATE_ENTRY) {
291 forceRemove(agent, entry.tableName(), entry.matchKey());
292 return agent.addTableEntry(entry);
293 } else {
294 throw e;
295 }
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700296 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700297 }
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700298
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700299 private void doRemove(Bmv2DeviceAgent agent, String tableName, long entryId, Bmv2MatchKey matchKey)
300 throws Bmv2RuntimeException {
301 try {
302 agent.deleteTableEntry(tableName, entryId);
303 } catch (Bmv2RuntimeException e) {
304 if (e.getCode() == TABLE_INVALID_HANDLE || e.getCode() == TABLE_EXPIRED_HANDLE) {
305 // entry is not there with the declared ID, try with a forced remove.
306 forceRemove(agent, tableName, matchKey);
307 } else {
308 throw e;
309 }
310 }
311 }
312
313 private void forceRemove(Bmv2DeviceAgent agent, String tableName, Bmv2MatchKey matchKey)
314 throws Bmv2RuntimeException {
315 // Find the entryID (expensive call!)
Carmelo Cascone25f18882016-06-14 19:16:50 -0700316 for (Bmv2ParsedTableEntry pEntry : agent.getTableEntries(tableName)) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700317 if (pEntry.matchKey().equals(matchKey)) {
318 // Remove entry and drop exceptions.
319 silentlyRemove(agent, tableName, pEntry.entryId());
320 break;
321 }
322 }
323 }
324
325 private void silentlyRemove(Bmv2DeviceAgent agent, String tableName, long entryId) {
326 try {
327 agent.deleteTableEntry(tableName, entryId);
328 } catch (Bmv2RuntimeException e) {
329 // do nothing
330 }
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700331 }
332
Carmelo Cascone2954f132016-04-15 10:26:40 -0700333 private enum Operation {
334 APPLY, REMOVE
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800335 }
Carmelo Cascone2954f132016-04-15 10:26:40 -0700336}