Refactored PI-ECMP app to use action profiles of basic.p4

Also removed obsolete ecmp.p4-related code.

Change-Id: Idaca90becfff5fc312de2530bf7924ccd502e076
diff --git a/apps/pi-demo/common/src/main/java/org/onosproject/pi/demo/app/common/AbstractUpgradableFabricApp.java b/apps/pi-demo/common/src/main/java/org/onosproject/pi/demo/app/common/AbstractUpgradableFabricApp.java
index 60c0e38..7c91bf3 100644
--- a/apps/pi-demo/common/src/main/java/org/onosproject/pi/demo/app/common/AbstractUpgradableFabricApp.java
+++ b/apps/pi-demo/common/src/main/java/org/onosproject/pi/demo/app/common/AbstractUpgradableFabricApp.java
@@ -16,7 +16,6 @@
 
 package org.onosproject.pi.demo.app.common;
 
-import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -43,6 +42,7 @@
 import org.onosproject.net.flow.FlowRule;
 import org.onosproject.net.flow.FlowRuleOperations;
 import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.group.GroupService;
 import org.onosproject.net.host.HostService;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.model.PiPipeconfId;
@@ -55,6 +55,7 @@
 import org.onosproject.net.topology.TopologyVertex;
 import org.slf4j.Logger;
 
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -81,17 +82,19 @@
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
- * Abstract implementation of an app providing fabric connectivity for a 2-stage Clos topology of P4Runtime devices.
+ * Abstract implementation of an app providing fabric connectivity for a 2-stage
+ * Clos topology of P4Runtime devices.
  */
 @Component(immediate = true)
 public abstract class AbstractUpgradableFabricApp {
 
-    private static final Map<String, AbstractUpgradableFabricApp> APP_HANDLES = Maps.newConcurrentMap();
+    private static final Map<String, AbstractUpgradableFabricApp>
+            APP_HANDLES = Maps.newConcurrentMap();
 
     // TOPO_SIZE should be the same of the --size argument when running bmv2-demo.py
     private static final int TOPO_SIZE = 2;
     private static final boolean WITH_IMBALANCED_STRIPING = false;
-    protected static final int HASHED_LINKS = TOPO_SIZE + (WITH_IMBALANCED_STRIPING ? 1 : 0);
+    private static final int HASHED_LINKS = TOPO_SIZE + (WITH_IMBALANCED_STRIPING ? 1 : 0);
 
     private static final int FLOW_PRIORITY = 100;
     private static final int CHECK_TOPOLOGY_INTERVAL_SECONDS = 5;
@@ -122,6 +125,9 @@
     private FlowRuleService flowRuleService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected GroupService groupService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     private ApplicationAdminService appService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -137,7 +143,7 @@
     private AbstractUpgradableFabricApp otherApp;
 
     private boolean flowRuleGenerated = false;
-    private ApplicationId appId;
+    protected ApplicationId appId;
 
     private Collection<PiPipeconf> appPipeconfs;
 
@@ -156,7 +162,8 @@
      * @param appName      app name
      * @param appPipeconfs collection of compatible pipeconfs
      */
-    protected AbstractUpgradableFabricApp(String appName, Collection<PiPipeconf> appPipeconfs) {
+    protected AbstractUpgradableFabricApp(String appName,
+                                          Collection<PiPipeconf> appPipeconfs) {
         this.appName = checkNotNull(appName);
         this.appPipeconfs = checkNotNull(appPipeconfs);
         checkArgument(appPipeconfs.size() > 0, "appPipeconfs cannot have size 0");
@@ -171,11 +178,13 @@
 
         if (APP_HANDLES.size() > 0) {
             if (APP_HANDLES.size() > 1) {
-                throw new IllegalStateException("Found more than 1 active app handles");
+                throw new IllegalStateException(
+                        "Found more than 1 active app handles");
             }
             otherAppFound = true;
             otherApp = APP_HANDLES.values().iterator().next();
-            log.info("Found other fabric app active, signaling to freeze to {}...", otherApp.appName);
+            log.info("Found other fabric app active, signaling to freeze to {}...",
+                     otherApp.appName);
             otherApp.setAppFreezed(true);
         }
 
@@ -221,12 +230,12 @@
             pipeconfFlags = Maps.newConcurrentMap();
         }
 
-        /*
-        Schedules a thread that periodically checks the topology, as soon as it corresponds to the expected
-        one, it generates the necessary flow rules and starts the deploy process on each device.
-         */
-        scheduledExecutorService.scheduleAtFixedRate(this::checkTopologyAndGenerateFlowRules,
-                                                     0, CHECK_TOPOLOGY_INTERVAL_SECONDS, TimeUnit.SECONDS);
+        // Schedules a thread that periodically checks the topology, as soon as
+        // it corresponds to the expected one, it generates the necessary flow
+        // rules and starts the deploy process on each device.
+        scheduledExecutorService.scheduleAtFixedRate(
+                this::checkTopologyAndGenerateFlowRules,
+                0, CHECK_TOPOLOGY_INTERVAL_SECONDS, TimeUnit.SECONDS);
     }
 
     private void setAppFreezed(boolean appFreezed) {
@@ -239,7 +248,8 @@
     }
 
     /**
-     * Perform device initialization. Returns true if the operation was successful, false otherwise.
+     * Perform device initialization. Returns true if the operation was
+     * successful, false otherwise.
      *
      * @param deviceId a device id
      * @return a boolean value
@@ -247,8 +257,8 @@
     public abstract boolean initDevice(DeviceId deviceId);
 
     /**
-     * Generates a list of flow rules for the given leaf switch, source host, destination hosts, spine switches and
-     * topology.
+     * Generates a list of flow rules for the given leaf switch, source host,
+     * destination hosts, spine switches and topology.
      *
      * @param leaf     a leaf device id
      * @param srcHost  a source host
@@ -258,12 +268,15 @@
      * @return a list of flow rules
      * @throws FlowRuleGeneratorException if flow rules cannot be generated
      */
-    public abstract List<FlowRule> generateLeafRules(DeviceId leaf, Host srcHost, Collection<Host> dstHosts,
-                                                     Collection<DeviceId> spines, Topology topology)
+    public abstract List<FlowRule> generateLeafRules(DeviceId leaf, Host srcHost,
+                                                     Set<Host> dstHosts,
+                                                     Set<DeviceId> spines,
+                                                     Topology topology)
             throws FlowRuleGeneratorException;
 
     /**
-     * Generates a list of flow rules for the given spine switch, destination hosts and topology.
+     * Generates a list of flow rules for the given spine switch, destination
+     * hosts and topology.
      *
      * @param deviceId a spine device id
      * @param dstHosts a collection of destination hosts
@@ -271,7 +284,9 @@
      * @return a list of flow rules
      * @throws FlowRuleGeneratorException if flow rules cannot be generated
      */
-    public abstract List<FlowRule> generateSpineRules(DeviceId deviceId, Collection<Host> dstHosts, Topology topology)
+    public abstract List<FlowRule> generateSpineRules(DeviceId deviceId,
+                                                      Set<Host> dstHosts,
+                                                      Topology topology)
             throws FlowRuleGeneratorException;
 
     private void deployAllDevices() {
@@ -301,7 +316,7 @@
      *
      * @param device a device
      */
-    public void deployDevice(Device device) {
+    private void deployDevice(Device device) {
 
         DeviceId deviceId = device.id();
 
@@ -317,7 +332,7 @@
                     pipeconfFlags.put(device.id(), true);
                 } else {
                     log.warn("Wrong pipeconf for {}, expecting {}, but found {}, aborting deploy",
-                             deviceId, MoreObjects.toStringHelper(appPipeconfs),
+                             deviceId, Arrays.toString(appPipeconfs.toArray()),
                              piPipeconfService.ofDevice(deviceId).get());
                     return;
                 }
@@ -331,7 +346,8 @@
             // Install rules.
             if (!ruleFlags.getOrDefault(deviceId, false) &&
                     deviceFlowRules.containsKey(deviceId)) {
-                log.info("Installing {} rules for {}...", deviceFlowRules.get(deviceId).size(), deviceId);
+                log.info("Installing {} rules for {}...",
+                         deviceFlowRules.get(deviceId).size(), deviceId);
                 installFlowRules(deviceFlowRules.get(deviceId));
                 ruleFlags.put(deviceId, true);
             }
@@ -352,7 +368,8 @@
     }
 
     /**
-     * Generates flow rules to provide host-to-host connectivity for the given topology and hosts.
+     * Generates flow rules to provide host-to-host connectivity for the given
+     * topology and hosts.
      */
     private synchronized void checkTopologyAndGenerateFlowRules() {
 
@@ -374,7 +391,7 @@
                 .forEach(did -> (isSpine(did, topo) ? spines : leafs).add(did));
 
         if (spines.size() != TOPO_SIZE || leafs.size() != TOPO_SIZE) {
-            log.info("Invalid leaf/spine switches count, aborting... > leafCount={}, spineCount={}",
+            log.info("Invalid leaf/spine count, aborting... > leafCount={}, spineCount={}",
                      spines.size(), leafs.size());
             return;
         }
@@ -383,7 +400,8 @@
             int portCount = deviceService.getPorts(did).size();
             // Expected port count: num leafs + 1 redundant leaf link (if imbalanced)
             if (portCount != HASHED_LINKS) {
-                log.info("Invalid port count for spine, aborting... > deviceId={}, portCount={}", did, portCount);
+                log.info("Invalid port count for spine, aborting... > deviceId={}, portCount={}",
+                         did, portCount);
                 return;
             }
         }
@@ -391,7 +409,8 @@
             int portCount = deviceService.getPorts(did).size();
             // Expected port count: num spines + host port + 1 redundant spine link
             if (portCount != HASHED_LINKS + 1) {
-                log.info("Invalid port count for leaf, aborting... > deviceId={}, portCount={}", did, portCount);
+                log.info("Invalid port count for leaf, aborting... > deviceId={}, portCount={}",
+                         did, portCount);
                 return;
             }
         }
@@ -400,7 +419,8 @@
         Map<DeviceId, Host> hostMap = Maps.newHashMap();
         hosts.forEach(h -> hostMap.put(h.location().deviceId(), h));
         if (hosts.size() != TOPO_SIZE || !leafs.equals(hostMap.keySet())) {
-            log.info("Wrong host configuration, aborting... > hostCount={}, hostMapz={}", hosts.size(), hostMap);
+            log.info("Wrong host configuration, aborting... > hostCount={}, hostMapz={}",
+                     hosts.size(), hostMap);
             return;
         }
 
@@ -409,14 +429,18 @@
         try {
             for (DeviceId deviceId : leafs) {
                 Host srcHost = hostMap.get(deviceId);
-                Set<Host> dstHosts = hosts.stream().filter(h -> h != srcHost).collect(toSet());
-                newFlowRules.addAll(generateLeafRules(deviceId, srcHost, dstHosts, spines, topo));
+                Set<Host> dstHosts = hosts.stream()
+                        .filter(h -> h != srcHost)
+                        .collect(toSet());
+                newFlowRules.addAll(generateLeafRules(deviceId, srcHost,
+                                                      dstHosts, spines, topo));
             }
             for (DeviceId deviceId : spines) {
                 newFlowRules.addAll(generateSpineRules(deviceId, hosts, topo));
             }
         } catch (FlowRuleGeneratorException e) {
-            log.warn("Exception while executing flow rule generator: {}", e.getMessage());
+            log.warn("Exception while executing flow rule generator: {}",
+                     e.getMessage());
             return;
         }
 
@@ -428,12 +452,13 @@
 
         // All good!
         // Divide flow rules per device id...
-        ImmutableMap.Builder<DeviceId, List<FlowRule>> mapBuilder = ImmutableMap.builder();
+        ImmutableMap.Builder<DeviceId, List<FlowRule>> mapBuilder =
+                ImmutableMap.builder();
         concat(spines.stream(), leafs.stream())
-                .map(deviceId -> ImmutableList.copyOf(newFlowRules
-                                                              .stream()
-                                                              .filter(fr -> fr.deviceId().equals(deviceId))
-                                                              .iterator()))
+                .map(deviceId -> ImmutableList.copyOf(
+                        newFlowRules.stream()
+                                .filter(fr -> fr.deviceId().equals(deviceId))
+                                .iterator()))
                 .forEach(frs -> mapBuilder.put(frs.get(0).deviceId(), frs));
         this.deviceFlowRules = mapBuilder.build();
 
@@ -443,7 +468,8 @@
         // Avoid other executions to modify the generated flow rules.
         flowRuleGenerated = true;
 
-        log.info("Generated {} flow rules for {} devices", newFlowRules.size(), spines.size() + leafs.size());
+        log.info("Generated {} flow rules for {} devices",
+                 newFlowRules.size(), spines.size() + leafs.size());
 
         spawnTask(this::deployAllDevices);
     }
@@ -455,11 +481,13 @@
      * @param tableId a table id
      * @return a new flow rule builder
      */
-    protected FlowRule.Builder flowRuleBuilder(DeviceId did, PiTableId tableId) throws FlowRuleGeneratorException {
+    protected FlowRule.Builder flowRuleBuilder(DeviceId did, PiTableId tableId)
+            throws FlowRuleGeneratorException {
 
         final Device device = deviceService.getDevice(did);
         if (!device.is(PiPipelineInterpreter.class)) {
-            throw new FlowRuleGeneratorException(format("Device %s has no PiPipelineInterpreter", did));
+            throw new FlowRuleGeneratorException(format(
+                    "Device %s has no PiPipelineInterpreter", did));
         }
 
         return DefaultFlowRule.builder()
@@ -486,12 +514,13 @@
 
     protected boolean isFabricPort(Port port, Topology topology) {
         // True if the port connects this device to another infrastructure device.
-        return topologyService.isInfrastructure(topology, new ConnectPoint(port.element().id(), port.number()));
+        return topologyService.isInfrastructure(
+                topology, new ConnectPoint(port.element().id(), port.number()));
     }
 
     /**
-     * A listener of device events that executes a device deploy task each time a device is added, updated or
-     * re-connects.
+     * A listener of device events that executes a device deploy task each time
+     * a device is added, updated or re-connects.
      */
     private class InternalDeviceListener implements DeviceListener {
         @Override
@@ -512,12 +541,12 @@
     /**
      * An exception occurred while generating flow rules for this fabric.
      */
-    public class FlowRuleGeneratorException extends Exception {
+    public static class FlowRuleGeneratorException extends Exception {
 
         public FlowRuleGeneratorException() {
         }
 
-        public FlowRuleGeneratorException(String msg) {
+        FlowRuleGeneratorException(String msg) {
             super(msg);
         }
     }
diff --git a/apps/pi-demo/ecmp/src/main/java/org/onosproject/pi/demo/app/ecmp/EcmpFabricApp.java b/apps/pi-demo/ecmp/src/main/java/org/onosproject/pi/demo/app/ecmp/EcmpFabricApp.java
index 5a40df4..bb15afc 100644
--- a/apps/pi-demo/ecmp/src/main/java/org/onosproject/pi/demo/app/ecmp/EcmpFabricApp.java
+++ b/apps/pi-demo/ecmp/src/main/java/org/onosproject/pi/demo/app/ecmp/EcmpFabricApp.java
@@ -18,9 +18,12 @@
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.commons.lang3.tuple.ImmutablePair;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.felix.scr.annotations.Component;
-import org.onlab.util.ImmutableByteSequence;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.onlab.packet.IpAddress;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
 import org.onosproject.net.Path;
@@ -29,12 +32,17 @@
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 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.PiCriterion;
+import org.onosproject.net.group.DefaultGroupBucket;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupKey;
 import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionGroupId;
 import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiGroupKey;
 import org.onosproject.net.pi.runtime.PiTableAction;
 import org.onosproject.net.topology.DefaultTopologyVertex;
 import org.onosproject.net.topology.Topology;
@@ -42,37 +50,44 @@
 import org.onosproject.pi.demo.app.common.AbstractUpgradableFabricApp;
 import org.onosproject.pipelines.basic.PipeconfLoader;
 
-import java.util.Collection;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
-import static java.lang.String.format;
 import static java.util.Collections.singleton;
 import static java.util.stream.Collectors.toSet;
-import static org.onlab.packet.EthType.EtherType.IPV4;
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onosproject.pipelines.basic.BasicConstants.ACT_PRF_WCMP_SELECTOR_ID;
 import static org.onosproject.pipelines.basic.BasicConstants.ACT_PRM_NEXT_HOP_ID;
 import static org.onosproject.pipelines.basic.BasicConstants.ACT_SET_NEXT_HOP_ID;
 import static org.onosproject.pipelines.basic.BasicConstants.HDR_NEXT_HOP_ID;
-import static org.onosproject.pipelines.basic.BasicConstants.HDR_SELECTOR_ID;
 import static org.onosproject.pipelines.basic.BasicConstants.TBL_TABLE0_ID;
-import static org.onosproject.pipelines.basic.EcmpConstants.TBL_ECMP_TABLE_ID;
-
+import static org.onosproject.pipelines.basic.BasicConstants.TBL_WCMP_TABLE_ID;
 
 /**
- * Implementation of an upgradable fabric app for the ECMP pipeconf.
+ * Implementation of an upgradable fabric app for the Basic pipeconf (basic.p4)
+ * with ECMP support.
  */
 @Component(immediate = true)
 public class EcmpFabricApp extends AbstractUpgradableFabricApp {
 
     private static final String APP_NAME = "org.onosproject.pi-ecmp";
 
-    private static final Map<DeviceId, Map<Set<PortNumber>, Short>> DEVICE_GROUP_ID_MAP = Maps.newHashMap();
+    private static final Map<DeviceId, Map<Set<PortNumber>, Short>>
+            DEVICE_GROUP_ID_MAP = Maps.newHashMap();
+
+    private final Set<Pair<DeviceId, GroupKey>> groupKeys = Sets.newHashSet();
 
     public EcmpFabricApp() {
-        super(APP_NAME, singleton(PipeconfLoader.ECMP_PIPECONF));
+        super(APP_NAME, singleton(PipeconfLoader.BASIC_PIPECONF));
+    }
+
+    @Deactivate
+    public void deactivate() {
+        groupKeys.forEach(pair -> groupService.removeGroup(
+                pair.getLeft(), pair.getRight(), appId));
+        super.deactivate();
     }
 
     @Override
@@ -82,8 +97,10 @@
     }
 
     @Override
-    public List<FlowRule> generateLeafRules(DeviceId leaf, Host srcHost, Collection<Host> dstHosts,
-                                            Collection<DeviceId> availableSpines, Topology topo)
+    public List<FlowRule> generateLeafRules(DeviceId leaf, Host localHost,
+                                            Set<Host> remoteHosts,
+                                            Set<DeviceId> availableSpines,
+                                            Topology topo)
             throws FlowRuleGeneratorException {
 
         // Get ports which connect this leaf switch to hosts.
@@ -91,18 +108,19 @@
                 .stream()
                 .filter(port -> !isFabricPort(port, topo))
                 .map(Port::number)
-                .collect(Collectors.toSet());
+                .collect(toSet());
 
         // Get ports which connect this leaf to the given available spines.
         TopologyGraph graph = topologyService.getGraph(topo);
-        Set<PortNumber> fabricPorts = graph.getEdgesFrom(new DefaultTopologyVertex(leaf))
+        Set<PortNumber> fabricPorts = graph
+                .getEdgesFrom(new DefaultTopologyVertex(leaf))
                 .stream()
                 .filter(e -> availableSpines.contains(e.dst().deviceId()))
                 .map(e -> e.link().src().port())
-                .collect(Collectors.toSet());
+                .collect(toSet());
 
         if (hostPorts.size() != 1 || fabricPorts.size() == 0) {
-            log.error("Leaf switch has invalid port configuration: hostPorts={}, fabricPorts={}",
+            log.error("Leaf has invalid port configuration: hostPorts={}, fabricPorts={}",
                       hostPorts.size(), fabricPorts.size());
             throw new FlowRuleGeneratorException();
         }
@@ -110,41 +128,40 @@
 
         List<FlowRule> rules = Lists.newArrayList();
 
-        TrafficTreatment treatment;
-        if (fabricPorts.size() > 1) {
-            // Do ECMP.
-            Pair<PiTableAction, List<FlowRule>> result = provisionEcmpPiTableAction(leaf, fabricPorts);
-            rules.addAll(result.getRight());
-            treatment = DefaultTrafficTreatment.builder().piTableAction(result.getLeft()).build();
-        } else {
-            // Output on port.
-            PortNumber outPort = fabricPorts.iterator().next();
-            treatment = DefaultTrafficTreatment.builder().setOutput(outPort).build();
-        }
+        // From local host to remote ones.
+        for (Host remoteHost : remoteHosts) {
+            int groupId = provisionGroup(leaf, fabricPorts);
 
-        // From srHost to dstHosts.
-        for (Host dstHost : dstHosts) {
-            FlowRule rule = flowRuleBuilder(leaf, TBL_TABLE0_ID)
-                    .withSelector(
-                            DefaultTrafficSelector.builder()
-                                    .matchInPort(hostPort)
-                                    .matchEthType(IPV4.ethType().toShort())
-                                    .matchEthSrc(srcHost.mac())
-                                    .matchEthDst(dstHost.mac())
-                                    .build())
-                    .withTreatment(treatment)
+            rules.add(groupFlowRule(leaf, groupId));
+
+            PiTableAction piTableAction = PiAction.builder()
+                    .withId(ACT_SET_NEXT_HOP_ID)
+                    .withParameter(new PiActionParam(
+                            ACT_PRM_NEXT_HOP_ID,
+                            copyFrom(groupId)))
                     .build();
-            rules.add(rule);
+
+            for (IpAddress ipAddr : remoteHost.ipAddresses()) {
+                FlowRule rule = flowRuleBuilder(leaf, TBL_TABLE0_ID)
+                        .withSelector(
+                                DefaultTrafficSelector.builder()
+                                        .matchIPDst(ipAddr.toIpPrefix())
+                                        .build())
+                        .withTreatment(
+                                DefaultTrafficTreatment.builder()
+                                        .piTableAction(piTableAction)
+                                        .build())
+                        .build();
+                rules.add(rule);
+            }
         }
 
-        // From fabric ports to this leaf host.
-        for (PortNumber port : fabricPorts) {
+        // From remote hosts to the local one
+        for (IpAddress dstIpAddr : localHost.ipAddresses()) {
             FlowRule rule = flowRuleBuilder(leaf, TBL_TABLE0_ID)
                     .withSelector(
                             DefaultTrafficSelector.builder()
-                                    .matchInPort(port)
-                                    .matchEthType(IPV4.ethType().toShort())
-                                    .matchEthDst(srcHost.mac())
+                                    .matchIPDst(dstIpAddr.toIpPrefix())
                                     .build())
                     .withTreatment(
                             DefaultTrafficTreatment.builder()
@@ -158,97 +175,105 @@
     }
 
     @Override
-    public List<FlowRule> generateSpineRules(DeviceId deviceId, Collection<Host> dstHosts, Topology topo)
+    public List<FlowRule> generateSpineRules(DeviceId spine, Set<Host> hosts,
+                                             Topology topo)
             throws FlowRuleGeneratorException {
 
         List<FlowRule> rules = Lists.newArrayList();
 
-        // for each host
-        for (Host dstHost : dstHosts) {
+        // For each host pair (src -> dst)
+        for (Host dstHost : hosts) {
 
-            Set<Path> paths = topologyService.getPaths(topo, deviceId, dstHost.location().deviceId());
+            Set<Path> paths = topologyService.getPaths(
+                    topo, spine, dstHost.location().deviceId());
 
             if (paths.size() == 0) {
-                log.warn("Can't find any path between spine {} and host {}", deviceId, dstHost);
+                log.warn("No path between spine {} and host {}",
+                         spine, dstHost);
                 throw new FlowRuleGeneratorException();
             }
 
-            TrafficTreatment treatment;
+            Set<PortNumber> ports = paths.stream()
+                    .map(p -> p.src().port())
+                    .collect(toSet());
 
-            if (paths.size() == 1) {
-                // Only one path, do output on port.
-                PortNumber port = paths.iterator().next().src().port();
-                treatment = DefaultTrafficTreatment.builder().setOutput(port).build();
-            } else {
-                // Multiple paths, do ECMP.
-                Set<PortNumber> portNumbers = paths.stream().map(p -> p.src().port()).collect(toSet());
-                Pair<PiTableAction, List<FlowRule>> result = provisionEcmpPiTableAction(deviceId, portNumbers);
-                rules.addAll(result.getRight());
-                treatment = DefaultTrafficTreatment.builder().piTableAction(result.getLeft()).build();
-            }
+            int groupId = provisionGroup(spine, ports);
 
-            FlowRule rule = flowRuleBuilder(deviceId, TBL_TABLE0_ID)
-                    .withSelector(
-                            DefaultTrafficSelector.builder()
-                                    .matchEthType(IPV4.ethType().toShort())
-                                    .matchEthDst(dstHost.mac())
-                                    .build())
-                    .withTreatment(treatment)
+            rules.add(groupFlowRule(spine, groupId));
+
+            PiTableAction piTableAction = PiAction.builder()
+                    .withId(ACT_SET_NEXT_HOP_ID)
+                    .withParameter(new PiActionParam(ACT_PRM_NEXT_HOP_ID,
+                                                     copyFrom(groupId)))
                     .build();
 
-            rules.add(rule);
+            for (IpAddress dstIpAddr : dstHost.ipAddresses()) {
+                FlowRule rule = flowRuleBuilder(spine, TBL_TABLE0_ID)
+                        .withSelector(DefaultTrafficSelector.builder()
+                                              .matchIPDst(dstIpAddr.toIpPrefix())
+                                              .build())
+                        .withTreatment(DefaultTrafficTreatment.builder()
+                                               .piTableAction(piTableAction)
+                                               .build())
+                        .build();
+                rules.add(rule);
+            }
         }
 
         return rules;
     }
 
-    private Pair<PiTableAction, List<FlowRule>> provisionEcmpPiTableAction(DeviceId deviceId,
-                                                                           Set<PortNumber> fabricPorts)
+    /**
+     * Provisions an ECMP group for the given device and set of ports, returns
+     * the group ID.
+     */
+    private int provisionGroup(DeviceId deviceId, Set<PortNumber> ports)
             throws FlowRuleGeneratorException {
 
-        // Install ECMP group table entries that map from hash values to actual fabric ports...
-        int groupId = groupIdOf(deviceId, fabricPorts);
-        if (fabricPorts.size() != HASHED_LINKS) {
-            throw new FlowRuleGeneratorException(format(
-                    "Invalid number of fabric ports for %s, expected %d but found %d",
-                    deviceId, HASHED_LINKS, fabricPorts.size()));
-        }
-        Iterator<PortNumber> portIterator = fabricPorts.iterator();
-        List<FlowRule> rules = Lists.newArrayList();
-        for (short i = 0; i < HASHED_LINKS; i++) {
-            FlowRule rule = flowRuleBuilder(deviceId, TBL_ECMP_TABLE_ID)
-                    .withSelector(
-                            buildEcmpTrafficSelector(groupId, i))
-                    .withTreatment(
-                            DefaultTrafficTreatment.builder()
-                                    .setOutput(portIterator.next())
-                                    .build())
-                    .build();
-            rules.add(rule);
-        }
+        int groupId = groupIdOf(deviceId, ports);
 
-        PiTableAction piTableAction = buildEcmpPiTableAction(groupId);
+        // Group buckets
+        List<GroupBucket> bucketList = ports.stream()
+                .map(port -> DefaultTrafficTreatment.builder()
+                        .setOutput(port)
+                        .build())
+                .map(DefaultGroupBucket::createSelectGroupBucket)
+                .collect(Collectors.toList());
 
-        return Pair.of(piTableAction, rules);
+        // Group cookie (with action profile ID)
+        PiGroupKey groupKey = new PiGroupKey(TBL_WCMP_TABLE_ID,
+                                             ACT_PRF_WCMP_SELECTOR_ID,
+                                             groupId);
+
+        log.info("Adding group {} to {}...", groupId, deviceId);
+        groupService.addGroup(
+                new DefaultGroupDescription(deviceId,
+                                            GroupDescription.Type.SELECT,
+                                            new GroupBuckets(bucketList),
+                                            groupKey,
+                                            groupId,
+                                            appId));
+
+        groupKeys.add(ImmutablePair.of(deviceId, groupKey));
+
+        return groupId;
     }
 
-    private PiTableAction buildEcmpPiTableAction(int groupId) {
-
-        return PiAction.builder()
-                .withId(ACT_SET_NEXT_HOP_ID)
-                .withParameter(new PiActionParam(ACT_PRM_NEXT_HOP_ID,
-                                                 ImmutableByteSequence.copyFrom(groupId)))
-                .build();
-    }
-
-    private TrafficSelector buildEcmpTrafficSelector(int groupId, int selector) {
-        Criterion ecmpCriterion = PiCriterion.builder()
-                .matchExact(HDR_NEXT_HOP_ID, groupId)
-                .matchExact(HDR_SELECTOR_ID, selector)
-                .build();
-
-        return DefaultTrafficSelector.builder()
-                .matchPi((PiCriterion) ecmpCriterion)
+    private FlowRule groupFlowRule(DeviceId deviceId, int groupId)
+            throws FlowRuleGeneratorException {
+        return flowRuleBuilder(deviceId, TBL_WCMP_TABLE_ID)
+                .withSelector(
+                        DefaultTrafficSelector.builder()
+                                .matchPi(
+                                        PiCriterion.builder()
+                                                .matchExact(HDR_NEXT_HOP_ID,
+                                                            groupId)
+                                                .build())
+                                .build())
+                .withTreatment(
+                        DefaultTrafficTreatment.builder()
+                                .piTableAction(PiActionGroupId.of(groupId))
+                                .build())
                 .build();
     }
 
@@ -259,4 +284,4 @@
         return DEVICE_GROUP_ID_MAP.get(deviceId).computeIfAbsent(ports, (pp) ->
                 (short) (DEVICE_GROUP_ID_MAP.get(deviceId).size() + 1));
     }
-}
\ No newline at end of file
+}
diff --git a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicConstants.java b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicConstants.java
index b71acbf..b6ee3c2 100644
--- a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicConstants.java
+++ b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicConstants.java
@@ -38,12 +38,15 @@
     public static final String DOT =  ".";
     public static final String HDR = "hdr";
     public static final String ETHERNET = "ethernet";
+    public static final String IPV4 = "ipv4";
     public static final String LOCAL_METADATA = "local_metadata";
     public static final String STANDARD_METADATA = "standard_metadata";
     public static final PiMatchFieldId HDR_IN_PORT_ID = PiMatchFieldId.of(STANDARD_METADATA + DOT + "ingress_port");
     public static final PiMatchFieldId HDR_ETH_DST_ID = PiMatchFieldId.of(HDR + DOT + ETHERNET + DOT + "dst_addr");
     public static final PiMatchFieldId HDR_ETH_SRC_ID = PiMatchFieldId.of(HDR + DOT + ETHERNET + DOT + "src_addr");
     public static final PiMatchFieldId HDR_ETH_TYPE_ID = PiMatchFieldId.of(HDR + DOT + ETHERNET + DOT + "ether_type");
+    public static final PiMatchFieldId HDR_IPV4_DST_ID = PiMatchFieldId.of(HDR + DOT + IPV4 + DOT + "dst_addr");
+    public static final PiMatchFieldId HDR_IPV4_SRC_ID = PiMatchFieldId.of(HDR + DOT + IPV4 + DOT + "src_addr");
     public static final PiMatchFieldId HDR_NEXT_HOP_ID = PiMatchFieldId.of(LOCAL_METADATA + DOT + "next_hop_id");
     public static final PiMatchFieldId HDR_SELECTOR_ID = PiMatchFieldId.of(LOCAL_METADATA + DOT + "selector");
     // Table IDs
@@ -66,7 +69,7 @@
     public static final PiActionParamId ACT_PRM_PORT_ID = PiActionParamId.of("port");
     public static final PiActionParamId ACT_PRM_NEXT_HOP_ID = PiActionParamId.of("next_hop_id");
     // Action Profile IDs
-    public static final PiActionProfileId ACT_PRF_WCMP_SELECTOR_ID = PiActionProfileId.of("wcmp_selector");
+    public static final PiActionProfileId ACT_PRF_WCMP_SELECTOR_ID = PiActionProfileId.of("wcmp_control.wcmp_selector");
     // Packet Metadata IDs
     public static final PiControlMetadataId PKT_META_EGRESS_PORT_ID = PiControlMetadataId.of("egress_port");
     public static final PiControlMetadataId PKT_META_INGRESS_PORT_ID = PiControlMetadataId.of("ingress_port");
diff --git a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicInterpreterImpl.java b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicInterpreterImpl.java
index cc0ef46..8e7b399 100644
--- a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicInterpreterImpl.java
+++ b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicInterpreterImpl.java
@@ -66,6 +66,8 @@
 import static org.onosproject.pipelines.basic.BasicConstants.HDR_ETH_SRC_ID;
 import static org.onosproject.pipelines.basic.BasicConstants.HDR_ETH_TYPE_ID;
 import static org.onosproject.pipelines.basic.BasicConstants.HDR_IN_PORT_ID;
+import static org.onosproject.pipelines.basic.BasicConstants.HDR_IPV4_DST_ID;
+import static org.onosproject.pipelines.basic.BasicConstants.HDR_IPV4_SRC_ID;
 import static org.onosproject.pipelines.basic.BasicConstants.PKT_META_EGRESS_PORT_ID;
 import static org.onosproject.pipelines.basic.BasicConstants.PKT_META_INGRESS_PORT_ID;
 import static org.onosproject.pipelines.basic.BasicConstants.PORT_BITWIDTH;
@@ -93,6 +95,8 @@
                     .put(Criterion.Type.ETH_DST, HDR_ETH_DST_ID)
                     .put(Criterion.Type.ETH_SRC, HDR_ETH_SRC_ID)
                     .put(Criterion.Type.ETH_TYPE, HDR_ETH_TYPE_ID)
+                    .put(Criterion.Type.IPV4_SRC, HDR_IPV4_SRC_ID)
+                    .put(Criterion.Type.IPV4_DST, HDR_IPV4_DST_ID)
                     .build();
 
     @Override
diff --git a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/PipeconfLoader.java b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/PipeconfLoader.java
index 399d4db..9b33883 100644
--- a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/PipeconfLoader.java
+++ b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/PipeconfLoader.java
@@ -50,14 +50,9 @@
     private static final String BASIC_JSON_PATH = "/p4c-out/bmv2/basic.json";
     private static final String BASIC_P4INFO = "/p4c-out/bmv2/basic.p4info";
 
-    private static final PiPipeconfId ECMP_PIPECONF_ID = new PiPipeconfId("org.onosproject.pipelines.ecmp");
-    private static final String ECMP_JSON_PATH = "/p4c-out/bmv2/ecmp.json";
-    private static final String ECMP_P4INFO = "/p4c-out/bmv2/ecmp.p4info";
-
     public static final PiPipeconf BASIC_PIPECONF = buildBasicPipeconf();
-    public static final PiPipeconf ECMP_PIPECONF = buildEcmpPipeconf();
 
-    private static final Collection<PiPipeconf> ALL_PIPECONFS = ImmutableList.of(BASIC_PIPECONF, ECMP_PIPECONF);
+    private static final Collection<PiPipeconf> ALL_PIPECONFS = ImmutableList.of(BASIC_PIPECONF);
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     private PiPipeconfService piPipeconfService;
@@ -90,21 +85,6 @@
                 .build();
     }
 
-    private static PiPipeconf buildEcmpPipeconf() {
-        final URL jsonUrl = PipeconfLoader.class.getResource(ECMP_JSON_PATH);
-        final URL p4InfoUrl = PipeconfLoader.class.getResource(ECMP_P4INFO);
-
-        return DefaultPiPipeconf.builder()
-                .withId(ECMP_PIPECONF_ID)
-                .withPipelineModel(parseP4Info(p4InfoUrl))
-                .addBehaviour(PiPipelineInterpreter.class, EcmpInterpreterImpl.class)
-                .addBehaviour(Pipeliner.class, DefaultSingleTablePipeline.class)
-                .addBehaviour(PortStatisticsDiscovery.class, PortStatisticsDiscoveryImpl.class)
-                .addExtension(P4_INFO_TEXT, p4InfoUrl)
-                .addExtension(BMV2_JSON, jsonUrl)
-                .build();
-    }
-
     private static PiPipelineModel parseP4Info(URL p4InfoUrl) {
         try {
             return P4InfoParser.parse(p4InfoUrl);
diff --git a/pipelines/basic/src/main/resources/Makefile b/pipelines/basic/src/main/resources/Makefile
index f695e02..2348ba6 100644
--- a/pipelines/basic/src/main/resources/Makefile
+++ b/pipelines/basic/src/main/resources/Makefile
@@ -1,14 +1,10 @@
-all: basic ecmp
+all: basic
 
 basic: basic.p4
 	p4c-bm2-ss -o p4c-out/bmv2/basic.json \
 		--p4runtime-file p4c-out/bmv2/basic.p4info \
 		--p4runtime-format text basic.p4
 
-ecmp: ecmp.p4
-	p4c-bm2-ss -o p4c-out/bmv2/ecmp.json \
-		--p4runtime-file p4c-out/bmv2/ecmp.p4info \
-		--p4runtime-format text ecmp.p4
 clean:
 	rm -rf p4c-out/bmv2/*.json
-	rm -rf p4c-out/bmv2/*.p4info
\ No newline at end of file
+	rm -rf p4c-out/bmv2/*.p4info
diff --git a/pipelines/basic/src/main/resources/ecmp.p4 b/pipelines/basic/src/main/resources/ecmp.p4
deleted file mode 100644
index ab86c39..0000000
--- a/pipelines/basic/src/main/resources/ecmp.p4
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * 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.
- */
-
-#include <core.p4>
-#include <v1model.p4>
-
-#include "include/headers.p4"
-#include "include/defines.p4"
-#include "include/parsers.p4"
-#include "include/actions.p4"
-#include "include/port_counters.p4"
-#include "include/checksums.p4"
-#include "include/packet_io.p4"
-#include "include/table0.p4"
-
-// FIXME: this program is obsolete and should be removed.
-// The PI ECMP demo app should be refactored to use the WCMP capability of default.p4
-
-// Expected number of ports of an ECMP group.
-// This value is fixed, .i.e. we do not support ECMP over port groups of different
-// size. Due to hardware limitations, this value must be constant and a power of 2.
-
-#define ECMP_GROUP_SIZE 128w2
-
-//------------------------------------------------------------------------------
-// INGRESS PIPELINE
-//------------------------------------------------------------------------------
-
-control ingress(inout headers_t hdr,
-                inout local_metadata_t local_metadata,
-                inout standard_metadata_t standard_metadata) {
-
-    direct_counter(CounterType.packets_and_bytes) ecmp_table_counter;
-
-    table ecmp_table {
-        key = {
-            local_metadata.next_hop_id : exact;
-            local_metadata.selector    : exact;
-        }
-        actions = {
-            set_egress_port(standard_metadata);
-        }
-        counters = ecmp_table_counter;
-    }
-
-    action set_ecmp_selector() {
-        hash(local_metadata.selector, HashAlgorithm.crc16, (bit<64>) 0,
-             {
-                 hdr.ipv4.src_addr,
-                 hdr.ipv4.dst_addr,
-                 hdr.ipv4.protocol,
-                 local_metadata.l4_src_port,
-                 local_metadata.l4_dst_port
-             },
-             ECMP_GROUP_SIZE);
-    }
-
-    apply {
-        port_counters_ingress.apply(hdr, standard_metadata);
-        packetio_ingress.apply(hdr, standard_metadata);
-        table0_control.apply(hdr, local_metadata, standard_metadata);
-        if (local_metadata.next_hop_id > 0) {
-            set_ecmp_selector();
-            ecmp_table.apply();
-        }
-     }
-}
-
-//------------------------------------------------------------------------------
-// EGRESS PIPELINE
-//------------------------------------------------------------------------------
-
-control egress(inout headers_t hdr,
-               inout local_metadata_t local_metadata,
-               inout standard_metadata_t standard_metadata) {
-
-    apply {
-        port_counters_egress.apply(hdr, standard_metadata);
-        packetio_egress.apply(hdr, standard_metadata);
-    }
-}
-
-//------------------------------------------------------------------------------
-// SWITCH INSTANTIATION
-//------------------------------------------------------------------------------
-
-V1Switch(parser_impl(),
-         verify_checksum_control(),
-         ingress(),
-         egress(),
-         compute_checksum_control(),
-         deparser()) main;
diff --git a/pipelines/basic/src/main/resources/include/headers.p4 b/pipelines/basic/src/main/resources/include/headers.p4
index b943c82..038e952 100644
--- a/pipelines/basic/src/main/resources/include/headers.p4
+++ b/pipelines/basic/src/main/resources/include/headers.p4
@@ -84,7 +84,6 @@
     bit<16>       l4_src_port;
     bit<16>       l4_dst_port;
     next_hop_id_t next_hop_id;
-    bit<16>       selector;
 }
 
 #endif
diff --git a/pipelines/basic/src/main/resources/p4c-out/bmv2/basic.json b/pipelines/basic/src/main/resources/p4c-out/bmv2/basic.json
index a38b290..f1a65d1 100644
--- a/pipelines/basic/src/main/resources/p4c-out/bmv2/basic.json
+++ b/pipelines/basic/src/main/resources/p4c-out/bmv2/basic.json
@@ -13,8 +13,7 @@
         ["tmp_0", 32, false],
         ["local_metadata_t.l4_src_port", 16, false],
         ["local_metadata_t.l4_dst_port", 16, false],
-        ["local_metadata_t.next_hop_id", 16, false],
-        ["local_metadata_t.selector", 16, false]
+        ["local_metadata_t.next_hop_id", 16, false]
       ]
     },
     {
diff --git a/pipelines/basic/src/main/resources/p4c-out/bmv2/ecmp.json b/pipelines/basic/src/main/resources/p4c-out/bmv2/ecmp.json
deleted file mode 100644
index 9c687f9..0000000
--- a/pipelines/basic/src/main/resources/p4c-out/bmv2/ecmp.json
+++ /dev/null
@@ -1,1244 +0,0 @@
-{
-  "program" : "ecmp.p4",
-  "__meta__" : {
-    "version" : [2, 7],
-    "compiler" : "https://github.com/p4lang/p4c"
-  },
-  "header_types" : [
-    {
-      "name" : "scalars_0",
-      "id" : 0,
-      "fields" : [
-        ["tmp", 32, false],
-        ["tmp_0", 32, false],
-        ["local_metadata_t.l4_src_port", 16, false],
-        ["local_metadata_t.l4_dst_port", 16, false],
-        ["local_metadata_t.next_hop_id", 16, false],
-        ["local_metadata_t.selector", 16, false]
-      ]
-    },
-    {
-      "name" : "ethernet_t",
-      "id" : 1,
-      "fields" : [
-        ["dst_addr", 48, false],
-        ["src_addr", 48, false],
-        ["ether_type", 16, false]
-      ]
-    },
-    {
-      "name" : "ipv4_t",
-      "id" : 2,
-      "fields" : [
-        ["version", 4, false],
-        ["ihl", 4, false],
-        ["diffserv", 8, false],
-        ["len", 16, false],
-        ["identification", 16, false],
-        ["flags", 3, false],
-        ["frag_offset", 13, false],
-        ["ttl", 8, false],
-        ["protocol", 8, false],
-        ["hdr_checksum", 16, false],
-        ["src_addr", 32, false],
-        ["dst_addr", 32, false]
-      ]
-    },
-    {
-      "name" : "tcp_t",
-      "id" : 3,
-      "fields" : [
-        ["src_port", 16, false],
-        ["dst_port", 16, false],
-        ["seq_no", 32, false],
-        ["ack_no", 32, false],
-        ["data_offset", 4, false],
-        ["res", 3, false],
-        ["ecn", 3, false],
-        ["ctrl", 6, false],
-        ["window", 16, false],
-        ["checksum", 16, false],
-        ["urgent_ptr", 16, false]
-      ]
-    },
-    {
-      "name" : "udp_t",
-      "id" : 4,
-      "fields" : [
-        ["src_port", 16, false],
-        ["dst_port", 16, false],
-        ["length_", 16, false],
-        ["checksum", 16, false]
-      ]
-    },
-    {
-      "name" : "packet_out_header_t",
-      "id" : 5,
-      "fields" : [
-        ["egress_port", 9, false],
-        ["_padding", 7, false]
-      ]
-    },
-    {
-      "name" : "packet_in_header_t",
-      "id" : 6,
-      "fields" : [
-        ["ingress_port", 9, false],
-        ["_padding_0", 7, false]
-      ]
-    },
-    {
-      "name" : "standard_metadata",
-      "id" : 7,
-      "fields" : [
-        ["ingress_port", 9, false],
-        ["egress_spec", 9, false],
-        ["egress_port", 9, false],
-        ["clone_spec", 32, false],
-        ["instance_type", 32, false],
-        ["drop", 1, false],
-        ["recirculate_port", 16, false],
-        ["packet_length", 32, false],
-        ["enq_timestamp", 32, false],
-        ["enq_qdepth", 19, false],
-        ["deq_timedelta", 32, false],
-        ["deq_qdepth", 19, false],
-        ["ingress_global_timestamp", 48, false],
-        ["lf_field_list", 32, false],
-        ["mcast_grp", 16, false],
-        ["resubmit_flag", 1, false],
-        ["egress_rid", 16, false],
-        ["checksum_error", 1, false],
-        ["_padding_1", 4, false]
-      ]
-    }
-  ],
-  "headers" : [
-    {
-      "name" : "scalars",
-      "id" : 0,
-      "header_type" : "scalars_0",
-      "metadata" : true,
-      "pi_omit" : true
-    },
-    {
-      "name" : "standard_metadata",
-      "id" : 1,
-      "header_type" : "standard_metadata",
-      "metadata" : true,
-      "pi_omit" : true
-    },
-    {
-      "name" : "ethernet",
-      "id" : 2,
-      "header_type" : "ethernet_t",
-      "metadata" : false,
-      "pi_omit" : true
-    },
-    {
-      "name" : "ipv4",
-      "id" : 3,
-      "header_type" : "ipv4_t",
-      "metadata" : false,
-      "pi_omit" : true
-    },
-    {
-      "name" : "tcp",
-      "id" : 4,
-      "header_type" : "tcp_t",
-      "metadata" : false,
-      "pi_omit" : true
-    },
-    {
-      "name" : "udp",
-      "id" : 5,
-      "header_type" : "udp_t",
-      "metadata" : false,
-      "pi_omit" : true
-    },
-    {
-      "name" : "packet_out",
-      "id" : 6,
-      "header_type" : "packet_out_header_t",
-      "metadata" : false,
-      "pi_omit" : true
-    },
-    {
-      "name" : "packet_in",
-      "id" : 7,
-      "header_type" : "packet_in_header_t",
-      "metadata" : false,
-      "pi_omit" : true
-    }
-  ],
-  "header_stacks" : [],
-  "header_union_types" : [],
-  "header_unions" : [],
-  "header_union_stacks" : [],
-  "field_lists" : [],
-  "errors" : [
-    ["NoError", 1],
-    ["PacketTooShort", 2],
-    ["NoMatch", 3],
-    ["StackOutOfBounds", 4],
-    ["HeaderTooShort", 5],
-    ["ParserTimeout", 6]
-  ],
-  "enums" : [],
-  "parsers" : [
-    {
-      "name" : "parser",
-      "id" : 0,
-      "init_state" : "start",
-      "parse_states" : [
-        {
-          "name" : "start",
-          "id" : 0,
-          "parser_ops" : [],
-          "transitions" : [
-            {
-              "value" : "0x00ff",
-              "mask" : null,
-              "next_state" : "parse_packet_out"
-            },
-            {
-              "value" : "default",
-              "mask" : null,
-              "next_state" : "parse_ethernet"
-            }
-          ],
-          "transition_key" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "ingress_port"]
-            }
-          ]
-        },
-        {
-          "name" : "parse_packet_out",
-          "id" : 1,
-          "parser_ops" : [
-            {
-              "parameters" : [
-                {
-                  "type" : "regular",
-                  "value" : "packet_out"
-                }
-              ],
-              "op" : "extract"
-            }
-          ],
-          "transitions" : [
-            {
-              "value" : "default",
-              "mask" : null,
-              "next_state" : "parse_ethernet"
-            }
-          ],
-          "transition_key" : []
-        },
-        {
-          "name" : "parse_ethernet",
-          "id" : 2,
-          "parser_ops" : [
-            {
-              "parameters" : [
-                {
-                  "type" : "regular",
-                  "value" : "ethernet"
-                }
-              ],
-              "op" : "extract"
-            }
-          ],
-          "transitions" : [
-            {
-              "value" : "0x0800",
-              "mask" : null,
-              "next_state" : "parse_ipv4"
-            },
-            {
-              "value" : "default",
-              "mask" : null,
-              "next_state" : null
-            }
-          ],
-          "transition_key" : [
-            {
-              "type" : "field",
-              "value" : ["ethernet", "ether_type"]
-            }
-          ]
-        },
-        {
-          "name" : "parse_ipv4",
-          "id" : 3,
-          "parser_ops" : [
-            {
-              "parameters" : [
-                {
-                  "type" : "regular",
-                  "value" : "ipv4"
-                }
-              ],
-              "op" : "extract"
-            }
-          ],
-          "transitions" : [
-            {
-              "value" : "0x06",
-              "mask" : null,
-              "next_state" : "parse_tcp"
-            },
-            {
-              "value" : "0x11",
-              "mask" : null,
-              "next_state" : "parse_udp"
-            },
-            {
-              "value" : "default",
-              "mask" : null,
-              "next_state" : null
-            }
-          ],
-          "transition_key" : [
-            {
-              "type" : "field",
-              "value" : ["ipv4", "protocol"]
-            }
-          ]
-        },
-        {
-          "name" : "parse_tcp",
-          "id" : 4,
-          "parser_ops" : [
-            {
-              "parameters" : [
-                {
-                  "type" : "regular",
-                  "value" : "tcp"
-                }
-              ],
-              "op" : "extract"
-            },
-            {
-              "parameters" : [
-                {
-                  "type" : "field",
-                  "value" : ["scalars", "local_metadata_t.l4_src_port"]
-                },
-                {
-                  "type" : "field",
-                  "value" : ["tcp", "src_port"]
-                }
-              ],
-              "op" : "set"
-            },
-            {
-              "parameters" : [
-                {
-                  "type" : "field",
-                  "value" : ["scalars", "local_metadata_t.l4_dst_port"]
-                },
-                {
-                  "type" : "field",
-                  "value" : ["tcp", "dst_port"]
-                }
-              ],
-              "op" : "set"
-            }
-          ],
-          "transitions" : [
-            {
-              "value" : "default",
-              "mask" : null,
-              "next_state" : null
-            }
-          ],
-          "transition_key" : []
-        },
-        {
-          "name" : "parse_udp",
-          "id" : 5,
-          "parser_ops" : [
-            {
-              "parameters" : [
-                {
-                  "type" : "regular",
-                  "value" : "udp"
-                }
-              ],
-              "op" : "extract"
-            },
-            {
-              "parameters" : [
-                {
-                  "type" : "field",
-                  "value" : ["scalars", "local_metadata_t.l4_src_port"]
-                },
-                {
-                  "type" : "field",
-                  "value" : ["udp", "src_port"]
-                }
-              ],
-              "op" : "set"
-            },
-            {
-              "parameters" : [
-                {
-                  "type" : "field",
-                  "value" : ["scalars", "local_metadata_t.l4_dst_port"]
-                },
-                {
-                  "type" : "field",
-                  "value" : ["udp", "dst_port"]
-                }
-              ],
-              "op" : "set"
-            }
-          ],
-          "transitions" : [
-            {
-              "value" : "default",
-              "mask" : null,
-              "next_state" : null
-            }
-          ],
-          "transition_key" : []
-        }
-      ]
-    }
-  ],
-  "deparsers" : [
-    {
-      "name" : "deparser",
-      "id" : 0,
-      "source_info" : {
-        "filename" : "include/parsers.p4",
-        "line" : 72,
-        "column" : 8,
-        "source_fragment" : "deparser"
-      },
-      "order" : ["packet_in", "ethernet", "ipv4", "tcp", "udp"]
-    }
-  ],
-  "meter_arrays" : [],
-  "counter_arrays" : [
-    {
-      "name" : "port_counters_ingress.ingress_port_counter",
-      "id" : 0,
-      "source_info" : {
-        "filename" : "include/port_counters.p4",
-        "line" : 26,
-        "column" : 38,
-        "source_fragment" : "ingress_port_counter"
-      },
-      "size" : 511,
-      "is_direct" : false
-    },
-    {
-      "name" : "table0_control.table0_counter",
-      "id" : 1,
-      "is_direct" : true,
-      "binding" : "table0_control.table0"
-    },
-    {
-      "name" : "ecmp_table_counter",
-      "id" : 2,
-      "is_direct" : true,
-      "binding" : "ecmp_table"
-    },
-    {
-      "name" : "port_counters_egress.egress_port_counter",
-      "id" : 3,
-      "source_info" : {
-        "filename" : "include/port_counters.p4",
-        "line" : 36,
-        "column" : 38,
-        "source_fragment" : "egress_port_counter"
-      },
-      "size" : 511,
-      "is_direct" : false
-    }
-  ],
-  "register_arrays" : [],
-  "calculations" : [
-    {
-      "name" : "calc",
-      "id" : 0,
-      "algo" : "crc16",
-      "input" : [
-        {
-          "type" : "field",
-          "value" : ["ipv4", "src_addr"]
-        },
-        {
-          "type" : "field",
-          "value" : ["ipv4", "dst_addr"]
-        },
-        {
-          "type" : "field",
-          "value" : ["ipv4", "protocol"]
-        },
-        {
-          "type" : "field",
-          "value" : ["scalars", "local_metadata_t.l4_src_port"]
-        },
-        {
-          "type" : "field",
-          "value" : ["scalars", "local_metadata_t.l4_dst_port"]
-        }
-      ]
-    }
-  ],
-  "learn_lists" : [],
-  "actions" : [
-    {
-      "name" : "set_egress_port",
-      "id" : 0,
-      "runtime_data" : [
-        {
-          "name" : "port",
-          "bitwidth" : 9
-        }
-      ],
-      "primitives" : [
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_spec"]
-            },
-            {
-              "type" : "runtime_data",
-              "value" : 0
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 28,
-            "column" : 36,
-            "source_fragment" : "port; ..."
-          }
-        }
-      ]
-    },
-    {
-      "name" : "set_egress_port",
-      "id" : 1,
-      "runtime_data" : [
-        {
-          "name" : "port",
-          "bitwidth" : 9
-        }
-      ],
-      "primitives" : [
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_spec"]
-            },
-            {
-              "type" : "runtime_data",
-              "value" : 0
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 28,
-            "column" : 36,
-            "source_fragment" : "port; ..."
-          }
-        }
-      ]
-    },
-    {
-      "name" : "send_to_cpu",
-      "id" : 2,
-      "runtime_data" : [],
-      "primitives" : [
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_spec"]
-            },
-            {
-              "type" : "hexstr",
-              "value" : "0x00ff"
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/headers.p4",
-            "line" : 19,
-            "column" : 24,
-            "source_fragment" : "255; ..."
-          }
-        }
-      ]
-    },
-    {
-      "name" : "_drop",
-      "id" : 3,
-      "runtime_data" : [],
-      "primitives" : [
-        {
-          "op" : "drop",
-          "parameters" : [],
-          "source_info" : {
-            "filename" : "include/actions.p4",
-            "line" : 32,
-            "column" : 4,
-            "source_fragment" : "mark_to_drop()"
-          }
-        }
-      ]
-    },
-    {
-      "name" : "NoAction",
-      "id" : 4,
-      "runtime_data" : [],
-      "primitives" : []
-    },
-    {
-      "name" : "table0_control.set_next_hop_id",
-      "id" : 5,
-      "runtime_data" : [
-        {
-          "name" : "next_hop_id",
-          "bitwidth" : 16
-        }
-      ],
-      "primitives" : [
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["scalars", "local_metadata_t.next_hop_id"]
-            },
-            {
-              "type" : "runtime_data",
-              "value" : 0
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/table0.p4",
-            "line" : 30,
-            "column" : 8,
-            "source_fragment" : "local_metadata.next_hop_id = next_hop_id"
-          }
-        }
-      ]
-    },
-    {
-      "name" : "set_ecmp_selector",
-      "id" : 6,
-      "runtime_data" : [],
-      "primitives" : [
-        {
-          "op" : "modify_field_with_hash_based_offset",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["scalars", "local_metadata_t.selector"]
-            },
-            {
-              "type" : "hexstr",
-              "value" : "0x0000000000000000"
-            },
-            {
-              "type" : "calculation",
-              "value" : "calc"
-            },
-            {
-              "type" : "hexstr",
-              "value" : "0x00000000000000000000000000000002"
-            }
-          ],
-          "source_info" : {
-            "filename" : "ecmp.p4",
-            "line" : 60,
-            "column" : 8,
-            "source_fragment" : "hash(local_metadata.selector, HashAlgorithm.crc16, (bit<64>) 0, ..."
-          }
-        }
-      ]
-    },
-    {
-      "name" : "act",
-      "id" : 7,
-      "runtime_data" : [],
-      "primitives" : [
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_spec"]
-            },
-            {
-              "type" : "field",
-              "value" : ["packet_out", "egress_port"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/packet_io.p4",
-            "line" : 27,
-            "column" : 12,
-            "source_fragment" : "standard_metadata.egress_spec = hdr.packet_out.egress_port"
-          }
-        },
-        {
-          "op" : "remove_header",
-          "parameters" : [
-            {
-              "type" : "header",
-              "value" : "packet_out"
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/packet_io.p4",
-            "line" : 28,
-            "column" : 12,
-            "source_fragment" : "hdr.packet_out.setInvalid()"
-          }
-        }
-      ]
-    },
-    {
-      "name" : "act_0",
-      "id" : 8,
-      "runtime_data" : [],
-      "primitives" : [
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["scalars", "tmp"]
-            },
-            {
-              "type" : "expression",
-              "value" : {
-                "type" : "expression",
-                "value" : {
-                  "op" : "&",
-                  "left" : {
-                    "type" : "field",
-                    "value" : ["standard_metadata", "ingress_port"]
-                  },
-                  "right" : {
-                    "type" : "hexstr",
-                    "value" : "0xffffffff"
-                  }
-                }
-              }
-            }
-          ]
-        },
-        {
-          "op" : "count",
-          "parameters" : [
-            {
-              "type" : "counter_array",
-              "value" : "port_counters_ingress.ingress_port_counter"
-            },
-            {
-              "type" : "field",
-              "value" : ["scalars", "tmp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/port_counters.p4",
-            "line" : 29,
-            "column" : 8,
-            "source_fragment" : "ingress_port_counter.count((bit<32>) standard_metadata.ingress_port)"
-          }
-        }
-      ]
-    },
-    {
-      "name" : "act_1",
-      "id" : 9,
-      "runtime_data" : [],
-      "primitives" : [
-        {
-          "op" : "add_header",
-          "parameters" : [
-            {
-              "type" : "header",
-              "value" : "packet_in"
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/packet_io.p4",
-            "line" : 38,
-            "column" : 12,
-            "source_fragment" : "hdr.packet_in.setValid()"
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["packet_in", "ingress_port"]
-            },
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "ingress_port"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/packet_io.p4",
-            "line" : 39,
-            "column" : 12,
-            "source_fragment" : "hdr.packet_in.ingress_port = standard_metadata.ingress_port"
-          }
-        }
-      ]
-    },
-    {
-      "name" : "act_2",
-      "id" : 10,
-      "runtime_data" : [],
-      "primitives" : [
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["scalars", "tmp_0"]
-            },
-            {
-              "type" : "expression",
-              "value" : {
-                "type" : "expression",
-                "value" : {
-                  "op" : "&",
-                  "left" : {
-                    "type" : "field",
-                    "value" : ["standard_metadata", "egress_port"]
-                  },
-                  "right" : {
-                    "type" : "hexstr",
-                    "value" : "0xffffffff"
-                  }
-                }
-              }
-            }
-          ]
-        },
-        {
-          "op" : "count",
-          "parameters" : [
-            {
-              "type" : "counter_array",
-              "value" : "port_counters_egress.egress_port_counter"
-            },
-            {
-              "type" : "field",
-              "value" : ["scalars", "tmp_0"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "include/port_counters.p4",
-            "line" : 39,
-            "column" : 8,
-            "source_fragment" : "egress_port_counter.count((bit<32>) standard_metadata.egress_port)"
-          }
-        }
-      ]
-    }
-  ],
-  "pipelines" : [
-    {
-      "name" : "ingress",
-      "id" : 0,
-      "source_info" : {
-        "filename" : "ecmp.p4",
-        "line" : 42,
-        "column" : 8,
-        "source_fragment" : "ingress"
-      },
-      "init_table" : "tbl_act",
-      "tables" : [
-        {
-          "name" : "tbl_act",
-          "id" : 0,
-          "key" : [],
-          "match_type" : "exact",
-          "type" : "simple",
-          "max_size" : 1024,
-          "with_counters" : false,
-          "support_timeout" : false,
-          "direct_meters" : null,
-          "action_ids" : [8],
-          "actions" : ["act_0"],
-          "base_default_next" : "node_3",
-          "next_tables" : {
-            "act_0" : "node_3"
-          },
-          "default_entry" : {
-            "action_id" : 8,
-            "action_const" : true,
-            "action_data" : [],
-            "action_entry_const" : true
-          }
-        },
-        {
-          "name" : "tbl_act_0",
-          "id" : 1,
-          "key" : [],
-          "match_type" : "exact",
-          "type" : "simple",
-          "max_size" : 1024,
-          "with_counters" : false,
-          "support_timeout" : false,
-          "direct_meters" : null,
-          "action_ids" : [7],
-          "actions" : ["act"],
-          "base_default_next" : null,
-          "next_tables" : {
-            "act" : null
-          },
-          "default_entry" : {
-            "action_id" : 7,
-            "action_const" : true,
-            "action_data" : [],
-            "action_entry_const" : true
-          }
-        },
-        {
-          "name" : "table0_control.table0",
-          "id" : 2,
-          "source_info" : {
-            "filename" : "include/table0.p4",
-            "line" : 33,
-            "column" : 10,
-            "source_fragment" : "table0"
-          },
-          "key" : [
-            {
-              "match_type" : "ternary",
-              "target" : ["standard_metadata", "ingress_port"],
-              "mask" : null
-            },
-            {
-              "match_type" : "ternary",
-              "target" : ["ethernet", "src_addr"],
-              "mask" : null
-            },
-            {
-              "match_type" : "ternary",
-              "target" : ["ethernet", "dst_addr"],
-              "mask" : null
-            },
-            {
-              "match_type" : "ternary",
-              "target" : ["ethernet", "ether_type"],
-              "mask" : null
-            },
-            {
-              "match_type" : "ternary",
-              "target" : ["ipv4", "src_addr"],
-              "mask" : null
-            },
-            {
-              "match_type" : "ternary",
-              "target" : ["ipv4", "dst_addr"],
-              "mask" : null
-            },
-            {
-              "match_type" : "ternary",
-              "target" : ["ipv4", "protocol"],
-              "mask" : null
-            },
-            {
-              "match_type" : "ternary",
-              "target" : ["scalars", "local_metadata_t.l4_src_port"],
-              "mask" : null
-            },
-            {
-              "match_type" : "ternary",
-              "target" : ["scalars", "local_metadata_t.l4_dst_port"],
-              "mask" : null
-            }
-          ],
-          "match_type" : "ternary",
-          "type" : "simple",
-          "max_size" : 1024,
-          "support_timeout" : false,
-          "direct_meters" : null,
-          "action_ids" : [0, 2, 5, 3],
-          "actions" : ["set_egress_port", "send_to_cpu", "table0_control.set_next_hop_id", "_drop"],
-          "base_default_next" : "node_6",
-          "next_tables" : {
-            "set_egress_port" : "node_6",
-            "send_to_cpu" : "node_6",
-            "table0_control.set_next_hop_id" : "node_6",
-            "_drop" : "node_6"
-          },
-          "default_entry" : {
-            "action_id" : 3,
-            "action_const" : true,
-            "action_data" : [],
-            "action_entry_const" : true
-          }
-        },
-        {
-          "name" : "tbl_set_ecmp_selector",
-          "id" : 3,
-          "key" : [],
-          "match_type" : "exact",
-          "type" : "simple",
-          "max_size" : 1024,
-          "with_counters" : false,
-          "support_timeout" : false,
-          "direct_meters" : null,
-          "action_ids" : [6],
-          "actions" : ["set_ecmp_selector"],
-          "base_default_next" : "ecmp_table",
-          "next_tables" : {
-            "set_ecmp_selector" : "ecmp_table"
-          },
-          "default_entry" : {
-            "action_id" : 6,
-            "action_const" : true,
-            "action_data" : [],
-            "action_entry_const" : true
-          }
-        },
-        {
-          "name" : "ecmp_table",
-          "id" : 4,
-          "source_info" : {
-            "filename" : "ecmp.p4",
-            "line" : 48,
-            "column" : 10,
-            "source_fragment" : "ecmp_table"
-          },
-          "key" : [
-            {
-              "match_type" : "exact",
-              "target" : ["scalars", "local_metadata_t.next_hop_id"],
-              "mask" : null
-            },
-            {
-              "match_type" : "exact",
-              "target" : ["scalars", "local_metadata_t.selector"],
-              "mask" : null
-            }
-          ],
-          "match_type" : "exact",
-          "type" : "simple",
-          "max_size" : 1024,
-          "support_timeout" : false,
-          "direct_meters" : null,
-          "action_ids" : [1, 4],
-          "actions" : ["set_egress_port", "NoAction"],
-          "base_default_next" : null,
-          "next_tables" : {
-            "set_egress_port" : null,
-            "NoAction" : null
-          },
-          "default_entry" : {
-            "action_id" : 4,
-            "action_const" : false,
-            "action_data" : [],
-            "action_entry_const" : false
-          }
-        }
-      ],
-      "action_profiles" : [],
-      "conditionals" : [
-        {
-          "name" : "node_3",
-          "id" : 0,
-          "source_info" : {
-            "filename" : "include/packet_io.p4",
-            "line" : 26,
-            "column" : 12,
-            "source_fragment" : "standard_metadata.ingress_port == CPU_PORT"
-          },
-          "expression" : {
-            "type" : "expression",
-            "value" : {
-              "op" : "==",
-              "left" : {
-                "type" : "field",
-                "value" : ["standard_metadata", "ingress_port"]
-              },
-              "right" : {
-                "type" : "hexstr",
-                "value" : "0x00ff"
-              }
-            }
-          },
-          "true_next" : "tbl_act_0",
-          "false_next" : "table0_control.table0"
-        },
-        {
-          "name" : "node_6",
-          "id" : 1,
-          "source_info" : {
-            "filename" : "ecmp.p4",
-            "line" : 75,
-            "column" : 12,
-            "source_fragment" : "local_metadata.next_hop_id > 0"
-          },
-          "expression" : {
-            "type" : "expression",
-            "value" : {
-              "op" : ">",
-              "left" : {
-                "type" : "field",
-                "value" : ["scalars", "local_metadata_t.next_hop_id"]
-              },
-              "right" : {
-                "type" : "hexstr",
-                "value" : "0x0000"
-              }
-            }
-          },
-          "false_next" : null,
-          "true_next" : "tbl_set_ecmp_selector"
-        }
-      ]
-    },
-    {
-      "name" : "egress",
-      "id" : 1,
-      "source_info" : {
-        "filename" : "ecmp.p4",
-        "line" : 86,
-        "column" : 8,
-        "source_fragment" : "egress"
-      },
-      "init_table" : "tbl_act_1",
-      "tables" : [
-        {
-          "name" : "tbl_act_1",
-          "id" : 5,
-          "key" : [],
-          "match_type" : "exact",
-          "type" : "simple",
-          "max_size" : 1024,
-          "with_counters" : false,
-          "support_timeout" : false,
-          "direct_meters" : null,
-          "action_ids" : [10],
-          "actions" : ["act_2"],
-          "base_default_next" : "node_12",
-          "next_tables" : {
-            "act_2" : "node_12"
-          },
-          "default_entry" : {
-            "action_id" : 10,
-            "action_const" : true,
-            "action_data" : [],
-            "action_entry_const" : true
-          }
-        },
-        {
-          "name" : "tbl_act_2",
-          "id" : 6,
-          "key" : [],
-          "match_type" : "exact",
-          "type" : "simple",
-          "max_size" : 1024,
-          "with_counters" : false,
-          "support_timeout" : false,
-          "direct_meters" : null,
-          "action_ids" : [9],
-          "actions" : ["act_1"],
-          "base_default_next" : null,
-          "next_tables" : {
-            "act_1" : null
-          },
-          "default_entry" : {
-            "action_id" : 9,
-            "action_const" : true,
-            "action_data" : [],
-            "action_entry_const" : true
-          }
-        }
-      ],
-      "action_profiles" : [],
-      "conditionals" : [
-        {
-          "name" : "node_12",
-          "id" : 2,
-          "source_info" : {
-            "filename" : "include/packet_io.p4",
-            "line" : 37,
-            "column" : 12,
-            "source_fragment" : "standard_metadata.egress_port == CPU_PORT"
-          },
-          "expression" : {
-            "type" : "expression",
-            "value" : {
-              "op" : "==",
-              "left" : {
-                "type" : "field",
-                "value" : ["standard_metadata", "egress_port"]
-              },
-              "right" : {
-                "type" : "hexstr",
-                "value" : "0x00ff"
-              }
-            }
-          },
-          "false_next" : null,
-          "true_next" : "tbl_act_2"
-        }
-      ]
-    }
-  ],
-  "checksums" : [],
-  "force_arith" : [],
-  "extern_instances" : [],
-  "field_aliases" : [
-    [
-      "queueing_metadata.enq_timestamp",
-      ["standard_metadata", "enq_timestamp"]
-    ],
-    [
-      "queueing_metadata.enq_qdepth",
-      ["standard_metadata", "enq_qdepth"]
-    ],
-    [
-      "queueing_metadata.deq_timedelta",
-      ["standard_metadata", "deq_timedelta"]
-    ],
-    [
-      "queueing_metadata.deq_qdepth",
-      ["standard_metadata", "deq_qdepth"]
-    ],
-    [
-      "intrinsic_metadata.ingress_global_timestamp",
-      ["standard_metadata", "ingress_global_timestamp"]
-    ],
-    [
-      "intrinsic_metadata.lf_field_list",
-      ["standard_metadata", "lf_field_list"]
-    ],
-    [
-      "intrinsic_metadata.mcast_grp",
-      ["standard_metadata", "mcast_grp"]
-    ],
-    [
-      "intrinsic_metadata.resubmit_flag",
-      ["standard_metadata", "resubmit_flag"]
-    ],
-    [
-      "intrinsic_metadata.egress_rid",
-      ["standard_metadata", "egress_rid"]
-    ]
-  ]
-}
\ No newline at end of file
diff --git a/pipelines/basic/src/main/resources/p4c-out/bmv2/ecmp.p4info b/pipelines/basic/src/main/resources/p4c-out/bmv2/ecmp.p4info
deleted file mode 100644
index 08a3dd9..0000000
--- a/pipelines/basic/src/main/resources/p4c-out/bmv2/ecmp.p4info
+++ /dev/null
@@ -1,224 +0,0 @@
-tables {
-  preamble {
-    id: 33571508
-    name: "table0_control.table0"
-    alias: "table0"
-  }
-  match_fields {
-    id: 1
-    name: "standard_metadata.ingress_port"
-    bitwidth: 9
-    match_type: TERNARY
-  }
-  match_fields {
-    id: 2
-    name: "hdr.ethernet.src_addr"
-    bitwidth: 48
-    match_type: TERNARY
-  }
-  match_fields {
-    id: 3
-    name: "hdr.ethernet.dst_addr"
-    bitwidth: 48
-    match_type: TERNARY
-  }
-  match_fields {
-    id: 4
-    name: "hdr.ethernet.ether_type"
-    bitwidth: 16
-    match_type: TERNARY
-  }
-  match_fields {
-    id: 5
-    name: "hdr.ipv4.src_addr"
-    bitwidth: 32
-    match_type: TERNARY
-  }
-  match_fields {
-    id: 6
-    name: "hdr.ipv4.dst_addr"
-    bitwidth: 32
-    match_type: TERNARY
-  }
-  match_fields {
-    id: 7
-    name: "hdr.ipv4.protocol"
-    bitwidth: 8
-    match_type: TERNARY
-  }
-  match_fields {
-    id: 8
-    name: "local_metadata.l4_src_port"
-    bitwidth: 16
-    match_type: TERNARY
-  }
-  match_fields {
-    id: 9
-    name: "local_metadata.l4_dst_port"
-    bitwidth: 16
-    match_type: TERNARY
-  }
-  action_refs {
-    id: 16794308
-  }
-  action_refs {
-    id: 16829080
-  }
-  action_refs {
-    id: 16802895
-  }
-  action_refs {
-    id: 16784184
-  }
-  const_default_action_id: 16784184
-  direct_resource_ids: 302046050
-  size: 1024
-}
-tables {
-  preamble {
-    id: 33601431
-    name: "ecmp_table"
-    alias: "ecmp_table"
-  }
-  match_fields {
-    id: 1
-    name: "local_metadata.next_hop_id"
-    bitwidth: 16
-    match_type: EXACT
-  }
-  match_fields {
-    id: 2
-    name: "local_metadata.selector"
-    bitwidth: 16
-    match_type: EXACT
-  }
-  action_refs {
-    id: 16794308
-  }
-  action_refs {
-    id: 16800567
-    annotations: "@defaultonly()"
-  }
-  direct_resource_ids: 302010883
-  size: 1024
-}
-actions {
-  preamble {
-    id: 16794308
-    name: "set_egress_port"
-    alias: "set_egress_port"
-  }
-  params {
-    id: 1
-    name: "port"
-    bitwidth: 9
-  }
-}
-actions {
-  preamble {
-    id: 16829080
-    name: "send_to_cpu"
-    alias: "send_to_cpu"
-  }
-}
-actions {
-  preamble {
-    id: 16784184
-    name: "_drop"
-    alias: "_drop"
-  }
-}
-actions {
-  preamble {
-    id: 16800567
-    name: "NoAction"
-    alias: "NoAction"
-  }
-}
-actions {
-  preamble {
-    id: 16802895
-    name: "table0_control.set_next_hop_id"
-    alias: "set_next_hop_id"
-  }
-  params {
-    id: 1
-    name: "next_hop_id"
-    bitwidth: 16
-  }
-}
-actions {
-  preamble {
-    id: 16789898
-    name: "set_ecmp_selector"
-    alias: "set_ecmp_selector"
-  }
-}
-counters {
-  preamble {
-    id: 302012579
-    name: "port_counters_ingress.ingress_port_counter"
-    alias: "ingress_port_counter"
-  }
-  spec {
-    unit: PACKETS
-  }
-  size: 511
-}
-counters {
-  preamble {
-    id: 302012501
-    name: "port_counters_egress.egress_port_counter"
-    alias: "egress_port_counter"
-  }
-  spec {
-    unit: PACKETS
-  }
-  size: 511
-}
-direct_counters {
-  preamble {
-    id: 302046050
-    name: "table0_control.table0_counter"
-    alias: "table0_counter"
-  }
-  spec {
-    unit: BOTH
-  }
-  direct_table_id: 33571508
-}
-direct_counters {
-  preamble {
-    id: 302010883
-    name: "ecmp_table_counter"
-    alias: "ecmp_table_counter"
-  }
-  spec {
-    unit: BOTH
-  }
-  direct_table_id: 33601431
-}
-controller_packet_metadata {
-  preamble {
-    id: 2868941301
-    name: "packet_in"
-    annotations: "@controller_header(\"packet_in\")"
-  }
-  metadata {
-    id: 1
-    name: "ingress_port"
-    bitwidth: 9
-  }
-}
-controller_packet_metadata {
-  preamble {
-    id: 2868916615
-    name: "packet_out"
-    annotations: "@controller_header(\"packet_out\")"
-  }
-  metadata {
-    id: 1
-    name: "egress_port"
-    bitwidth: 9
-  }
-}