blob: 3f7a3ec5af71a2a604ab8c5292969818b0ba1fc5 [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;
51import java.util.List;
Carmelo Casconea2f510e2016-05-03 18:36:45 -070052import java.util.Set;
Carmelo Cascone2954f132016-04-15 10:26:40 -070053import java.util.concurrent.ConcurrentMap;
Carmelo Casconed4e7a772016-05-03 11:21:29 -070054import java.util.concurrent.ExecutionException;
55import java.util.concurrent.TimeUnit;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080056
Carmelo Casconee9121642016-04-27 17:02:38 -070057/**
58 * Flow rule programmable device behaviour implementation for BMv2.
59 */
60public class Bmv2FlowRuleProgrammable extends AbstractHandlerBehaviour
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080061 implements FlowRuleProgrammable {
62
Carmelo Cascone2954f132016-04-15 10:26:40 -070063 private static final Logger LOG =
Carmelo Casconee9121642016-04-27 17:02:38 -070064 LoggerFactory.getLogger(Bmv2FlowRuleProgrammable.class);
Carmelo Casconed4e7a772016-05-03 11:21:29 -070065
Carmelo Casconea2f510e2016-05-03 18:36:45 -070066 // There's no Bmv2 client method to poll flow entries from the device. Use a local store.
67 // FIXME: this information should be distributed across instances of the cluster.
Carmelo Cascone2954f132016-04-15 10:26:40 -070068 private static final ConcurrentMap<Triple<DeviceId, String, Bmv2MatchKey>, Pair<Long, FlowEntry>>
69 ENTRIES_MAP = Maps.newConcurrentMap();
Carmelo Casconed4e7a772016-05-03 11:21:29 -070070
71 // Cache model objects instead of parsing the JSON each time.
72 private static final LoadingCache<String, Bmv2Model> MODEL_CACHE = CacheBuilder.newBuilder()
73 .expireAfterAccess(60, TimeUnit.SECONDS)
74 .build(new CacheLoader<String, Bmv2Model>() {
75 @Override
76 public Bmv2Model load(String jsonString) throws Exception {
77 // Expensive call.
78 return Bmv2Model.parse(Json.parse(jsonString).asObject());
79 }
80 });
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080081
82 @Override
83 public Collection<FlowEntry> getFlowEntries() {
Carmelo Cascone2954f132016-04-15 10:26:40 -070084
85 DeviceId deviceId = handler().data().deviceId();
86
Carmelo Casconea2f510e2016-05-03 18:36:45 -070087 Bmv2Client deviceClient;
88 try {
89 deviceClient = Bmv2ThriftClient.of(deviceId);
90 } catch (Bmv2RuntimeException e) {
91 LOG.error("Failed to connect to Bmv2 device", e);
92 return Collections.emptyList();
93 }
94
95 Bmv2Model model = getTranslator(deviceId).config().model();
96
Carmelo Cascone2954f132016-04-15 10:26:40 -070097 List<FlowEntry> entryList = Lists.newArrayList();
98
Carmelo Casconea2f510e2016-05-03 18:36:45 -070099 model.tables().forEach(table -> {
100 // For each table declared in the model for this device, do:
101 try {
102 // Bmv2 doesn't support proper polling for table entries, but only a string based table dump.
103 // The trick here is to first dump the entry ids currently installed in the device for a given table,
104 // and then filter ENTRIES_MAP based on the retrieved values.
105 Set<Long> installedEntryIds = Sets.newHashSet(deviceClient.getInstalledEntryIds(table.name()));
106 ENTRIES_MAP.forEach((key, value) -> {
107 if (key.getLeft() == deviceId && key.getMiddle() == table.name()
108 && value != null) {
109 // Filter entries_map for this device and table.
110 if (installedEntryIds.contains(value.getKey())) {
111 // Entry is installed.
112 entryList.add(value.getRight());
113 } else {
114 // No such entry on device, can remove from local store.
115 ENTRIES_MAP.remove(key);
116 }
117 }
118 });
119 } catch (Bmv2RuntimeException e) {
120 LOG.error("Unable to get flow entries for table {} of device {}: {}",
121 table.name(), deviceId, e.toString());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700122 }
123 });
124
125 return Collections.unmodifiableCollection(entryList);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800126 }
127
128 @Override
129 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800130
Carmelo Cascone2954f132016-04-15 10:26:40 -0700131 return processFlowRules(rules, Operation.APPLY);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800132 }
133
134 @Override
135 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone2954f132016-04-15 10:26:40 -0700136
137 return processFlowRules(rules, Operation.REMOVE);
138 }
139
140 private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules, Operation operation) {
141
142 DeviceId deviceId = handler().data().deviceId();
143
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700144 Bmv2Client deviceClient;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800145 try {
Carmelo Cascone2954f132016-04-15 10:26:40 -0700146 deviceClient = Bmv2ThriftClient.of(deviceId);
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700147 } catch (Bmv2RuntimeException e) {
Carmelo Cascone2954f132016-04-15 10:26:40 -0700148 LOG.error("Failed to connect to Bmv2 device", e);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800149 return Collections.emptyList();
150 }
151
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700152 Bmv2FlowRuleTranslator translator = getTranslator(deviceId);
153
Carmelo Cascone2954f132016-04-15 10:26:40 -0700154 List<FlowRule> processedFlowRules = Lists.newArrayList();
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800155
156 for (FlowRule rule : rules) {
157
Carmelo Cascone2954f132016-04-15 10:26:40 -0700158 Bmv2TableEntry bmv2Entry;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800159
Carmelo Cascone2954f132016-04-15 10:26:40 -0700160 try {
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700161 bmv2Entry = translator.translate(rule);
Carmelo Cascone2954f132016-04-15 10:26:40 -0700162 } catch (Bmv2FlowRuleTranslatorException e) {
163 LOG.error("Unable to translate flow rule: {}", e.getMessage());
164 continue;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800165 }
Carmelo Cascone2954f132016-04-15 10:26:40 -0700166
167 String tableName = bmv2Entry.tableName();
168 Triple<DeviceId, String, Bmv2MatchKey> entryKey = Triple.of(deviceId, tableName, bmv2Entry.matchKey());
169
170 /*
171 From here on threads are synchronized over entryKey, i.e. serialize operations
172 over the same matchKey of a specific table and device.
173 */
174 ENTRIES_MAP.compute(entryKey, (key, value) -> {
175 try {
176 if (operation == Operation.APPLY) {
177 // Apply entry
178 long entryId;
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700179 if (value != null) {
180 // Existing entry.
Carmelo Cascone2954f132016-04-15 10:26:40 -0700181 entryId = value.getKey();
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700182 try {
183 // Tentatively delete entry before re-adding.
184 // It might not exist on device due to inconsistencies.
185 deviceClient.deleteTableEntry(bmv2Entry.tableName(), entryId);
186 } catch (Bmv2RuntimeException e) {
187 // Silently drop exception as we can probably fix this by re-adding the entry.
188 }
Carmelo Cascone2954f132016-04-15 10:26:40 -0700189 }
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700190 // Add entry.
191 entryId = deviceClient.addTableEntry(bmv2Entry);
Carmelo Cascone2954f132016-04-15 10:26:40 -0700192 // TODO: evaluate flow entry life, bytes and packets
193 FlowEntry flowEntry = new DefaultFlowEntry(
194 rule, FlowEntry.FlowEntryState.ADDED, 0, 0, 0);
195 value = Pair.of(entryId, flowEntry);
196 } else {
197 // Remove entry
198 if (value == null) {
199 // Entry not found in map, how come?
200 LOG.debug("Trying to remove entry, but entry ID not found: " + entryKey);
201 } else {
202 deviceClient.deleteTableEntry(tableName, value.getKey());
203 value = null;
204 }
205 }
206 // If here, no exceptions... things went well :)
207 processedFlowRules.add(rule);
208 } catch (Bmv2RuntimeException e) {
Carmelo Casconea2f510e2016-05-03 18:36:45 -0700209 LOG.warn("Unable to {} flow rule: {}", operation.name().toLowerCase(), e.toString());
Carmelo Cascone2954f132016-04-15 10:26:40 -0700210 }
211 return value;
212 });
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800213 }
214
Carmelo Cascone2954f132016-04-15 10:26:40 -0700215 return processedFlowRules;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800216 }
217
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700218 /**
219 * Gets the appropriate flow rule translator based on the device running configuration.
220 *
221 * @param deviceId a device id
222 * @return a flow rule translator
223 */
224 private Bmv2FlowRuleTranslator getTranslator(DeviceId deviceId) {
225
226 DeviceService deviceService = handler().get(DeviceService.class);
227 if (deviceService == null) {
228 LOG.error("Unable to get device service");
229 return null;
230 }
231
232 Device device = deviceService.getDevice(deviceId);
233 if (device == null) {
234 LOG.error("Unable to get device {}", deviceId);
235 return null;
236 }
237
238 String jsonString = device.annotations().value("bmv2JsonConfigValue");
239 if (jsonString == null) {
240 LOG.error("Unable to read bmv2 JSON config from device {}", deviceId);
241 return null;
242 }
243
244 Bmv2Model model;
245 try {
246 model = MODEL_CACHE.get(jsonString);
247 } catch (ExecutionException e) {
248 LOG.error("Unable to parse bmv2 JSON config for device {}:", deviceId, e.getCause());
249 return null;
250 }
251
252 // TODO: get translator config dynamically.
253 // Now it's hardcoded, selection should be based on the device bmv2 model.
254 Bmv2FlowRuleTranslator.TranslatorConfig translatorConfig = new Bmv2SimpleTranslatorConfig(model);
255 return new Bmv2DefaultFlowRuleTranslator(translatorConfig);
256 }
257
Carmelo Cascone2954f132016-04-15 10:26:40 -0700258 private enum Operation {
259 APPLY, REMOVE
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800260 }
Carmelo Cascone2954f132016-04-15 10:26:40 -0700261}