Carmelo Cascone

Translator-based Bmv2 flow rule driver

Removed old parsing logic. Now it uses Bmv2FlowRuleTranslator to
translate ONOS flow rule into Bmv2 model-dependent table entries.

Change-Id: I1febc23b334acade027e806c8a8c266acc061277
/*
* Copyright 2014-2016 Open Networking Laboratory
* 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.
......@@ -16,221 +16,146 @@
package org.onosproject.drivers.bmv2;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector;
import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment;
import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
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.net.DeviceId;
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.onosproject.net.flow.criteria.Criterion;
import org.onosproject.net.flow.criteria.ExtensionCriterion;
import org.onosproject.net.flow.criteria.ExtensionSelector;
import org.onosproject.net.flow.criteria.ExtensionSelectorType;
import org.onosproject.net.flow.instructions.ExtensionTreatment;
import org.onosproject.net.flow.instructions.ExtensionTreatmentType;
import org.onosproject.net.flow.instructions.Instruction;
import org.onosproject.net.flow.instructions.Instructions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
public class Bmv2FlowRuleDriver extends AbstractHandlerBehaviour
implements FlowRuleProgrammable {
private final Logger log =
LoggerFactory.getLogger(this.getClass());
// Bmv2 doesn't support proper table dump, use a local store
// FIXME: synchronize entries with device
private final Map<FlowRule, FlowEntry> deviceEntriesMap = Maps.newHashMap();
private final Map<Integer, Set<FlowRule>> tableRulesMap = Maps.newHashMap();
private final Map<FlowRule, Long> tableEntryIdsMap = Maps.newHashMap();
private static final Logger LOG =
LoggerFactory.getLogger(Bmv2FlowRuleDriver.class);
// There's no Bmv2 client method to poll flow entries from the device device. gitNeed a local store.
private static final ConcurrentMap<Triple<DeviceId, String, Bmv2MatchKey>, Pair<Long, FlowEntry>>
ENTRIES_MAP = Maps.newConcurrentMap();
private static final Bmv2FlowRuleTranslator TRANSLATOR = new Bmv2DefaultFlowRuleTranslator();
@Override
public Collection<FlowEntry> getFlowEntries() {
return Collections.unmodifiableCollection(
deviceEntriesMap.values());
}
@Override
public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Bmv2ThriftClient deviceClient;
try {
deviceClient = getDeviceClient();
} catch (Bmv2RuntimeException e) {
return Collections.emptyList();
}
List<FlowRule> appliedFlowRules = Lists.newArrayList();
for (FlowRule rule : rules) {
DeviceId deviceId = handler().data().deviceId();
Bmv2TableEntry entry;
List<FlowEntry> entryList = Lists.newArrayList();
try {
entry = parseFlowRule(rule);
} catch (IllegalStateException e) {
log.error("Unable to parse flow rule", e);
continue;
// 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());
}
});
// Instantiate flowrule set for table if it does not exist
if (!tableRulesMap.containsKey(rule.tableId())) {
tableRulesMap.put(rule.tableId(), Sets.newHashSet());
}
return Collections.unmodifiableCollection(entryList);
}
if (tableRulesMap.get(rule.tableId()).contains(rule)) {
/* Rule is already installed in the table */
long entryId = tableEntryIdsMap.get(rule);
@Override
public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
try {
deviceClient.modifyTableEntry(
entry.tableName(), entryId, entry.action());
// Replace stored rule as treatment, etc. might have changed
// Java Set doesn't replace on add, remove first
tableRulesMap.get(rule.tableId()).remove(rule);
tableRulesMap.get(rule.tableId()).add(rule);
tableEntryIdsMap.put(rule, entryId);
deviceEntriesMap.put(rule, new DefaultFlowEntry(
rule, FlowEntry.FlowEntryState.ADDED, 0, 0, 0));
} catch (Bmv2RuntimeException e) {
log.error("Unable to update flow rule", e);
continue;
}
return processFlowRules(rules, Operation.APPLY);
}
} else {
/* Rule is new */
try {
long entryId = deviceClient.addTableEntry(entry);
@Override
public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
tableRulesMap.get(rule.tableId()).add(rule);
tableEntryIdsMap.put(rule, entryId);
deviceEntriesMap.put(rule, new DefaultFlowEntry(
rule, FlowEntry.FlowEntryState.ADDED, 0, 0, 0));
} catch (Bmv2RuntimeException e) {
log.error("Unable to add flow rule", e);
continue;
}
}
return processFlowRules(rules, Operation.REMOVE);
}
appliedFlowRules.add(rule);
}
private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules, Operation operation) {
return Collections.unmodifiableCollection(appliedFlowRules);
}
DeviceId deviceId = handler().data().deviceId();
@Override
public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Bmv2ThriftClient deviceClient;
try {
deviceClient = getDeviceClient();
deviceClient = Bmv2ThriftClient.of(deviceId);
} catch (Bmv2RuntimeException e) {
LOG.error("Failed to connect to Bmv2 device", e);
return Collections.emptyList();
}
List<FlowRule> removedFlowRules = Lists.newArrayList();
List<FlowRule> processedFlowRules = Lists.newArrayList();
for (FlowRule rule : rules) {
if (tableEntryIdsMap.containsKey(rule)) {
long entryId = tableEntryIdsMap.get(rule);
String tableName = parseTableName(rule.tableId());
try {
deviceClient.deleteTableEntry(tableName, entryId);
} catch (Bmv2RuntimeException e) {
log.error("Unable to delete flow rule", e);
continue;
}
/* remove from local store */
tableEntryIdsMap.remove(rule);
tableRulesMap.get(rule.tableId()).remove(rule);
deviceEntriesMap.remove(rule);
Bmv2TableEntry bmv2Entry;
removedFlowRules.add(rule);
try {
bmv2Entry = TRANSLATOR.translate(rule);
} catch (Bmv2FlowRuleTranslatorException e) {
LOG.error("Unable to translate flow rule: {}", e.getMessage());
continue;
}
}
return Collections.unmodifiableCollection(removedFlowRules);
}
String tableName = bmv2Entry.tableName();
Triple<DeviceId, String, Bmv2MatchKey> entryKey = Triple.of(deviceId, tableName, bmv2Entry.matchKey());
private Bmv2TableEntry parseFlowRule(FlowRule flowRule) {
// TODO make it pipeline dependant, i.e. implement mapping
Bmv2TableEntry.Builder entryBuilder = Bmv2TableEntry.builder();
// Check selector
ExtensionCriterion ec =
(ExtensionCriterion) flowRule
.selector().getCriterion(Criterion.Type.EXTENSION);
Preconditions.checkState(
flowRule.selector().criteria().size() == 1
&& ec != null,
"Selector must have only 1 criterion of type EXTENSION");
ExtensionSelector es = ec.extensionSelector();
Preconditions.checkState(
es.type() == ExtensionSelectorType.ExtensionSelectorTypes.P4_BMV2_MATCH_KEY.type(),
"ExtensionSelectorType must be P4_BMV2_MATCH_KEY");
// Selector OK, get Bmv2MatchKey
entryBuilder.withMatchKey(((Bmv2ExtensionSelector) es).matchKey());
// Check treatment
Instruction inst = flowRule.treatment().allInstructions().get(0);
Preconditions.checkState(
flowRule.treatment().allInstructions().size() == 1
&& inst.type() == Instruction.Type.EXTENSION,
"Treatment must have only 1 instruction of type EXTENSION");
ExtensionTreatment et =
((Instructions.ExtensionInstructionWrapper) inst)
.extensionInstruction();
Preconditions.checkState(
et.type() == ExtensionTreatmentType.ExtensionTreatmentTypes.P4_BMV2_ACTION.type(),
"ExtensionTreatmentType must be P4_BMV2_ACTION");
// Treatment OK, get Bmv2Action
entryBuilder.withAction(((Bmv2ExtensionTreatment) et).getAction());
// Table name
entryBuilder.withTableName(parseTableName(flowRule.tableId()));
if (!flowRule.isPermanent()) {
entryBuilder.withTimeout(flowRule.timeout());
/*
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;
});
}
entryBuilder.withPriority(flowRule.priority());
return entryBuilder.build();
}
private String parseTableName(int tableId) {
// TODO: map tableId with tableName according to P4 JSON
return "table" + String.valueOf(tableId);
return processedFlowRules;
}
private Bmv2ThriftClient getDeviceClient() throws Bmv2RuntimeException {
try {
return Bmv2ThriftClient.of(handler().data().deviceId());
} catch (Bmv2RuntimeException e) {
log.error("Failed to connect to Bmv2 device", e);
throw e;
}
private enum Operation {
APPLY, REMOVE
}
}
}
\ No newline at end of file
......