Improve fabric.p4 to reduce pipeline resources and refactor pipeconf impl

This patch affects both the P4 pipeline implementation and the
Java pipeconf.

P4 PIPELINE
- Less tables and smarter use of metadata to reduce inter-tables
dependencies and favor parallel execution of tables.
- Removed unused actions / renamed existing ones to make forwarding
behavior clearer (e.g. ingress_port_vlan table)
- Remove co-existence of simple and hansed table. Hashed should be the
default one, but implementations that do not support action profiles
might compile fabric.p4 to use the simple one.
- Use @name annotations for match fields to make control plane
independent of table implementation.
- Use @hidden to avoid showing actions and table on the p4info that
cannot be controlled at runtime.
- First attempt to support double VLAN cross-connect (xconnect table).
- New design has been tested with "fabric-refactoring" branch of
fabric-p4test:
github.com/opennetworkinglab/fabric-p4test/tree/fabric-refactoring

JAVA PIPECONF
This patch brings a major refactoring that reflects the experience
gathered in the past months of working on fabric.p4 and reasoning on its
pipeconf implementation. Indeed, the FlowObjective API is
under-specified and sometimes ambiguous which makes the process of
creating and maintaining a pipeliner implementation tedious. This
refactoring brings a simplified implementation by removing unused/
unnecessary functionalities and by recognizing commonality when possible
(e.g. by means of abstract and utility classes). It also makes design
patterns more explicit and consistent. Overall, the goal is to reduce
technical debt and to make it easier to support new features as we
evolve fabric.p4

Changes include:
- Changes in pipeliner/interpreter to reflect new pipeline design.
- By default translate objective treatment to PiAction. This favors
debuggability of flow rules in ONOS.
- Support new NextObjective’s NextTreatment class.
- Remove lots of unused/unnecessary code (e.g. async callback handling
for pending objective install status in pipeliner as current
implementation was always returning success)
- Gather commonality in abstract classes and simplify implementation
for objective translator (filtering, forwarding, next)
- New implementation of ForwardingFunctionTypes (FFT) that looks at
criterion instance values along with their types (to avoid relying on
case-specific if-else conditions to recognize variants of an FFT)
- Adaptive translation of NextObjective based on presence of simple or
hashed table.
- Support DENY FilteringObjective

Also:
- Fix onos-p4-gen-constants to avoid generating conflicting
PiMatchFieldId variable names.
- Install Graphviz tools in p4vm to generate p4c graphs
- Generate p4c graphs by default when compiling fabric.p4
- Use more compact Hex string when printing PI values

