blob: e5302356d0169836e6cb7a1bc66a1386760245e0 [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 Cascone2954f132016-04-15 10:26:40 -070025import org.apache.commons.lang3.tuple.Pair;
26import org.apache.commons.lang3.tuple.Triple;
Carmelo Casconed4e7a772016-05-03 11:21:29 -070027import org.onosproject.bmv2.api.model.Bmv2Model;
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -070028import org.onosproject.bmv2.api.runtime.Bmv2Client;
Carmelo Cascone2954f132016-04-15 10:26:40 -070029import org.onosproject.bmv2.api.runtime.Bmv2MatchKey;
Carmelo Casconeaa8b6292016-04-13 14:27:06 -070030import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
Carmelo Cascone2954f132016-04-15 10:26:40 -070031import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080032import org.onosproject.bmv2.ctl.Bmv2ThriftClient;
Carmelo Cascone2954f132016-04-15 10:26:40 -070033import org.onosproject.drivers.bmv2.translators.Bmv2DefaultFlowRuleTranslator;
34import org.onosproject.drivers.bmv2.translators.Bmv2FlowRuleTranslator;
35import org.onosproject.drivers.bmv2.translators.Bmv2FlowRuleTranslatorException;
Carmelo Casconed4e7a772016-05-03 11:21:29 -070036import org.onosproject.drivers.bmv2.translators.Bmv2SimpleTranslatorConfig;
37import org.onosproject.net.Device;
Carmelo Cascone2954f132016-04-15 10:26:40 -070038import org.onosproject.net.DeviceId;
Carmelo Casconed4e7a772016-05-03 11:21:29 -070039import org.onosproject.net.device.DeviceService;
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 Casconed4e7a772016-05-03 11:21:29 -070052import java.util.concurrent.ExecutionException;
53import java.util.concurrent.TimeUnit;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080054
Carmelo Casconee9121642016-04-27 17:02:38 -070055/**
56 * Flow rule programmable device behaviour implementation for BMv2.
57 */
58public class Bmv2FlowRuleProgrammable extends AbstractHandlerBehaviour
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080059 implements FlowRuleProgrammable {
60
Carmelo Cascone2954f132016-04-15 10:26:40 -070061 private static final Logger LOG =
Carmelo Casconee9121642016-04-27 17:02:38 -070062 LoggerFactory.getLogger(Bmv2FlowRuleProgrammable.class);
Carmelo Casconed4e7a772016-05-03 11:21:29 -070063
64 // There's no Bmv2 client method to poll flow entries from the device device. Need a local store.
Carmelo Cascone2954f132016-04-15 10:26:40 -070065 private static final ConcurrentMap<Triple<DeviceId, String, Bmv2MatchKey>, Pair<Long, FlowEntry>>
66 ENTRIES_MAP = Maps.newConcurrentMap();
Carmelo Casconed4e7a772016-05-03 11:21:29 -070067
68 // Cache model objects instead of parsing the JSON each time.
69 private static final LoadingCache<String, Bmv2Model> MODEL_CACHE = CacheBuilder.newBuilder()
70 .expireAfterAccess(60, TimeUnit.SECONDS)
71 .build(new CacheLoader<String, Bmv2Model>() {
72 @Override
73 public Bmv2Model load(String jsonString) throws Exception {
74 // Expensive call.
75 return Bmv2Model.parse(Json.parse(jsonString).asObject());
76 }
77 });
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080078
79 @Override
80 public Collection<FlowEntry> getFlowEntries() {
Carmelo Cascone2954f132016-04-15 10:26:40 -070081
82 DeviceId deviceId = handler().data().deviceId();
83
84 List<FlowEntry> entryList = Lists.newArrayList();
85
86 // FIXME: improve this, e.g. might store a separate Map<DeviceId, Collection<FlowEntry>>
87 ENTRIES_MAP.forEach((key, value) -> {
88 if (key.getLeft() == deviceId && value != null) {
89 entryList.add(value.getRight());
90 }
91 });
92
93 return Collections.unmodifiableCollection(entryList);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080094 }
95
96 @Override
97 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080098
Carmelo Cascone2954f132016-04-15 10:26:40 -070099 return processFlowRules(rules, Operation.APPLY);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800100 }
101
102 @Override
103 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone2954f132016-04-15 10:26:40 -0700104
105 return processFlowRules(rules, Operation.REMOVE);
106 }
107
108 private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules, Operation operation) {
109
110 DeviceId deviceId = handler().data().deviceId();
111
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700112 Bmv2Client deviceClient;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800113 try {
Carmelo Cascone2954f132016-04-15 10:26:40 -0700114 deviceClient = Bmv2ThriftClient.of(deviceId);
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700115 } catch (Bmv2RuntimeException e) {
Carmelo Cascone2954f132016-04-15 10:26:40 -0700116 LOG.error("Failed to connect to Bmv2 device", e);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800117 return Collections.emptyList();
118 }
119
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700120 Bmv2FlowRuleTranslator translator = getTranslator(deviceId);
121
Carmelo Cascone2954f132016-04-15 10:26:40 -0700122 List<FlowRule> processedFlowRules = Lists.newArrayList();
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800123
124 for (FlowRule rule : rules) {
125
Carmelo Cascone2954f132016-04-15 10:26:40 -0700126 Bmv2TableEntry bmv2Entry;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800127
Carmelo Cascone2954f132016-04-15 10:26:40 -0700128 try {
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700129 bmv2Entry = translator.translate(rule);
Carmelo Cascone2954f132016-04-15 10:26:40 -0700130 } catch (Bmv2FlowRuleTranslatorException e) {
131 LOG.error("Unable to translate flow rule: {}", e.getMessage());
132 continue;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800133 }
Carmelo Cascone2954f132016-04-15 10:26:40 -0700134
135 String tableName = bmv2Entry.tableName();
136 Triple<DeviceId, String, Bmv2MatchKey> entryKey = Triple.of(deviceId, tableName, bmv2Entry.matchKey());
137
138 /*
139 From here on threads are synchronized over entryKey, i.e. serialize operations
140 over the same matchKey of a specific table and device.
141 */
142 ENTRIES_MAP.compute(entryKey, (key, value) -> {
143 try {
144 if (operation == Operation.APPLY) {
145 // Apply entry
146 long entryId;
147 if (value == null) {
148 // New entry
149 entryId = deviceClient.addTableEntry(bmv2Entry);
150 } else {
151 // Existing entry
152 entryId = value.getKey();
153 // FIXME: check if priority or timeout changed
154 // In this case we should to re-add the entry (not modify)
155 deviceClient.modifyTableEntry(tableName, entryId, bmv2Entry.action());
156 }
157 // TODO: evaluate flow entry life, bytes and packets
158 FlowEntry flowEntry = new DefaultFlowEntry(
159 rule, FlowEntry.FlowEntryState.ADDED, 0, 0, 0);
160 value = Pair.of(entryId, flowEntry);
161 } else {
162 // Remove entry
163 if (value == null) {
164 // Entry not found in map, how come?
165 LOG.debug("Trying to remove entry, but entry ID not found: " + entryKey);
166 } else {
167 deviceClient.deleteTableEntry(tableName, value.getKey());
168 value = null;
169 }
170 }
171 // If here, no exceptions... things went well :)
172 processedFlowRules.add(rule);
173 } catch (Bmv2RuntimeException e) {
174 LOG.error("Unable to " + operation.name().toLowerCase() + " flow rule", e);
175 } catch (Exception e) {
176 LOG.error("Uncaught exception while processing flow rule", e);
177 }
178 return value;
179 });
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800180 }
181
Carmelo Cascone2954f132016-04-15 10:26:40 -0700182 return processedFlowRules;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800183 }
184
Carmelo Casconed4e7a772016-05-03 11:21:29 -0700185 /**
186 * Gets the appropriate flow rule translator based on the device running configuration.
187 *
188 * @param deviceId a device id
189 * @return a flow rule translator
190 */
191 private Bmv2FlowRuleTranslator getTranslator(DeviceId deviceId) {
192
193 DeviceService deviceService = handler().get(DeviceService.class);
194 if (deviceService == null) {
195 LOG.error("Unable to get device service");
196 return null;
197 }
198
199 Device device = deviceService.getDevice(deviceId);
200 if (device == null) {
201 LOG.error("Unable to get device {}", deviceId);
202 return null;
203 }
204
205 String jsonString = device.annotations().value("bmv2JsonConfigValue");
206 if (jsonString == null) {
207 LOG.error("Unable to read bmv2 JSON config from device {}", deviceId);
208 return null;
209 }
210
211 Bmv2Model model;
212 try {
213 model = MODEL_CACHE.get(jsonString);
214 } catch (ExecutionException e) {
215 LOG.error("Unable to parse bmv2 JSON config for device {}:", deviceId, e.getCause());
216 return null;
217 }
218
219 // TODO: get translator config dynamically.
220 // Now it's hardcoded, selection should be based on the device bmv2 model.
221 Bmv2FlowRuleTranslator.TranslatorConfig translatorConfig = new Bmv2SimpleTranslatorConfig(model);
222 return new Bmv2DefaultFlowRuleTranslator(translatorConfig);
223 }
224
Carmelo Cascone2954f132016-04-15 10:26:40 -0700225 private enum Operation {
226 APPLY, REMOVE
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800227 }
Carmelo Cascone2954f132016-04-15 10:26:40 -0700228}