blob: e5302356d0169836e6cb7a1bc66a1386760245e0 [file] [log] [blame]
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.drivers.bmv2;
import com.eclipsesource.json.Json;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.onosproject.bmv2.api.model.Bmv2Model;
import org.onosproject.bmv2.api.runtime.Bmv2Client;
import org.onosproject.bmv2.api.runtime.Bmv2MatchKey;
import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
import org.onosproject.bmv2.ctl.Bmv2ThriftClient;
import org.onosproject.drivers.bmv2.translators.Bmv2DefaultFlowRuleTranslator;
import org.onosproject.drivers.bmv2.translators.Bmv2FlowRuleTranslator;
import org.onosproject.drivers.bmv2.translators.Bmv2FlowRuleTranslatorException;
import org.onosproject.drivers.bmv2.translators.Bmv2SimpleTranslatorConfig;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.driver.AbstractHandlerBehaviour;
import org.onosproject.net.flow.DefaultFlowEntry;
import org.onosproject.net.flow.FlowEntry;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleProgrammable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* Flow rule programmable device behaviour implementation for BMv2.
*/
public class Bmv2FlowRuleProgrammable extends AbstractHandlerBehaviour
implements FlowRuleProgrammable {
private static final Logger LOG =
LoggerFactory.getLogger(Bmv2FlowRuleProgrammable.class);
// There's no Bmv2 client method to poll flow entries from the device device. Need a local store.
private static final ConcurrentMap<Triple<DeviceId, String, Bmv2MatchKey>, Pair<Long, FlowEntry>>
ENTRIES_MAP = Maps.newConcurrentMap();
// Cache model objects instead of parsing the JSON each time.
private static final LoadingCache<String, Bmv2Model> MODEL_CACHE = CacheBuilder.newBuilder()
.expireAfterAccess(60, TimeUnit.SECONDS)
.build(new CacheLoader<String, Bmv2Model>() {
@Override
public Bmv2Model load(String jsonString) throws Exception {
// Expensive call.
return Bmv2Model.parse(Json.parse(jsonString).asObject());
}
});
@Override
public Collection<FlowEntry> getFlowEntries() {
DeviceId deviceId = handler().data().deviceId();
List<FlowEntry> entryList = Lists.newArrayList();
// FIXME: improve this, e.g. might store a separate Map<DeviceId, Collection<FlowEntry>>
ENTRIES_MAP.forEach((key, value) -> {
if (key.getLeft() == deviceId && value != null) {
entryList.add(value.getRight());
}
});
return Collections.unmodifiableCollection(entryList);
}
@Override
public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
return processFlowRules(rules, Operation.APPLY);
}
@Override
public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
return processFlowRules(rules, Operation.REMOVE);
}
private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules, Operation operation) {
DeviceId deviceId = handler().data().deviceId();
Bmv2Client deviceClient;
try {
deviceClient = Bmv2ThriftClient.of(deviceId);
} catch (Bmv2RuntimeException e) {
LOG.error("Failed to connect to Bmv2 device", e);
return Collections.emptyList();
}
Bmv2FlowRuleTranslator translator = getTranslator(deviceId);
List<FlowRule> processedFlowRules = Lists.newArrayList();
for (FlowRule rule : rules) {
Bmv2TableEntry bmv2Entry;
try {
bmv2Entry = translator.translate(rule);
} catch (Bmv2FlowRuleTranslatorException e) {
LOG.error("Unable to translate flow rule: {}", e.getMessage());
continue;
}
String tableName = bmv2Entry.tableName();
Triple<DeviceId, String, Bmv2MatchKey> entryKey = Triple.of(deviceId, tableName, bmv2Entry.matchKey());
/*
From here on threads are synchronized over entryKey, i.e. serialize operations
over the same matchKey of a specific table and device.
*/
ENTRIES_MAP.compute(entryKey, (key, value) -> {
try {
if (operation == Operation.APPLY) {
// Apply entry
long entryId;
if (value == null) {
// New entry
entryId = deviceClient.addTableEntry(bmv2Entry);
} else {
// Existing entry
entryId = value.getKey();
// FIXME: check if priority or timeout changed
// In this case we should to re-add the entry (not modify)
deviceClient.modifyTableEntry(tableName, entryId, bmv2Entry.action());
}
// TODO: evaluate flow entry life, bytes and packets
FlowEntry flowEntry = new DefaultFlowEntry(
rule, FlowEntry.FlowEntryState.ADDED, 0, 0, 0);
value = Pair.of(entryId, flowEntry);
} else {
// Remove entry
if (value == null) {
// Entry not found in map, how come?
LOG.debug("Trying to remove entry, but entry ID not found: " + entryKey);
} else {
deviceClient.deleteTableEntry(tableName, value.getKey());
value = null;
}
}
// If here, no exceptions... things went well :)
processedFlowRules.add(rule);
} catch (Bmv2RuntimeException e) {
LOG.error("Unable to " + operation.name().toLowerCase() + " flow rule", e);
} catch (Exception e) {
LOG.error("Uncaught exception while processing flow rule", e);
}
return value;
});
}
return processedFlowRules;
}
/**
* Gets the appropriate flow rule translator based on the device running configuration.
*
* @param deviceId a device id
* @return a flow rule translator
*/
private Bmv2FlowRuleTranslator getTranslator(DeviceId deviceId) {
DeviceService deviceService = handler().get(DeviceService.class);
if (deviceService == null) {
LOG.error("Unable to get device service");
return null;
}
Device device = deviceService.getDevice(deviceId);
if (device == null) {
LOG.error("Unable to get device {}", deviceId);
return null;
}
String jsonString = device.annotations().value("bmv2JsonConfigValue");
if (jsonString == null) {
LOG.error("Unable to read bmv2 JSON config from device {}", deviceId);
return null;
}
Bmv2Model model;
try {
model = MODEL_CACHE.get(jsonString);
} catch (ExecutionException e) {
LOG.error("Unable to parse bmv2 JSON config for device {}:", deviceId, e.getCause());
return null;
}
// TODO: get translator config dynamically.
// Now it's hardcoded, selection should be based on the device bmv2 model.
Bmv2FlowRuleTranslator.TranslatorConfig translatorConfig = new Bmv2SimpleTranslatorConfig(model);
return new Bmv2DefaultFlowRuleTranslator(translatorConfig);
}
private enum Operation {
APPLY, REMOVE
}
}