blob: 7d78fe3a6f78d291f23c51885409f7ee6084ceda [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 Casconed4e7a772016-05-03 11:21:29 -070019import com.eclipsesource.json.Json;
20import com.google.common.cache.CacheBuilder;
21import com.google.common.cache.CacheLoader;
22import com.google.common.cache.LoadingCache;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080023import com.google.common.collect.Lists;
24import com.google.common.collect.Maps;
Carmelo Casconea2f510e2016-05-03 18:36:45 -070025import com.google.common.collect.Sets;
Carmelo Cascone2954f132016-04-15 10:26:40 -070026import org.apache.commons.lang3.tuple.Pair;
27import org.apache.commons.lang3.tuple.Triple;
Carmelo Casconed4e7a772016-05-03 11:21:29 -070028import org.onosproject.bmv2.api.model.Bmv2Model;
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -070029import org.onosproject.bmv2.api.runtime.Bmv2Client;
Carmelo Cascone2954f132016-04-15 10:26:40 -070030import org.onosproject.bmv2.api.runtime.Bmv2MatchKey;
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 Cascone2ea177b2016-02-25 18:38:42 -080033import org.onosproject.bmv2.ctl.Bmv2ThriftClient;
Carmelo Cascone2954f132016-04-15 10:26:40 -070034import org.onosproject.drivers.bmv2.translators.Bmv2DefaultFlowRuleTranslator;
35import org.onosproject.drivers.bmv2.translators.Bmv2FlowRuleTranslator;
36import org.onosproject.drivers.bmv2.translators.Bmv2FlowRuleTranslatorException;
Carmelo Casconed4e7a772016-05-03 11:21:29 -070037import org.onosproject.drivers.bmv2.translators.Bmv2SimpleTranslatorConfig;
38import org.onosproject.net.Device;
Carmelo Cascone2954f132016-04-15 10:26:40 -070039import org.onosproject.net.DeviceId;
Carmelo Casconed4e7a772016-05-03 11:21:29 -070040import org.onosproject.net.device.DeviceService;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080041import org.onosproject.net.driver.AbstractHandlerBehaviour;
42import org.onosproject.net.flow.DefaultFlowEntry;
43import org.onosproject.net.flow.FlowEntry;
44import org.onosproject.net.flow.FlowRule;
45import org.onosproject.net.flow.FlowRuleProgrammable;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080046import org.slf4j.Logger;
47import org.slf4j.LoggerFactory;
48
49import java.util.Collection;
50import java.util.Collections;
Carmelo Casconef8cf2882016-05-04 14:06:17 -070051import java.util.Date;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080052import java.util.List;
Carmelo Casconea2f510e2016-05-03 18:36:45 -070053import java.util.Set;
Carmelo Cascone2954f132016-04-15 10:26:40 -070054import java.util.concurrent.ConcurrentMap;
Carmelo Casconed4e7a772016-05-03 11:21:29 -070055import java.util.concurrent.ExecutionException;
56import java.util.concurrent.TimeUnit;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080057
Carmelo Casconef8cf2882016-05-04 14:06:17 -070058import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
59
Carmelo Casconee9121642016-04-27 17:02:38 -070060/**
61 * Flow rule programmable device behaviour implementation for BMv2.
62 */
63public class Bmv2FlowRuleProgrammable extends AbstractHandlerBehaviour
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080064 implements FlowRuleProgrammable {
65
Carmelo Cascone2954f132016-04-15 10:26:40 -070066 private static final Logger LOG =
Carmelo Casconee9121642016-04-27 17:02:38 -070067 LoggerFactory.getLogger(Bmv2FlowRuleProgrammable.class);
Carmelo Casconed4e7a772016-05-03 11:21:29 -070068
Carmelo Casconea2f510e2016-05-03 18:36:45 -070069 // There's no Bmv2 client method to poll flow entries from the device. Use a local store.
70 // FIXME: this information should be distributed across instances of the cluster.
Carmelo Casconef8cf2882016-05-04 14:06:17 -070071 private static final ConcurrentMap<Triple<DeviceId, String, Bmv2MatchKey>, Pair<Long, TimestampedFlowRule>>
Carmelo Cascone2954f132016-04-15 10:26:40 -070072 ENTRIES_MAP = Maps.newConcurrentMap();
Carmelo Casconed4e7a772016-05-03 11:21:29 -070073
74 // Cache model objects instead of parsing the JSON each time.
75 private static final LoadingCache<String, Bmv2Model> MODEL_CACHE = CacheBuilder.newBuilder()
76 .expireAfterAccess(60, TimeUnit.SECONDS)
77 .build(new CacheLoader<String, Bmv2Model>() {
78 @Override
79 public Bmv2Model load(String jsonString) throws Exception {
80 // Expensive call.
81 return Bmv2Model.parse(Json.parse(jsonString).asObject());
82 }
83 });
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080084
85 @Override
86 public Collection<FlowEntry> getFlowEntries() {
Carmelo Cascone2954f132016-04-15 10:26:40 -070087
88 DeviceId deviceId = handler().data().deviceId();
89
Carmelo Casconea2f510e2016-05-03 18:36:45 -070090 Bmv2Client deviceClient;
91 try {
92 deviceClient = Bmv2ThriftClient.of(deviceId);
93 } catch (Bmv2RuntimeException e) {
94 LOG.error("Failed to connect to Bmv2 device", e);
95 return Collections.emptyList();
96 }
97
98 Bmv2Model model = getTranslator(deviceId).config().model();
99
Carmelo Cascone2954f132016-04-15 10:26:40 -0700100 List<FlowEntry> entryList = Lists.newArrayList();
101
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700102 model.tables().forEach(table -> {
103 // For each table declared in the model for this device, do:
104 try {
105 // Bmv2 doesn't support proper polling for table entries, but only a string based table dump.
106 // The trick here is to first dump the entry ids currently installed in the device for a given table,
107 // and then filter ENTRIES_MAP based on the retrieved values.
108 Set<Long> installedEntryIds = Sets.newHashSet(deviceClient.getInstalledEntryIds(table.name()));
109 ENTRIES_MAP.forEach((key, value) -> {
110 if (key.getLeft() == deviceId && key.getMiddle() == table.name()
111 && value != null) {
Carmelo Casconef8cf2882016-05-04 14:06:17 -0700112 long entryId = value.getKey();
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700113 // Filter entries_map for this device and table.
Carmelo Casconef8cf2882016-05-04 14:06:17 -0700114 if (installedEntryIds.contains(entryId)) {
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700115 // Entry is installed.
Carmelo Casconef8cf2882016-05-04 14:06:17 -0700116 long bytes = 0L;
117 long packets = 0L;
118 if (table.hasCounters()) {
119 // Read counter values from device.
120 try {
121 Pair<Long, Long> counterValue = deviceClient.readTableEntryCounter(table.name(),
122 entryId);
123 bytes = counterValue.getLeft();
124 packets = counterValue.getRight();
125 } catch (Bmv2RuntimeException e) {
126 LOG.warn("Unable to get counter values for entry {} of table {} of device {}: {}",
127 entryId, table.name(), deviceId, e.toString());
128 }
129 }
130 TimestampedFlowRule tsRule = value.getRight();
131 FlowEntry entry = new DefaultFlowEntry(tsRule.rule(), ADDED,
132 tsRule.lifeInSeconds(), packets, bytes);
133 entryList.add(entry);
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700134 } else {
135 // No such entry on device, can remove from local store.
136 ENTRIES_MAP.remove(key);
137 }
138 }
139 });
140 } catch (Bmv2RuntimeException e) {
141 LOG.error("Unable to get flow entries for table {} of device {}: {}",
142 table.name(), deviceId, e.toString());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700143 }
144 });
145
146 return Collections.unmodifiableCollection(entryList);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800147 }
148
149 @Override
150 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800151
Carmelo Cascone2954f132016-04-15 10:26:40 -0700152 return processFlowRules(rules, Operation.APPLY);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800153 }
154
155 @Override
156 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone2954f132016-04-15 10:26:40 -0700157
158 return processFlowRules(rules, Operation.REMOVE);
159 }
160
161 private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules, Operation operation) {
162
163 DeviceId deviceId = handler().data().deviceId();
164
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700165 Bmv2Client deviceClient;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800166 try {
Carmelo Cascone2954f132016-04-15 10:26:40 -0700167 deviceClient = Bmv2ThriftClient.of(deviceId);
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700168 } catch (Bmv2RuntimeException e) {
Carmelo Cascone2954f132016-04-15 10:26:40 -0700169 LOG.error("Failed to connect to Bmv2 device", e);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800170 return Collections.emptyList();
171 }
172
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700173 Bmv2FlowRuleTranslator translator = getTranslator(deviceId);
174
Carmelo Cascone2954f132016-04-15 10:26:40 -0700175 List<FlowRule> processedFlowRules = Lists.newArrayList();
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800176
177 for (FlowRule rule : rules) {
178
Carmelo Cascone2954f132016-04-15 10:26:40 -0700179 Bmv2TableEntry bmv2Entry;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800180
Carmelo Cascone2954f132016-04-15 10:26:40 -0700181 try {
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700182 bmv2Entry = translator.translate(rule);
Carmelo Cascone2954f132016-04-15 10:26:40 -0700183 } catch (Bmv2FlowRuleTranslatorException e) {
184 LOG.error("Unable to translate flow rule: {}", e.getMessage());
185 continue;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800186 }
Carmelo Cascone2954f132016-04-15 10:26:40 -0700187
188 String tableName = bmv2Entry.tableName();
189 Triple<DeviceId, String, Bmv2MatchKey> entryKey = Triple.of(deviceId, tableName, bmv2Entry.matchKey());
190
191 /*
192 From here on threads are synchronized over entryKey, i.e. serialize operations
193 over the same matchKey of a specific table and device.
194 */
195 ENTRIES_MAP.compute(entryKey, (key, value) -> {
196 try {
197 if (operation == Operation.APPLY) {
198 // Apply entry
199 long entryId;
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700200 if (value != null) {
201 // Existing entry.
Carmelo Cascone2954f132016-04-15 10:26:40 -0700202 entryId = value.getKey();
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700203 try {
204 // Tentatively delete entry before re-adding.
205 // It might not exist on device due to inconsistencies.
206 deviceClient.deleteTableEntry(bmv2Entry.tableName(), entryId);
Carmelo Casconef8cf2882016-05-04 14:06:17 -0700207 value = null;
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700208 } catch (Bmv2RuntimeException e) {
209 // Silently drop exception as we can probably fix this by re-adding the entry.
210 }
Carmelo Cascone2954f132016-04-15 10:26:40 -0700211 }
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700212 // Add entry.
213 entryId = deviceClient.addTableEntry(bmv2Entry);
Carmelo Casconef8cf2882016-05-04 14:06:17 -0700214 value = Pair.of(entryId, new TimestampedFlowRule(rule));
Carmelo Cascone2954f132016-04-15 10:26:40 -0700215 } else {
216 // Remove entry
217 if (value == null) {
218 // Entry not found in map, how come?
219 LOG.debug("Trying to remove entry, but entry ID not found: " + entryKey);
220 } else {
221 deviceClient.deleteTableEntry(tableName, value.getKey());
222 value = null;
223 }
224 }
225 // If here, no exceptions... things went well :)
226 processedFlowRules.add(rule);
227 } catch (Bmv2RuntimeException e) {
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700228 LOG.warn("Unable to {} flow rule: {}", operation.name().toLowerCase(), e.toString());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700229 }
230 return value;
231 });
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800232 }
233
Carmelo Cascone2954f132016-04-15 10:26:40 -0700234 return processedFlowRules;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800235 }
236
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700237 /**
238 * Gets the appropriate flow rule translator based on the device running configuration.
239 *
240 * @param deviceId a device id
241 * @return a flow rule translator
242 */
243 private Bmv2FlowRuleTranslator getTranslator(DeviceId deviceId) {
244
245 DeviceService deviceService = handler().get(DeviceService.class);
246 if (deviceService == null) {
247 LOG.error("Unable to get device service");
248 return null;
249 }
250
251 Device device = deviceService.getDevice(deviceId);
252 if (device == null) {
253 LOG.error("Unable to get device {}", deviceId);
254 return null;
255 }
256
257 String jsonString = device.annotations().value("bmv2JsonConfigValue");
258 if (jsonString == null) {
259 LOG.error("Unable to read bmv2 JSON config from device {}", deviceId);
260 return null;
261 }
262
263 Bmv2Model model;
264 try {
265 model = MODEL_CACHE.get(jsonString);
266 } catch (ExecutionException e) {
267 LOG.error("Unable to parse bmv2 JSON config for device {}:", deviceId, e.getCause());
268 return null;
269 }
270
271 // TODO: get translator config dynamically.
272 // Now it's hardcoded, selection should be based on the device bmv2 model.
273 Bmv2FlowRuleTranslator.TranslatorConfig translatorConfig = new Bmv2SimpleTranslatorConfig(model);
274 return new Bmv2DefaultFlowRuleTranslator(translatorConfig);
275 }
276
Carmelo Cascone2954f132016-04-15 10:26:40 -0700277 private enum Operation {
278 APPLY, REMOVE
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800279 }
Carmelo Casconef8cf2882016-05-04 14:06:17 -0700280
281 private class TimestampedFlowRule {
282 private final FlowRule rule;
283 private final Date addedDate;
284
285 public TimestampedFlowRule(FlowRule rule) {
286 this.rule = rule;
287 this.addedDate = new Date();
288 }
289
290 public FlowRule rule() {
291 return rule;
292 }
293
294 public long lifeInSeconds() {
295 return (new Date().getTime() - addedDate.getTime()) / 1000;
296 }
297 }
Carmelo Cascone2954f132016-04-15 10:26:40 -0700298}