blob: 01d812fe6908ef2c589d9dcb86f34fc1bb269b08 [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
121 // Bmv2 doesn't support proper polling for table entries, but only a string based table dump.
122 // The trick here is to first dump the entries currently installed in the device for a given table,
123 // and then query a service for the corresponding, previously applied, flow rule.
124 List<Bmv2ParsedTableEntry> installedEntries = tableEntryService.getTableEntries(deviceId, table.name());
125 installedEntries.forEach(parsedEntry -> {
126 Bmv2TableEntryReference entryRef = new Bmv2TableEntryReference(deviceId,
127 table.name(),
128 parsedEntry.matchKey());
129 ENTRY_LOCKS.compute(entryRef, (key, value) -> {
130
131 Bmv2FlowRuleWrapper frWrapper = tableEntryService.lookupEntryReference(entryRef);
132
133 if (frWrapper == null) {
134 log.warn("missing reference from table entry service, BUG? " +
135 "deviceId={}, tableName={}, matchKey={}",
136 deviceId, table.name(), entryRef.matchKey());
137 return null;
138 }
139
140 long remoteEntryId = parsedEntry.entryId();
141 long localEntryId = frWrapper.entryId();
142
143 if (remoteEntryId != localEntryId) {
144 log.warn("getFlowEntries(): inconsistent entry id! BUG? Updating it... remote={}, local={}",
145 remoteEntryId, localEntryId);
146 frWrapper = new Bmv2FlowRuleWrapper(frWrapper.rule(), remoteEntryId,
147 frWrapper.creationDate());
148 tableEntryService.bindEntryReference(entryRef, frWrapper);
149 }
150
151 long bytes = 0L;
152 long packets = 0L;
153
154 if (table.hasCounters()) {
155 // Read counter values from device.
156 try {
157 Pair<Long, Long> counterValue = deviceAgent.readTableEntryCounter(table.name(),
158 remoteEntryId);
159 bytes = counterValue.getLeft();
160 packets = counterValue.getRight();
161 } catch (Bmv2RuntimeException e) {
162 log.warn("Unable to get counters for entry {}/{} of device {}: {}",
163 table.name(), remoteEntryId, deviceId, e.explain());
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700164 }
165 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700166
167 FlowEntry entry = new DefaultFlowEntry(frWrapper.rule(), ADDED, frWrapper.lifeInSeconds(),
168 packets, bytes);
169 entryList.add(entry);
170 return true;
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700171 });
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700172
173 });
Carmelo Cascone2954f132016-04-15 10:26:40 -0700174 });
175
176 return Collections.unmodifiableCollection(entryList);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800177 }
178
179 @Override
180 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800181
Carmelo Cascone2954f132016-04-15 10:26:40 -0700182 return processFlowRules(rules, Operation.APPLY);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800183 }
184
185 @Override
186 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone2954f132016-04-15 10:26:40 -0700187
188 return processFlowRules(rules, Operation.REMOVE);
189 }
190
191 private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules, Operation operation) {
192
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700193 if (!init()) {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800194 return Collections.emptyList();
195 }
196
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700197 DeviceId deviceId = handler().data().deviceId();
198
199 Bmv2DeviceAgent deviceAgent;
200 try {
201 deviceAgent = controller.getAgent(deviceId);
202 } catch (Bmv2RuntimeException e) {
203 log.error("Failed to get BMv2 device agent: {}", e.explain());
204 return Collections.emptyList();
205 }
206
207 Bmv2DeviceContext context = contextService.getContext(deviceId);
208 if (context == null) {
209 log.error("Unable to get device context for {}", deviceId);
210 return Collections.emptyList();
211 }
212
213 Bmv2FlowRuleTranslator translator = tableEntryService.getFlowRuleTranslator();
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700214
Carmelo Cascone2954f132016-04-15 10:26:40 -0700215 List<FlowRule> processedFlowRules = Lists.newArrayList();
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800216
217 for (FlowRule rule : rules) {
218
Carmelo Cascone2954f132016-04-15 10:26:40 -0700219 Bmv2TableEntry bmv2Entry;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800220
Carmelo Cascone2954f132016-04-15 10:26:40 -0700221 try {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700222 bmv2Entry = translator.translate(rule, context);
Carmelo Cascone2954f132016-04-15 10:26:40 -0700223 } catch (Bmv2FlowRuleTranslatorException e) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700224 log.warn("Unable to translate flow rule: {} - {}", e.getMessage(), rule);
Carmelo Cascone2954f132016-04-15 10:26:40 -0700225 continue;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800226 }
Carmelo Cascone2954f132016-04-15 10:26:40 -0700227
228 String tableName = bmv2Entry.tableName();
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700229 Bmv2TableEntryReference entryRef = new Bmv2TableEntryReference(deviceId, tableName, bmv2Entry.matchKey());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700230
231 /*
232 From here on threads are synchronized over entryKey, i.e. serialize operations
233 over the same matchKey of a specific table and device.
234 */
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700235 ENTRY_LOCKS.compute(entryRef, (key, value) -> {
236 // Get from store
237 Bmv2FlowRuleWrapper frWrapper = tableEntryService.lookupEntryReference(entryRef);
Carmelo Cascone2954f132016-04-15 10:26:40 -0700238 try {
239 if (operation == Operation.APPLY) {
240 // Apply entry
241 long entryId;
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700242 if (frWrapper != null) {
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700243 // Existing entry.
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700244 entryId = frWrapper.entryId();
245 // Tentatively delete entry before re-adding.
246 // It might not exist on device due to inconsistencies.
247 silentlyRemove(deviceAgent, entryRef.tableName(), entryId);
Carmelo Cascone2954f132016-04-15 10:26:40 -0700248 }
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700249 // Add entry.
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700250 entryId = doAddEntry(deviceAgent, bmv2Entry);
251 frWrapper = new Bmv2FlowRuleWrapper(rule, entryId, new Date());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700252 } else {
253 // Remove entry
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700254 if (frWrapper == null) {
Carmelo Cascone2954f132016-04-15 10:26:40 -0700255 // Entry not found in map, how come?
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700256 forceRemove(deviceAgent, entryRef.tableName(), entryRef.matchKey());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700257 } else {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700258 long entryId = frWrapper.entryId();
259 doRemove(deviceAgent, entryRef.tableName(), entryId, entryRef.matchKey());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700260 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700261 frWrapper = null;
Carmelo Cascone2954f132016-04-15 10:26:40 -0700262 }
263 // If here, no exceptions... things went well :)
264 processedFlowRules.add(rule);
265 } catch (Bmv2RuntimeException e) {
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700266 log.warn("Unable to {} flow rule: {}", operation.name(), e.explain());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700267 }
Carmelo Cascone0831efb2016-05-31 14:50:19 -0700268 // Update binding in table entry service.
269 if (frWrapper != null) {
270 tableEntryService.bindEntryReference(entryRef, frWrapper);
271 return true;
272 } else {
273 tableEntryService.unbindEntryReference(entryRef);
274 return null;
275 }
Carmelo Cascone2954f132016-04-15 10:26:40 -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) {
286 if (e.getCode() != TABLE_DUPLICATE_ENTRY) {
287 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) {
300 if (e.getCode() == TABLE_INVALID_HANDLE || e.getCode() == TABLE_EXPIRED_HANDLE) {
301 // 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!)
312 for (Bmv2ParsedTableEntry pEntry : tableEntryService.getTableEntries(agent.deviceId(), tableName)) {
313 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}