Final implementation of Tor app

Change-Id: I5626171c0a872a596e285a11499189a3c6dc950b
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 485fc12..59af9dd 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
@@ -85,8 +85,9 @@
 
     private static final Map<String, AbstractUpgradableFabricApp> APP_HANDLES = Maps.newConcurrentMap();
 
-
-    private static final boolean TEST_MODE = true;
+    // Set to true to install test flow rules and groups to
+    // devices, without waiting for the full topology to come up.
+    private static final boolean TEST_MODE = false;
 
     // TOPO_SIZE should be the same of the --size argument when running bmv2-demo.py
     private static final int TOPO_SIZE = 2;
@@ -137,7 +138,7 @@
     private AbstractUpgradableFabricApp otherApp;
 
     private boolean flowRuleGenerated = false;
-    private ApplicationId appId;
+    protected ApplicationId appId;
 
     private Collection<PiPipeconf> appPipeconfs;
 
@@ -231,6 +232,10 @@
          */
         scheduledExecutorService.scheduleAtFixedRate(this::checkTopologyAndGenerateFlowRules,
                                                      0, CHECK_TOPOLOGY_INTERVAL_SECONDS, TimeUnit.SECONDS);
+
+        if (TEST_MODE) {
+            deviceService.getAvailableDevices().forEach(d -> testDevice(d.id()));
+        }
     }
 
     private void setAppFreezed(boolean appFreezed) {
@@ -364,6 +369,10 @@
      */
     private synchronized void checkTopologyAndGenerateFlowRules() {
 
+        if (TEST_MODE) {
+            return;
+        }
+
         Topology topo = topologyService.currentTopology();
         Set<Host> hosts = Sets.newHashSet(hostService.getHosts());
 
@@ -491,6 +500,14 @@
         return topologyService.isInfrastructure(topology, new ConnectPoint(port.element().id(), port.number()));
     }
 
+    private void testDevice(DeviceId deviceId) {
+        try {
+            generateTestFlowRules(deviceId).forEach(flowRuleService::applyFlowRules);
+        } catch (FlowRuleGeneratorException e) {
+            log.error("Exception while generating test flow rules for {}: {}", deviceId, e);
+        }
+    }
+
     /**
      * A listener of device events that executes a device deploy task each time a device is added, updated or
      * re-connects.
@@ -498,15 +515,12 @@
     private class InternalDeviceListener implements DeviceListener {
         @Override
         public void event(DeviceEvent event) {
-            spawnTask(() -> deployDevice(event.subject()));
-            if (event.type() == DEVICE_ADDED && TEST_MODE) {
-                DeviceId deviceId = event.subject().id();
-                try {
-                    generateTestFlowRules(deviceId).forEach(flowRuleService::applyFlowRules);
-                } catch (FlowRuleGeneratorException e) {
-                    log.error("Exception while generating test flow rules for {}: {}", deviceId, e);
+            spawnTask(() -> {
+                deployDevice(event.subject());
+                if (event.type() == DEVICE_ADDED && TEST_MODE) {
+                    testDevice(event.subject().id());
                 }
-            }
+            });
         }
 
         @Override
diff --git a/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorApp.java b/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorApp.java
index f5d43c6..f01d407 100644
--- a/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorApp.java
+++ b/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorApp.java
@@ -17,7 +17,16 @@
 package org.onosproject.pi.demo.app.tor;
 
 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.ImmutableTriple;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.lang3.tuple.Triple;
 import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
@@ -30,22 +39,32 @@
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.FlowRule;
 import org.onosproject.net.flow.TrafficTreatment;
+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.group.GroupService;
 import org.onosproject.net.pi.runtime.PiAction;
-import org.onosproject.net.pi.runtime.PiActionId;
+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;
 import org.onosproject.net.topology.TopologyGraph;
 import org.onosproject.pi.demo.app.common.AbstractUpgradableFabricApp;
 
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
-import java.util.Random;
+import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
 import static com.google.common.collect.Collections2.permutations;
+import static java.util.Collections.singleton;
+import static java.util.stream.Collectors.toSet;
 import static org.onlab.util.ImmutableByteSequence.copyFrom;
 import static org.onosproject.pi.demo.app.tor.Combination.combinations;
 import static org.onosproject.pi.demo.app.tor.TorInterpreter.*;
@@ -58,11 +77,27 @@
 public class TorApp extends AbstractUpgradableFabricApp {
 
     private static final String APP_NAME = "org.onosproject.pi-tor";
+    // DeviceId -> Group reference (ports, smac, dmac) -> group id
+    private static final Map<DeviceId, Map<Triple<Set<PortNumber>, MacAddress, MacAddress>, Integer>>
+            DEVICE_GROUP_ID_MAP = Maps.newHashMap();
+
+    private static final boolean USE_ACTION_WITH_SINGLE_MEMBER_GROUP = false;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private GroupService groupService;
+
+    private final Set<Pair<DeviceId, GroupKey>> groupKeys = Sets.newHashSet();
 
     public TorApp() {
         super(APP_NAME, TorPipeconfFactory.getAll());
     }
 
+    @Deactivate
+    public void deactivate() {
+        groupKeys.forEach(pair -> groupService.removeGroup(pair.getLeft(), pair.getRight(), appId));
+        super.deactivate();
+    }
+
     @Override
     public boolean initDevice(DeviceId deviceId) {
         // Nothing to do.
@@ -70,37 +105,54 @@
     }
 
     public List<FlowRule> generateTestFlowRules(DeviceId deviceId) throws FlowRuleGeneratorException {
-        Random rand = new Random();
+
+        /*
+        To test if the flow rules and groups are working, host pinging should work with this mn configuration:
+            $ sudo -E mn --custom $BMV2_MN_PY \
+                --switch onosbmv2,pipeconfId=bmv2-tor \
+                --controller remote,ip=192.168.56.1 --mac
+         */
         List<FlowRule> rules = Lists.newArrayList();
-        MacAddress ethAddr = MacAddress.ONOS;
-        IpAddress ipAddr = Ip4Address.valueOf(rand.nextInt());
-        PortNumber outPort =  PortNumber.portNumber(1, "eth0");
+        MacAddress ethAddr1 = MacAddress.valueOf("00:00:00:00:00:01");
+        MacAddress ethAddr2 = MacAddress.valueOf("00:00:00:00:00:02");
+        IpAddress ipAddr1 = Ip4Address.valueOf("10.0.0.1");
+        IpAddress ipAddr2 = Ip4Address.valueOf("10.0.0.2");
+        PortNumber outPort1 = PortNumber.portNumber(1);
+        PortNumber outPort2 = PortNumber.portNumber(2);
 
         // Filtering (match on eth, NoAction)
-        rules.add(
-                flowRuleBuilder(deviceId, L3_FILTER_TBL_ID)
-                        .withSelector(DefaultTrafficSelector.builder()
-                                              .matchEthDst(ethAddr)
-                                              .build())
-                        .withTreatment(DefaultTrafficTreatment.builder()
-                                               .piTableAction(PiAction.builder()
-                                                                      .withId(PiActionId.of("NoAction"))
-                                                                      .build())
-                                               .build())
-                        .build());
+        rules.addAll(generateFilteringRules(deviceId, Sets.newHashSet(ethAddr1, ethAddr2)));
 
-        // Forwarding
-        rules.add(
-                flowRuleBuilder(deviceId, L3_FWD_TBL_ID)
-                        .withSelector(
-                                DefaultTrafficSelector.builder()
-                                        .matchIPDst(ipAddr.toIpPrefix())
-                                        .build())
-                        .withTreatment(
-                                DefaultTrafficTreatment.builder()
-                                        .piTableAction(nextHopAction(outPort, ethAddr, ethAddr))
-                                        .build())
-                        .build());
+        try {
+            PiTableAction groupAction1 = provisionGroup(deviceId, singleton(outPort1), ethAddr2, ethAddr1);
+            PiTableAction groupAction2 = provisionGroup(deviceId, singleton(outPort2), ethAddr1, ethAddr2);
+            // Forwarding
+            rules.add(
+                    flowRuleBuilder(deviceId, L3_FWD_TBL_ID)
+                            .withSelector(
+                                    DefaultTrafficSelector.builder()
+                                            .matchIPDst(ipAddr1.toIpPrefix())
+                                            .build())
+                            .withTreatment(
+                                    DefaultTrafficTreatment.builder()
+                                            .piTableAction(groupAction1)
+                                            .build())
+                            .build());
+
+            rules.add(
+                    flowRuleBuilder(deviceId, L3_FWD_TBL_ID)
+                            .withSelector(
+                                    DefaultTrafficSelector.builder()
+                                            .matchIPDst(ipAddr2.toIpPrefix())
+                                            .build())
+                            .withTreatment(
+                                    DefaultTrafficTreatment.builder()
+                                            .piTableAction(groupAction2)
+                                            .build())
+                            .build());
+        } catch (Throwable e) {
+            log.error("{}", e);
+        }
 
         return rules;
     }
