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
diff --git a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2FlowRuleDriver.java b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2FlowRuleDriver.java
index c226e60..3c6acf5 100644
--- a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2FlowRuleDriver.java
+++ b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2FlowRuleDriver.java
@@ -1,5 +1,5 @@
/*
- * 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());
+
+ 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) {
- Bmv2ThriftClient deviceClient;
- try {
- deviceClient = getDeviceClient();
- } catch (Bmv2RuntimeException e) {
- return Collections.emptyList();
- }
- List<FlowRule> appliedFlowRules = Lists.newArrayList();
-
- for (FlowRule rule : rules) {
-
- Bmv2TableEntry entry;
-
- try {
- entry = parseFlowRule(rule);
- } catch (IllegalStateException e) {
- log.error("Unable to parse flow rule", e);
- continue;
- }
-
- // Instantiate flowrule set for table if it does not exist
- if (!tableRulesMap.containsKey(rule.tableId())) {
- tableRulesMap.put(rule.tableId(), Sets.newHashSet());
- }
-
- if (tableRulesMap.get(rule.tableId()).contains(rule)) {
- /* Rule is already installed in the table */
- long entryId = tableEntryIdsMap.get(rule);
-
- 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;
- }
-
- } else {
- /* Rule is new */
- try {
- long entryId = deviceClient.addTableEntry(entry);
-
- 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;
- }
- }
-
- appliedFlowRules.add(rule);
- }
-
- return Collections.unmodifiableCollection(appliedFlowRules);
+ 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();
+
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());
+ Bmv2TableEntry bmv2Entry;
- 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);
-
- removedFlowRules.add(rule);
+ 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 Collections.unmodifiableCollection(removedFlowRules);
+ return processedFlowRules;
}
- 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());
- }
-
- entryBuilder.withPriority(flowRule.priority());
-
- return entryBuilder.build();
+ private enum Operation {
+ APPLY, REMOVE
}
-
- private String parseTableName(int tableId) {
- // TODO: map tableId with tableName according to P4 JSON
- return "table" + String.valueOf(tableId);
- }
-
- 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;
- }
- }
-}
+}
\ No newline at end of file