Major refactoring of the BMv2 protocol module (onos1.6 cherry-pick)

- Created 3 separate sub-modules: API (doesn't depend on
    Thrift), CTL (depends on Thrift), THRIFT-API (to generate Thrift
    sources)
- Implemented 2 new services (for device configuration swapping and
    table entry management) needed to distribute BMv2-specific state
    among ONOS instances.
- Implemented a BMv2 controller (previously other modules where
    using separately a Thrift client and a server)
- Added a default BMv2 JSON configuration (default.json) and interpreter
    to be used for devices that connect for the first time to ONOS.
    This allows for basic services to work (i.e. LLDP link discovery,
    ARP proxy. etc.).
- Changed behavior of the flow rule translator and extension selector,
    now it allows extension to specify only some of the match parameters
    (before extension selectors were expected to describe the whole
    match key, i.e. all fields)
- Various renaming to better represent the API
- Various java doc fixes / improvements

Change-Id: Ida4b5e546b0def97c3552a6c05f7bce76fd32c28
diff --git a/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2FlowRuleTranslatorImpl.java b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2FlowRuleTranslatorImpl.java
new file mode 100644
index 0000000..b3c1e8a
--- /dev/null
+++ b/protocols/bmv2/ctl/src/main/java/org/onosproject/bmv2/ctl/Bmv2FlowRuleTranslatorImpl.java
@@ -0,0 +1,394 @@
+/*
+ * 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.bmv2.ctl;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.Sets;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.bmv2.api.context.Bmv2ActionModel;
+import org.onosproject.bmv2.api.context.Bmv2Configuration;
+import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
+import org.onosproject.bmv2.api.context.Bmv2FieldModel;
+import org.onosproject.bmv2.api.context.Bmv2FlowRuleTranslator;
+import org.onosproject.bmv2.api.context.Bmv2FlowRuleTranslatorException;
+import org.onosproject.bmv2.api.context.Bmv2Interpreter;
+import org.onosproject.bmv2.api.context.Bmv2InterpreterException;
+import org.onosproject.bmv2.api.context.Bmv2RuntimeDataModel;
+import org.onosproject.bmv2.api.context.Bmv2TableKeyModel;
+import org.onosproject.bmv2.api.context.Bmv2TableModel;
+import org.onosproject.bmv2.api.runtime.Bmv2Action;
+import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam;
+import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector;
+import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment;
+import org.onosproject.bmv2.api.runtime.Bmv2LpmMatchParam;
+import org.onosproject.bmv2.api.runtime.Bmv2MatchKey;
+import org.onosproject.bmv2.api.runtime.Bmv2MatchParam;
+import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
+import org.onosproject.bmv2.api.runtime.Bmv2TernaryMatchParam;
+import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.EthCriterion;
+import org.onosproject.net.flow.criteria.EthTypeCriterion;
+import org.onosproject.net.flow.criteria.ExtensionCriterion;
+import org.onosproject.net.flow.criteria.PortCriterion;
+import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.flow.instructions.Instructions.ExtensionInstructionWrapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.roundToBytes;
+import static org.onosproject.net.flow.criteria.Criterion.Type.EXTENSION;
+import static org.onosproject.net.flow.criteria.ExtensionSelectorType.ExtensionSelectorTypes.BMV2_MATCH_PARAMS;
+
+/**
+ * Default implementation of a BMv2 flow rule translator.
+ */
+@Beta
+public class Bmv2FlowRuleTranslatorImpl implements Bmv2FlowRuleTranslator {
+
+    private final Logger log = LoggerFactory.getLogger(this.getClass());
+
+    @Override
+    public Bmv2TableEntry translate(FlowRule rule, Bmv2DeviceContext context)
+            throws Bmv2FlowRuleTranslatorException {
+
+        Bmv2Configuration configuration = context.configuration();
+        Bmv2Interpreter interpreter = context.interpreter();
+
+        int tableId = rule.tableId();
+        String tableName = interpreter.tableIdMap().get(tableId);
+
+        Bmv2TableModel table = (tableName == null) ? configuration.table(tableId) : configuration.table(tableName);
+
+        if (table == null) {
+            throw new Bmv2FlowRuleTranslatorException("Unknown table ID: " + tableId);
+        }
+
+        /* Translate selector */
+        Bmv2MatchKey bmv2MatchKey = buildMatchKey(interpreter, rule.selector(), table);
+
+        /* Translate treatment */
+        TrafficTreatment treatment = rule.treatment();
+        Bmv2Action bmv2Action = null;
+        // If treatment has only 1 instruction of type extension, use that
+        for (Instruction inst : treatment.allInstructions()) {
+            if (inst.type() == Instruction.Type.EXTENSION) {
+                if (treatment.allInstructions().size() == 1) {
+                    bmv2Action = getActionFromExtension((ExtensionInstructionWrapper) inst);
+                } else {
+                    throw new Bmv2FlowRuleTranslatorException("Unable to translate traffic treatment, found multiple " +
+                                                                      "instructions of which one is an extension: " +
+                                                                      treatment.toString());
+                }
+            }
+        }
+
+        if (bmv2Action == null) {
+            // No extension, use interpreter to build action.
+            try {
+                bmv2Action = interpreter.mapTreatment(treatment, configuration);
+            } catch (Bmv2InterpreterException e) {
+                throw new Bmv2FlowRuleTranslatorException("Unable to translate treatment. " + e.toString());
+            }
+        }
+
+        if (bmv2Action == null) {
+            // Interpreter returned null.
+            throw new Bmv2FlowRuleTranslatorException("Unable to translate treatment");
+        }
+
+        // Check action
+        Bmv2ActionModel actionModel = configuration.action(bmv2Action.name());
+        if (actionModel == null) {
+            throw new Bmv2FlowRuleTranslatorException("Unknown action " + bmv2Action.name());
+        }
+        if (!table.actions().contains(actionModel)) {
+            throw new Bmv2FlowRuleTranslatorException("Action " + bmv2Action.name()
+                                                              + " is not defined for table " + tableName);
+        }
+        if (actionModel.runtimeDatas().size() != bmv2Action.parameters().size()) {
+            throw new Bmv2FlowRuleTranslatorException("Wrong number of parameters for action "
+                                                              + actionModel.name() + ", expected "
+                                                              + actionModel.runtimeDatas().size() + ", but found "
+                                                              + bmv2Action.parameters().size());
+        }
+        for (int i = 0; i < actionModel.runtimeDatas().size(); i++) {
+            Bmv2RuntimeDataModel data = actionModel.runtimeDatas().get(i);
+            ImmutableByteSequence param = bmv2Action.parameters().get(i);
+            if (param.size() != roundToBytes(data.bitWidth())) {
+                throw new Bmv2FlowRuleTranslatorException("Wrong byte-width for parameter " + data.name()
+                                                                  + " of action " + actionModel.name()
+                                                                  + ", expected " + roundToBytes(data.bitWidth())
+                                                                  + " bytes, but found " + param.size());
+            }
+        }
+
+        Bmv2TableEntry.Builder tableEntryBuilder = Bmv2TableEntry.builder();
+
+        // In BMv2 0 is the highest priority.
+        int newPriority = Integer.MAX_VALUE - rule.priority();
+
+        tableEntryBuilder
+                .withTableName(table.name())
+                .withPriority(newPriority)
+                .withMatchKey(bmv2MatchKey)
+                .withAction(bmv2Action);
+
+        if (!rule.isPermanent()) {
+            if (table.hasTimeouts()) {
+                tableEntryBuilder.withTimeout((double) rule.timeout());
+            } else {
+                log.warn("Flow rule is temporary but table {} doesn't support timeouts, translating to permanent",
+                         table.name());
+            }
+
+        }
+
+        return tableEntryBuilder.build();
+    }
+
+    private Bmv2TernaryMatchParam buildTernaryParam(Bmv2FieldModel field, Criterion criterion, int bitWidth)
+            throws Bmv2FlowRuleTranslatorException {
+
+        // Value and mask will be filled according to criterion type
+        ImmutableByteSequence value;
+        ImmutableByteSequence mask = null;
+
+        int byteWidth = roundToBytes(bitWidth);
+
+        switch (criterion.type()) {
+            case IN_PORT:
+                long port = ((PortCriterion) criterion).port().toLong();
+                value = ImmutableByteSequence.copyFrom(port);
+                break;
+            case ETH_DST:
+                EthCriterion c = (EthCriterion) criterion;
+                value = ImmutableByteSequence.copyFrom(c.mac().toBytes());
+                if (c.mask() != null) {
+                    mask = ImmutableByteSequence.copyFrom(c.mask().toBytes());
+                }
+                break;
+            case ETH_SRC:
+                EthCriterion c2 = (EthCriterion) criterion;
+                value = ImmutableByteSequence.copyFrom(c2.mac().toBytes());
+                if (c2.mask() != null) {
+                    mask = ImmutableByteSequence.copyFrom(c2.mask().toBytes());
+                }
+                break;
+            case ETH_TYPE:
+                short ethType = ((EthTypeCriterion) criterion).ethType().toShort();
+                value = ImmutableByteSequence.copyFrom(ethType);
+                break;
+            // TODO: implement building for other criterion types (easy with DefaultCriterion of ONOS-4034)
+            default:
+                throw new Bmv2FlowRuleTranslatorException("Feature not implemented, ternary builder for criterion" +
+                                                                  "type: " + criterion.type().name());
+        }
+
+        // Fit byte sequence in field model bit-width.
+        try {
+            value = Bmv2TranslatorUtils.fitByteSequence(value, bitWidth);
+        } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
+            throw new Bmv2FlowRuleTranslatorException(
+                    "Fit exception for criterion " + criterion.type().name() + " value, " + e.getMessage());
+        }
+
+        if (mask == null) {
+            // no mask, all ones
+            mask = ImmutableByteSequence.ofOnes(byteWidth);
+        } else {
+            try {
+                mask = Bmv2TranslatorUtils.fitByteSequence(mask, bitWidth);
+            } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
+                throw new Bmv2FlowRuleTranslatorException(
+                        "Fit exception for criterion " + criterion.type().name() + " mask, " + e.getMessage());
+            }
+        }
+
+        return new Bmv2TernaryMatchParam(value, mask);
+    }
+
+    private Bmv2Action getActionFromExtension(Instructions.ExtensionInstructionWrapper inst)
+            throws Bmv2FlowRuleTranslatorException {
+
+        ExtensionTreatment extTreatment = inst.extensionInstruction();
+
+        if (extTreatment.type() == ExtensionTreatmentTypes.BMV2_ACTION.type()) {
+            if (extTreatment instanceof Bmv2ExtensionTreatment) {
+                return ((Bmv2ExtensionTreatment) extTreatment).getAction();
+            } else {
+                throw new Bmv2FlowRuleTranslatorException("Unable to decode treatment extension: " + extTreatment);
+            }
+        } else {
+            throw new Bmv2FlowRuleTranslatorException("Unsupported treatment extension type: " + extTreatment.type());
+        }
+    }
+
+    private Bmv2MatchKey buildMatchKey(Bmv2Interpreter interpreter, TrafficSelector selector, Bmv2TableModel tableModel)
+            throws Bmv2FlowRuleTranslatorException {
+
+        // Find a bmv2 extension selector (if any) and get the parameter map.
+        Optional<Bmv2ExtensionSelector> extSelector = selector.criteria().stream()
+                .filter(c -> c.type().equals(EXTENSION))
+                .map(c -> (ExtensionCriterion) c)
+                .map(ExtensionCriterion::extensionSelector)
+                .filter(c -> c.type().equals(BMV2_MATCH_PARAMS.type()))
+                .map(c -> (Bmv2ExtensionSelector) c)
+                .findFirst();
+        Map<String, Bmv2MatchParam> extParamMap =
+                (extSelector.isPresent()) ? extSelector.get().parameterMap() : Collections.emptyMap();
+
+        Set<Criterion> translatedCriteria = Sets.newHashSet();
+        Set<String> usedExtParams = Sets.newHashSet();
+
+        Bmv2MatchKey.Builder matchKeyBuilder = Bmv2MatchKey.builder();
+
+        keysLoop:
+        for (Bmv2TableKeyModel keyModel : tableModel.keys()) {
+
+            // use fieldName dot notation (e.g. ethernet.dstAddr)
+            String fieldName = keyModel.field().header().name() + "." + keyModel.field().type().name();
+
+            int bitWidth = keyModel.field().type().bitWidth();
+            int byteWidth = roundToBytes(bitWidth);
+
+            Criterion.Type criterionType = interpreter.criterionTypeMap().inverse().get(fieldName);
+
+            if (!extParamMap.containsKey(fieldName) &&
+                    (criterionType == null || selector.getCriterion(criterionType) == null)) {
+                // Neither an extension nor a mapping / criterion is available for this field.
+                switch (keyModel.matchType()) {
+                    case TERNARY:
+                        // Wildcard field
+                        matchKeyBuilder.withWildcard(byteWidth);
+                        break;
+                    case LPM:
+                        // LPM with prefix 0
+                        matchKeyBuilder.add(new Bmv2LpmMatchParam(ImmutableByteSequence.ofZeros(byteWidth), 0));
+                        break;
+                    default:
+                        throw new Bmv2FlowRuleTranslatorException("No value found for required match field "
+                                                                          + fieldName);
+                }
+                // Next key
+                continue keysLoop;
+            }
+
+            Bmv2MatchParam matchParam;
+
+            if (extParamMap.containsKey(fieldName)) {
+                // Parameter found in extension
+                if (criterionType != null && selector.getCriterion(criterionType) != null) {
+                    // Found also a criterion that can be mapped. This is bad.
+                    throw new Bmv2FlowRuleTranslatorException("Both an extension and a criterion mapping are defined " +
+                                                                      "for match field " + fieldName);
+                }
+
+                matchParam = extParamMap.get(fieldName);
+                usedExtParams.add(fieldName);
+
+                // Check parameter type and size
+                if (!keyModel.matchType().equals(matchParam.type())) {
+                    throw new Bmv2FlowRuleTranslatorException("Wrong match type for parameter " + fieldName
+                                                                      + ", expected " + keyModel.matchType().name()
+                                                                      + ", but found " + matchParam.type().name());
+                }
+                int foundByteWidth;
+                switch (keyModel.matchType()) {
+                    case EXACT:
+                        Bmv2ExactMatchParam m1 = (Bmv2ExactMatchParam) matchParam;
+                        foundByteWidth = m1.value().size();
+                        break;
+                    case TERNARY:
+                        Bmv2TernaryMatchParam m2 = (Bmv2TernaryMatchParam) matchParam;
+                        foundByteWidth = m2.value().size();
+                        break;
+                    case LPM:
+                        Bmv2LpmMatchParam m3 = (Bmv2LpmMatchParam) matchParam;
+                        foundByteWidth = m3.value().size();
+                        break;
+                    case VALID:
+                        foundByteWidth = -1;
+                        break;
+                    default:
+                        // should never be her
+                        throw new RuntimeException("Unrecognized match type " + keyModel.matchType().name());
+                }
+                if (foundByteWidth != -1 && foundByteWidth != byteWidth) {
+                    throw new Bmv2FlowRuleTranslatorException("Wrong byte-width for match parameter " + fieldName
+                                                                      + ", expected " + byteWidth + ", but found "
+                                                                      + foundByteWidth);
+                }
+
+            } else {
+                // A criterion mapping is available for this key
+                Criterion criterion = selector.getCriterion(criterionType);
+                translatedCriteria.add(criterion);
+                switch (keyModel.matchType()) {
+                    case TERNARY:
+                        matchParam = buildTernaryParam(keyModel.field(), criterion, bitWidth);
+                        break;
+                    default:
+                        // TODO: implement other match param builders (exact, LPM, etc.)
+                        throw new Bmv2FlowRuleTranslatorException("Feature not yet implemented, match param builder: "
+                                                                          + keyModel.matchType().name());
+                }
+            }
+
+            matchKeyBuilder.add(matchParam);
+        }
+
+        // Check if all criteria have been translated
+        Set<Criterion> ignoredCriteria = selector.criteria()
+                .stream()
+                .filter(c -> !c.type().equals(EXTENSION))
+                .filter(c -> !translatedCriteria.contains(c))
+                .collect(Collectors.toSet());
+
+        if (ignoredCriteria.size() > 0) {
+            throw new Bmv2FlowRuleTranslatorException("The following criteria cannot be translated for table "
+                                                              + tableModel.name() + ": " + ignoredCriteria.toString());
+        }
+
+        // Check is all extension parameters have been used
+        Set<String> ignoredExtParams = extParamMap.keySet()
+                .stream()
+                .filter(k -> !usedExtParams.contains(k))
+                .collect(Collectors.toSet());
+
+        if (ignoredExtParams.size() > 0) {
+            throw new Bmv2FlowRuleTranslatorException("The following extension match parameters cannot be used for " +
+                                                              "table " + tableModel.name() + ": "
+                                                              + ignoredExtParams.toString());
+        }
+
+        return matchKeyBuilder.build();
+    }
+
+}