@@ -115,7 +167,7 @@
                 .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);
@@ -123,7 +175,7 @@
                 .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={}",
@@ -135,26 +187,13 @@
         List<FlowRule> rules = Lists.newArrayList();
 
         // Filtering rules
-        rules.addAll(generateFilteringRules(leaf, remoteHosts));
-        rules.addAll(generateFilteringRules(leaf, Collections.singleton(localHost)));
-
-        // FIXME: ignore ECMP for the moment
-        // 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();
-        // }
-
-
-        PortNumber outPort = fabricPorts.iterator().next();
+        Set<MacAddress> remoteMacAddrs = remoteHosts.stream().map(Host::mac).collect(toSet());
+        rules.addAll(generateFilteringRules(leaf, remoteMacAddrs));
+        rules.addAll(generateFilteringRules(leaf, singleton(localHost.mac())));
 
         // From local host to remote ones.
         for (Host remoteHost : remoteHosts) {
+            PiTableAction piTableAction = provisionGroup(leaf, fabricPorts, localHost.mac(), remoteHost.mac());
             for (IpAddress ipAddr : remoteHost.ipAddresses()) {
                 FlowRule rule = flowRuleBuilder(leaf, L3_FWD_TBL_ID)
                         .withSelector(
@@ -163,7 +202,7 @@
                                         .build())
                         .withTreatment(
                                 DefaultTrafficTreatment.builder()
-                                        .piTableAction(nextHopAction(outPort, localHost.mac(), remoteHost.mac()))
+                                        .piTableAction(piTableAction)
                                         .build())
                         .build();
                 rules.add(rule);
@@ -173,6 +212,8 @@
         // From remote hosts to the local one
         for (IpAddress dstIpAddr : localHost.ipAddresses()) {
             for (Host remoteHost : remoteHosts) {
+                PiTableAction tableAction = provisionGroup(leaf, singleton(hostPort), remoteHost.mac(),
+                                                           localHost.mac());
                 FlowRule rule = flowRuleBuilder(leaf, L3_FWD_TBL_ID)
                         .withSelector(
                                 DefaultTrafficSelector.builder()
@@ -180,7 +221,7 @@
                                         .build())
                         .withTreatment(
                                 DefaultTrafficTreatment.builder()
-                                        .piTableAction(nextHopAction(hostPort, remoteHost.mac(), localHost.mac()))
+                                        .piTableAction(tableAction)
                                         .build())
                         .build();
                 rules.add(rule);
@@ -190,23 +231,14 @@
         return rules;
     }
 
-    private PiAction nextHopAction(PortNumber port, MacAddress smac, MacAddress dmac) {
-        return PiAction.builder()
-                .withId(SET_NEXT_HOP_ACT_ID)
-                .withParameter(new PiActionParam(PORT_ACT_PRM_ID, copyFrom(port.toLong())))
-                // Ignore L3 routing behaviour by keeping the original host mac addresses at each hop.
-                .withParameter(new PiActionParam(SMAC_ACT_PRM_ID, copyFrom(smac.toBytes())))
-                .withParameter(new PiActionParam(DMAC_ACT_PRM_ID, copyFrom(dmac.toBytes())))
-                .build();
-    }
-
     @Override
     public List<FlowRule> generateSpineRules(DeviceId spine, Set<Host> hosts, Topology topo)
             throws FlowRuleGeneratorException {
 
         List<FlowRule> rules = Lists.newArrayList();
 
-        rules.addAll(generateFilteringRules(spine, hosts));
+        Set<MacAddress> macAddrs = hosts.stream().map(Host::mac).collect(toSet());
+        rules.addAll(generateFilteringRules(spine, macAddrs));
 
         // For each host pair (src -> dst)
         for (Set<Host> hostCombs : combinations(hosts, 2)) {
@@ -222,24 +254,11 @@
                     throw new FlowRuleGeneratorException();
                 }
 
-                TrafficTreatment treatment;
+                Set<PortNumber> ports = paths.stream().map(p -> p.src().port()).collect(toSet());
+                PiTableAction piTableAction = provisionGroup(spine, ports, srcHost.mac(), dstHost.mac());
 
-                // FIXME: ingore ECMP for the moment
-                // 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();
-                // }
-
-                PortNumber port = paths.iterator().next().src().port();
-                treatment = DefaultTrafficTreatment.builder()
-                        .piTableAction(nextHopAction(port, srcHost.mac(), dstHost.mac()))
+                TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                        .piTableAction(piTableAction)
                         .build();
 
                 for (IpAddress dstIpAddr : dstHost.ipAddresses()) {
@@ -259,19 +278,17 @@
         return rules;
     }
 
-    private List<FlowRule> generateFilteringRules(DeviceId deviceId, Collection<Host> dstHosts)
+    private List<FlowRule> generateFilteringRules(DeviceId deviceId, Collection<MacAddress> macAddrs)
             throws FlowRuleGeneratorException {
-
         List<FlowRule> rules = Lists.newArrayList();
-        for (Host host : dstHosts) {
-            MacAddress ethAddr = host.mac();
+        for (MacAddress macAddr : macAddrs) {
             FlowRule rule = flowRuleBuilder(deviceId, L3_FILTER_TBL_ID)
                     .withSelector(DefaultTrafficSelector.builder()
-                                          .matchEthDst(ethAddr)
+                                          .matchEthDst(macAddr)
                                           .build())
                     .withTreatment(DefaultTrafficTreatment.builder()
                                            .piTableAction(PiAction.builder()
-                                                                  .withId(PiActionId.of("NoAction"))
+                                                                  .withId(NO_ACTION_ID)
                                                                   .build())
                                            .build())
                     .build();
@@ -280,4 +297,59 @@
         return rules;
     }
 
+    private PiTableAction provisionGroup(DeviceId deviceId, Set<PortNumber> ports, MacAddress smac,
+                                         MacAddress dmac) {
+
+        if (USE_ACTION_WITH_SINGLE_MEMBER_GROUP && ports.size() == 1) {
+            return nextHopAction(ports.iterator().next(), smac, dmac);
+        }
+
+        DEVICE_GROUP_ID_MAP.putIfAbsent(deviceId, Maps.newHashMap());
+
+        Triple<Set<PortNumber>, MacAddress, MacAddress> groupRef = ImmutableTriple.of(ports, smac, dmac);
+
+        if (!DEVICE_GROUP_ID_MAP.get(deviceId).containsKey(groupRef)) {
+            // Provision group to device
+
+            // We get a group ID by counting the number of unique <ports, smac, dmac> triples for each deviceId.
+            // Each distinct triple will have a unique group ID unique in the scope of a device.
+            int groupId = DEVICE_GROUP_ID_MAP.get(deviceId).size() + 1;
+
+            // Group buckets
+            List<GroupBucket> bucketList = ports.stream()
+                    .map(port -> nextHopAction(port, smac, dmac))
+                    .map(piAction -> DefaultTrafficTreatment.builder().piTableAction(piAction).build())
+                    .map(DefaultGroupBucket::createSelectGroupBucket)
+                    .collect(Collectors.toList());
+
+            // Group cookie (with action profile ID)
+            PiGroupKey groupKey = new PiGroupKey(L3_FWD_TBL_ID, WCMP_ACT_PROF_ID, groupId);
+            groupKeys.add(ImmutablePair.of(deviceId, groupKey));
+
+            // Group desc
+            GroupDescription groupDesc = new DefaultGroupDescription(deviceId,
+                                                                     GroupDescription.Type.SELECT,
+                                                                     new GroupBuckets(bucketList),
+                                                                     groupKey,
+                                                                     groupId,
+                                                                     appId);
+            log.info("Adding group {} to {}...", groupId, deviceId);
+            groupService.addGroup(groupDesc);
+
+            DEVICE_GROUP_ID_MAP.get(deviceId).put(groupRef, groupId);
+        }
+
+        return PiActionGroupId.of(DEVICE_GROUP_ID_MAP.get(deviceId).get(groupRef));
+    }
+
+    private PiAction nextHopAction(PortNumber port, MacAddress smac, MacAddress dmac) {
+        return PiAction.builder()
+                .withId(SET_NEXT_HOP_ACT_ID)
+                .withParameter(new PiActionParam(PORT_ACT_PRM_ID, copyFrom(port.toLong())))
+                // Ignore L3 routing behaviour by keeping the original host mac addresses at each hop.
+                .withParameter(new PiActionParam(SMAC_ACT_PRM_ID, copyFrom(smac.toBytes())))
+                .withParameter(new PiActionParam(DMAC_ACT_PRM_ID, copyFrom(dmac.toBytes())))
+                .build();
+    }
+
 }
\ No newline at end of file
diff --git a/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorInterpreter.java b/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorInterpreter.java
index c893e4a..6bf4f77 100644
--- a/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorInterpreter.java
+++ b/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorInterpreter.java
@@ -38,6 +38,7 @@
 import org.onosproject.net.pi.runtime.PiActionId;
 import org.onosproject.net.pi.runtime.PiActionParam;
 import org.onosproject.net.pi.runtime.PiActionParamId;
+import org.onosproject.net.pi.runtime.PiActionProfileId;
 import org.onosproject.net.pi.runtime.PiCounterId;
 import org.onosproject.net.pi.runtime.PiHeaderFieldId;
 import org.onosproject.net.pi.runtime.PiPacketMetadata;
@@ -83,6 +84,8 @@
     static final PiActionParamId PORT_ACT_PRM_ID = PiActionParamId.of("port");
     static final PiActionParamId SMAC_ACT_PRM_ID = PiActionParamId.of("smac");
     static final PiActionParamId DMAC_ACT_PRM_ID = PiActionParamId.of("dmac");
+    static final PiActionProfileId WCMP_ACT_PROF_ID = PiActionProfileId.of("l3_fwd.wcmp_action_profile");
+    static final PiActionId NO_ACTION_ID = PiActionId.of("NoAction");
 
     // Set as per value in headers.p4 in packet_out_header
     private static final int PORT_FIELD_BITWIDTH = 9;
diff --git a/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorPipeconfFactory.java b/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorPipeconfFactory.java
index 8b6b94d..e09435f 100644
--- a/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorPipeconfFactory.java
+++ b/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorPipeconfFactory.java
@@ -32,10 +32,7 @@
 import java.util.Collection;
 
 import static java.lang.String.format;
-import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.BMV2_JSON;
-import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.P4_INFO_TEXT;
-import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.TOFINO_BIN;
-import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.TOFINO_CONTEXT_JSON;
+import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.*;
 
 /**
  * Pipeconf factory for all tor.p4 based pipeconf, both BMv2 and Tofino
@@ -76,7 +73,7 @@
                 // .addBehaviour(LinkDiscovery.class, NetcfgLinkDiscovery.class)
                 // FIXME: change underneath to proper TOR implementation
                 .addBehaviour(Pipeliner.class, DefaultSingleTablePipeline.class)
-                .addBehaviour(PortStatisticsDiscovery.class, DefaultP4PortStatisticsDiscovery.class);
+                .addBehaviour(PortStatisticsDiscovery.class, TorPortStatisticsDiscovery.class);
     }
 
     private static PiPipeconf buildTofinoPipeconf(String system) {
@@ -98,4 +95,11 @@
                 .addExtension(BMV2_JSON, BMV2_JSON_URL)
                 .build();
     }
+
+    public static class TorPortStatisticsDiscovery extends DefaultP4PortStatisticsDiscovery {
+        @Override
+        public String scope() {
+            return "packetio_egress";
+        }
+    }
 }
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
index fe2df80..19530f4 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
@@ -76,7 +76,8 @@
     /*
     If true, we avoid querying the device and return the content of the ENTRY_STORE.
      */
-    private boolean ignoreDeviceWhenGet = false;
+    // TODO: set to false as soon as the read-table tofino bug is fixed.
+    private boolean ignoreDeviceWhenGet = true;
 
     /*
     If true, we read all direct counters of a table with one request. Otherwise, send as many request as the number of