Change-Id: Ife79e44054dc5bc48833f95d0551a7370150eac5
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricIntProgrammable.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricIntProgrammable.java
new file mode 100644
index 0000000..a2bf569
--- /dev/null
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricIntProgrammable.java
@@ -0,0 +1,487 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.pipelines.fabric;
+
+import com.google.common.collect.Sets;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.inbandtelemetry.api.IntConfig;
+import org.onosproject.inbandtelemetry.api.IntIntent;
+import org.onosproject.inbandtelemetry.api.IntObjective;
+import org.onosproject.inbandtelemetry.api.IntProgrammable;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.TableId;
+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.IPCriterion;
+import org.onosproject.net.flow.criteria.PiCriterion;
+import org.onosproject.net.flow.criteria.TcpPortCriterion;
+import org.onosproject.net.flow.criteria.UdpPortCriterion;
+import org.onosproject.net.pi.model.PiTableId;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.provider.general.device.api.GeneralProviderDeviceConfig;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
+
+/**
+ * Implementation of INT programmable behavior for fabric.p4. Currently supports
+ * only SOURCE and TRANSIT functionalities.
+ */
+public class FabricIntProgrammable extends AbstractFabricHandlerBehavior
+        implements IntProgrammable {
+
+    // TODO: change this value to the value of diameter of a network.
+    private static final int DEFAULT_PRIORITY = 10000;
+    private static final int MAXHOP = 64;
+    private static final int PORTMASK = 0xffff;
+    private static final int PKT_INSTANCE_TYPE_INGRESS_CLONE = 1;
+
+    private static final Set<Criterion.Type> SUPPORTED_CRITERION = Sets.newHashSet(
+            Criterion.Type.IPV4_DST, Criterion.Type.IPV4_SRC,
+            Criterion.Type.UDP_SRC, Criterion.Type.UDP_DST,
+            Criterion.Type.TCP_SRC, Criterion.Type.TCP_DST,
+            Criterion.Type.IP_PROTO);
+
+    private static final Set<PiTableId> TABLES_TO_CLEANUP = Sets.newHashSet(
+            FabricConstants.FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_TB_INT_INSERT,
+            FabricConstants.FABRIC_INGRESS_PROCESS_SET_SOURCE_SINK_TB_SET_SOURCE,
+            FabricConstants.FABRIC_INGRESS_PROCESS_SET_SOURCE_SINK_TB_SET_SINK,
+            FabricConstants.FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_SOURCE_TB_INT_SOURCE,
+            FabricConstants.FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_REPORT_TB_GENERATE_REPORT
+    );
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    private FlowRuleService flowRuleService;
+
+    private CoreService coreService;
+    private NetworkConfigService cfgService;
+    private DeviceId deviceId;
+    private ApplicationId appId;
+
+    private boolean setupBehaviour() {
+        deviceId = this.data().deviceId();
+        flowRuleService = handler().get(FlowRuleService.class);
+        coreService = handler().get(CoreService.class);
+        cfgService = handler().get(NetworkConfigService.class);
+        appId = coreService.getAppId(PipeconfLoader.PIPELINE_APP_NAME);
+        if (appId == null) {
+            log.warn("Application ID is null. Cannot initialize behaviour.");
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean init() {
+
+        if (!setupBehaviour()) {
+            return false;
+        }
+
+        final GeneralProviderDeviceConfig cfg = cfgService.getConfig(
+                deviceId, GeneralProviderDeviceConfig.class);
+        if (cfg == null) {
+            log.warn("Missing GeneralProviderDevice config for {}", deviceId);
+            return false;
+        }
+        final String switchId = cfg.protocolsInfo().containsKey("int") ?
+                cfg.protocolsInfo().get("int").configValues().get("switchId")
+                : null;
+        if (switchId == null || switchId.isEmpty()) {
+            log.warn("Missing INT device config for {}", deviceId);
+            return false;
+        }
+
+        PiActionParam transitIdParam;
+        try {
+            transitIdParam = new PiActionParam(
+                    FabricConstants.SWITCH_ID,
+                    // FIXME set switch ID from netcfg
+                    copyFrom(Integer.parseInt(switchId)));
+        } catch (NumberFormatException e) {
+            log.warn("Invalid INT switch ID for {}: {}", deviceId, switchId);
+            return false;
+        }
+
+        PiAction transitAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_INIT_METADATA)
+                .withParameter(transitIdParam)
+                .build();
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .piTableAction(transitAction)
+                .build();
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchPi(PiCriterion.builder().matchExact(
+                        FabricConstants.HDR_INT_IS_VALID, (byte) 0x01)
+                                 .build())
+                .build();
+
+        FlowRule transitFlowRule = DefaultFlowRule.builder()
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .fromApp(appId)
+                .withPriority(DEFAULT_PRIORITY)
+                .makePermanent()
+                .forDevice(deviceId)
+                .forTable(FabricConstants.FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_TRANSIT_TB_INT_INSERT)
+                .build();
+
+        flowRuleService.applyFlowRules(transitFlowRule);
+        return true;
+    }
+
+    @Override
+    public boolean setSourcePort(PortNumber port) {
+
+        if (!setupBehaviour()) {
+            return false;
+        }
+
+        PiCriterion ingressCriterion = PiCriterion.builder()
+                .matchExact(FabricConstants.HDR_IG_PORT, port.toLong())
+                .build();
+        TrafficSelector srcSelector = DefaultTrafficSelector.builder()
+                .matchPi(ingressCriterion)
+                .build();
+        PiAction setSourceAct = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_PROCESS_SET_SOURCE_SINK_INT_SET_SOURCE)
+                .build();
+        TrafficTreatment srcTreatment = DefaultTrafficTreatment.builder()
+                .piTableAction(setSourceAct)
+                .build();
+        FlowRule srcFlowRule = DefaultFlowRule.builder()
+                .withSelector(srcSelector)
+                .withTreatment(srcTreatment)
+                .fromApp(appId)
+                .withPriority(DEFAULT_PRIORITY)
+                .makePermanent()
+                .forDevice(deviceId)
+                .forTable(FabricConstants.FABRIC_INGRESS_PROCESS_SET_SOURCE_SINK_TB_SET_SOURCE)
+                .build();
+        flowRuleService.applyFlowRules(srcFlowRule);
+        return true;
+    }
+
+    @Override
+    public boolean setSinkPort(PortNumber port) {
+
+        if (!setupBehaviour()) {
+            return false;
+        }
+
+        PiCriterion egressCriterion = PiCriterion.builder()
+                .matchExact(FabricConstants.HDR_EG_PORT, port.toLong())
+                .build();
+        TrafficSelector sinkSelector = DefaultTrafficSelector.builder()
+                .matchPi(egressCriterion)
+                .build();
+        PiAction setSinkAct = PiAction.builder()
+                .withId(FabricConstants.FABRIC_INGRESS_PROCESS_SET_SOURCE_SINK_INT_SET_SINK)
+                .build();
+        TrafficTreatment sinkTreatment = DefaultTrafficTreatment.builder()
+                .piTableAction(setSinkAct)
+                .build();
+        FlowRule sinkFlowRule = DefaultFlowRule.builder()
+                .withSelector(sinkSelector)
+                .withTreatment(sinkTreatment)
+                .fromApp(appId)
+                .withPriority(DEFAULT_PRIORITY)
+                .makePermanent()
+                .forDevice(deviceId)
+                .forTable(FabricConstants.FABRIC_INGRESS_PROCESS_SET_SOURCE_SINK_TB_SET_SINK)
+                .build();
+        flowRuleService.applyFlowRules(sinkFlowRule);
+        return true;
+    }
+
+    @Override
+    public boolean addIntObjective(IntObjective obj) {
+
+        if (!setupBehaviour()) {
+            return false;
+        }
+
+        return processIntObjective(obj, true);
+    }
+
+    @Override
+    public boolean removeIntObjective(IntObjective obj) {
+
+        if (!setupBehaviour()) {
+            return false;
+        }
+
+        return processIntObjective(obj, false);
+    }
+
+    @Override
+    public boolean setupIntConfig(IntConfig config) {
+
+        if (!setupBehaviour()) {
+            return false;
+        }
+
+        return setupIntReportInternal(config);
+    }
+
+    @Override
+    public void cleanup() {
+
+        if (!setupBehaviour()) {
+            return;
+        }
+
+        StreamSupport.stream(flowRuleService.getFlowEntries(
+                data().deviceId()).spliterator(), false)
+                .filter(f -> f.table().type() == TableId.Type.PIPELINE_INDEPENDENT)
+                .filter(f -> TABLES_TO_CLEANUP.contains((PiTableId) f.table()))
+                .forEach(flowRuleService::removeFlowRules);
+    }
+
+    @Override
+    public boolean supportsFunctionality(IntFunctionality functionality) {
+        // Sink not fully supported yet.
+        return functionality == IntFunctionality.SOURCE || functionality == IntFunctionality.TRANSIT;
+    }
+
+    private FlowRule buildWatchlistEntry(IntObjective obj) {
+        int instructionBitmap = buildInstructionBitmap(obj.metadataTypes());
+        PiActionParam maxHopParam = new PiActionParam(
+                FabricConstants.MAX_HOP,
+                copyFrom(MAXHOP));
+        PiActionParam instCntParam = new PiActionParam(
+                FabricConstants.INS_CNT,
+                copyFrom(Integer.bitCount(instructionBitmap)));
+        PiActionParam inst0003Param = new PiActionParam(
+                FabricConstants.INS_MASK0003,
+                copyFrom((instructionBitmap >> 12) & 0xF));
+        PiActionParam inst0407Param = new PiActionParam(
+                FabricConstants.INS_MASK0407,
+                copyFrom((instructionBitmap >> 8) & 0xF));
+
+        PiAction intSourceAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_SOURCE_INT_SOURCE_DSCP)
+                .withParameter(maxHopParam)
+                .withParameter(instCntParam)
+                .withParameter(inst0003Param)
+                .withParameter(inst0407Param)
+                .build();
+
+        TrafficTreatment instTreatment = DefaultTrafficTreatment.builder()
+                .piTableAction(intSourceAction)
+                .build();
+
+        TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+        for (Criterion criterion : obj.selector().criteria()) {
+            switch (criterion.type()) {
+                case IPV4_SRC:
+                    sBuilder.matchIPSrc(((IPCriterion) criterion).ip());
+                    break;
+                case IPV4_DST:
+                    sBuilder.matchIPDst(((IPCriterion) criterion).ip());
+                    break;
+                case TCP_SRC:
+                    sBuilder.matchPi(
+                            PiCriterion.builder().matchTernary(
+                                    FabricConstants.HDR_L4_SPORT,
+                                    ((TcpPortCriterion) criterion).tcpPort().toInt(), PORTMASK)
+                                    .build());
+                    break;
+                case UDP_SRC:
+                    sBuilder.matchPi(
+                            PiCriterion.builder().matchTernary(
+                                    FabricConstants.HDR_L4_SPORT,
+                                    ((UdpPortCriterion) criterion).udpPort().toInt(), PORTMASK)
+                                    .build());
+                    break;
+                case TCP_DST:
+                    sBuilder.matchPi(
+                            PiCriterion.builder().matchTernary(
+                                    FabricConstants.HDR_L4_DPORT,
+                                    ((TcpPortCriterion) criterion).tcpPort().toInt(), PORTMASK)
+                                    .build());
+                    break;
+                case UDP_DST:
+                    sBuilder.matchPi(
+                            PiCriterion.builder().matchTernary(
+                                    FabricConstants.HDR_L4_DPORT,
+                                    ((UdpPortCriterion) criterion).udpPort().toInt(), PORTMASK)
+                                    .build());
+                    break;
+                default:
+                    log.warn("Unsupported criterion type: {}", criterion.type());
+            }
+        }
+
+        return DefaultFlowRule.builder()
+                .forDevice(deviceId)
+                .withSelector(sBuilder.build())
+                .withTreatment(instTreatment)
+                .withPriority(DEFAULT_PRIORITY)
+                .forTable(FabricConstants.FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_SOURCE_TB_INT_SOURCE)
+                .fromApp(appId)
+                .makePermanent()
+                .build();
+    }
+
+    private int buildInstructionBitmap(Set<IntIntent.IntMetadataType> metadataTypes) {
+        int instBitmap = 0;
+        for (IntIntent.IntMetadataType metadataType : metadataTypes) {
+            switch (metadataType) {
+                case SWITCH_ID:
+                    instBitmap |= (1 << 15);
+                    break;
+                case L1_PORT_ID:
+                    instBitmap |= (1 << 14);
+                    break;
+                case HOP_LATENCY:
+                    instBitmap |= (1 << 13);
+                    break;
+                case QUEUE_OCCUPANCY:
+                    instBitmap |= (1 << 12);
+                    break;
+                case INGRESS_TIMESTAMP:
+                    instBitmap |= (1 << 11);
+                    break;
+                case EGRESS_TIMESTAMP:
+                    instBitmap |= (1 << 10);
+                    break;
+                case L2_PORT_ID:
+                    instBitmap |= (1 << 9);
+                    break;
+                case EGRESS_TX_UTIL:
+                    instBitmap |= (1 << 8);
+                    break;
+                default:
+                    log.info("Unsupported metadata type {}. Ignoring...", metadataType);
+                    break;
+            }
+        }
+        return instBitmap;
+    }
+
+    /**
+     * Returns a subset of Criterion from given selector, which is unsupported
+     * by this INT pipeline.
+     *
+     * @param selector a traffic selector
+     * @return a subset of Criterion from given selector, unsupported by this
+     * INT pipeline, empty if all criteria are supported.
+     */
+    private Set<Criterion> unsupportedSelectors(TrafficSelector selector) {
+        return selector.criteria().stream()
+                .filter(criterion -> !SUPPORTED_CRITERION.contains(criterion.type()))
+                .collect(Collectors.toSet());
+    }
+
+    private boolean processIntObjective(IntObjective obj, boolean install) {
+        if (install && !unsupportedSelectors(obj.selector()).isEmpty()) {
+            log.warn("Criteria {} not supported by {} for INT watchlist",
+                     unsupportedSelectors(obj.selector()), deviceId);
+            return false;
+        }
+
+        FlowRule flowRule = buildWatchlistEntry(obj);
+        if (flowRule != null) {
+            if (install) {
+                flowRuleService.applyFlowRules(flowRule);
+            } else {
+                flowRuleService.removeFlowRules(flowRule);
+            }
+            log.debug("IntObjective {} has been {} {}",
+                      obj, install ? "installed to" : "removed from", deviceId);
+            return true;
+        } else {
+            log.warn("Failed to {} IntObjective {} on {}",
+                     install ? "install" : "remove", obj, deviceId);
+            return false;
+        }
+    }
+
+    private boolean setupIntReportInternal(IntConfig cfg) {
+        // Report not fully supported yet.
+        return true;
+        // FlowRule reportRule = buildReportEntry(cfg, PKT_INSTANCE_TYPE_INGRESS_CLONE);
+        // if (reportRule != null) {
+        //     flowRuleService.applyFlowRules(reportRule);
+        //     log.info("Report entry {} has been added to {}", reportRule, this.data().deviceId());
+        //     return true;
+        // } else {
+        //     log.warn("Failed to add report entry on {}", this.data().deviceId());
+        //     return false;
+        // }
+    }
+
+    private FlowRule buildReportEntry(IntConfig cfg, int type) {
+
+        if (!setupBehaviour()) {
+            return null;
+        }
+
+        PiActionParam srcMacParam = new PiActionParam(
+                FabricConstants.SRC_MAC,
+                copyFrom(cfg.sinkMac().toBytes()));
+        PiActionParam nextHopMacParam = new PiActionParam(
+                FabricConstants.MON_MAC,
+                copyFrom(cfg.collectorNextHopMac().toBytes()));
+        PiActionParam srcIpParam = new PiActionParam(
+                FabricConstants.SRC_IP,
+                copyFrom(cfg.sinkIp().toOctets()));
+        PiActionParam monIpParam = new PiActionParam(
+                FabricConstants.MON_IP,
+                copyFrom(cfg.collectorIp().toOctets()));
+        PiActionParam monPortParam = new PiActionParam(
+                FabricConstants.MON_PORT,
+                copyFrom(cfg.collectorPort().toInt()));
+        PiAction reportAction = PiAction.builder()
+                .withId(FabricConstants.FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_REPORT_DO_REPORT_ENCAPSULATION)
+                .withParameter(srcMacParam)
+                .withParameter(nextHopMacParam)
+                .withParameter(srcIpParam)
+                .withParameter(monIpParam)
+                .withParameter(monPortParam)
+                .build();
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .piTableAction(reportAction)
+                .build();
+
+        return DefaultFlowRule.builder()
+                .withTreatment(treatment)
+                .fromApp(appId)
+                .withPriority(DEFAULT_PRIORITY)
+                .makePermanent()
+                .forDevice(this.data().deviceId())
+                .forTable(FabricConstants.FABRIC_EGRESS_PROCESS_INT_MAIN_PROCESS_INT_REPORT_TB_GENERATE_REPORT)
+                .build();
+    }
+
+}