Get CPU port dynamically in fabric interpreter

Different HW platforms have different CPU ports, here we allow using
the same interpreter with multiple platform.

Change-Id: I0895d4d3e11560c75aca3fa908ca38759b597d67
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipeconf.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiPipeconf.java
index 9b353c8..5fedaf7 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipeconf.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiPipeconf.java
@@ -100,6 +100,14 @@
         /**
          * Barefoot's Tofino context JSON.
          */
-        TOFINO_CONTEXT_JSON
+        TOFINO_CONTEXT_JSON,
+
+        /**
+         * CPU port file in UTF 8 encoding.
+         */
+        // TODO: consider a better way to get the CPU port in the interpreter
+        // (see FabricInterpreter.java mapLogicalPortNumber). Perhaps using
+        // pipeconf annotations?
+        CPU_PORT_TXT
     }
 }
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java
index b3f996d..3115584 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/FabricInterpreter.java
@@ -36,12 +36,20 @@
 import org.onosproject.net.packet.InboundPacket;
 import org.onosproject.net.packet.OutboundPacket;
 import org.onosproject.net.pi.model.PiMatchFieldId;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconfId;
 import org.onosproject.net.pi.model.PiPipelineInterpreter;
 import org.onosproject.net.pi.model.PiTableId;
 import org.onosproject.net.pi.runtime.PiAction;
 import org.onosproject.net.pi.runtime.PiControlMetadata;
 import org.onosproject.net.pi.runtime.PiPacketOperation;
+import org.onosproject.net.pi.service.PiPipeconfService;
+import org.slf4j.Logger;
 
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.nio.ByteBuffer;
 import java.util.Collection;
 import java.util.List;
@@ -51,9 +59,12 @@
 import static java.lang.String.format;
 import static java.util.stream.Collectors.toList;
 import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onosproject.net.PortNumber.CONTROLLER;
 import static org.onosproject.net.PortNumber.FLOOD;
 import static org.onosproject.net.flow.instructions.Instruction.Type.OUTPUT;
 import static org.onosproject.net.pi.model.PiPacketOperationType.PACKET_OUT;
+import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.CPU_PORT_TXT;
+import static org.slf4j.LoggerFactory.getLogger;
 
 /**
  * Interpreter for fabric pipeline.
@@ -61,6 +72,8 @@
 public class FabricInterpreter extends AbstractHandlerBehaviour
         implements PiPipelineInterpreter {
 
+    private final Logger log = getLogger(getClass());
+
     public static final int PORT_BITWIDTH = 9;
 
     private static final ImmutableBiMap<Integer, PiTableId> TABLE_ID_MAP =
@@ -262,4 +275,55 @@
                     FabricConstants.INGRESS_PORT, deviceId, packetIn));
         }
     }
+
+    @Override
+    public Optional<Integer> mapLogicalPortNumber(PortNumber port) {
+        if (!port.equals(CONTROLLER)) {
+            return Optional.empty();
+        }
+        // This is probably brittle, but needed to dynamically get the CPU port
+        // for different platforms.
+        final DeviceId deviceId = data().deviceId();
+        final PiPipeconfService pipeconfService = handler().get(
+                PiPipeconfService.class);
+        final PiPipeconfId pipeconfId = pipeconfService
+                .ofDevice(deviceId).orElse(null);
+        if (pipeconfId == null ||
+                !pipeconfService.getPipeconf(pipeconfId).isPresent()) {
+            log.error("Unable to get pipeconf of {} - BUG?");
+            return Optional.empty();
+        }
+        final PiPipeconf pipeconf = pipeconfService.getPipeconf(pipeconfId).get();
+        if (!pipeconf.extension(CPU_PORT_TXT).isPresent()) {
+            log.error("Missing {} extension from pipeconf {}",
+                      CPU_PORT_TXT, pipeconfId);
+            return Optional.empty();
+        }
+        return Optional.ofNullable(
+                readCpuPort(pipeconf.extension(CPU_PORT_TXT).get(),
+                            pipeconfId));
+    }
+
+    private Integer readCpuPort(InputStream stream, PiPipeconfId pipeconfId) {
+        try {
+            final BufferedReader buff = new BufferedReader(
+                    new InputStreamReader(stream));
+            final String str = buff.readLine();
+            buff.close();
+            if (str == null) {
+                log.error("Empty CPU port file for {}", pipeconfId);
+                return null;
+            }
+            try {
+                return Integer.parseInt(str);
+            } catch (NumberFormatException e) {
+                log.error("Invalid CPU port for {}: {}", pipeconfId, str);
+                return null;
+            }
+        } catch (IOException e) {
+            log.error("Unable to read CPU port file of {}: {}",
+                      pipeconfId, e.getMessage());
+            return null;
+        }
+    }
 }
diff --git a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/PipeconfLoader.java b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/PipeconfLoader.java
index 3d04426..f808fc8 100644
--- a/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/PipeconfLoader.java
+++ b/pipelines/fabric/src/main/java/org/onosproject/pipelines/fabric/PipeconfLoader.java
@@ -23,7 +23,6 @@
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.onosproject.core.CoreService;
 import org.onosproject.inbandtelemetry.api.IntProgrammable;
-import org.onosproject.net.PortNumber;
 import org.onosproject.net.behaviour.Pipeliner;
 import org.onosproject.net.device.PortStatisticsDiscovery;
 import org.onosproject.net.pi.model.DefaultPiPipeconf;
@@ -44,7 +43,6 @@
 import java.net.URL;
 import java.util.Collection;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.stream.Collectors;
 
 import static java.lang.String.format;
@@ -79,13 +77,12 @@
     private static final String DEFAULT_PLATFORM = "default";
     private static final String BMV2_JSON = "bmv2.json";
     private static final String P4INFO_TXT = "p4info.txt";
+    private static final String CPU_PORT_TXT = "cpu_port.txt";
     private static final String TOFINO_BIN = "tofino.bin";
     private static final String TOFINO_CTX_JSON = "context.json";
     private static final String INT_PROFILE_SUFFIX = "-int";
     private static final String FULL_PROFILE_SUFFIX = "-full";
 
-    private static final int BMV2_CPU_PORT = 255;
-
     private static final Collection<PiPipeconf> PIPECONFS = buildAllPipeconf();
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -131,45 +128,56 @@
         String profile = pieces[1];
         String target = pieces[2];
         String platform = pieces[3];
+        final DefaultPiPipeconf.Builder pipeconfBuilder;
         try {
             switch (target) {
                 case BMV2:
-                    return buildBmv2Pipeconf(profile, platform);
+                    pipeconfBuilder = bmv2Pipeconf(profile, platform);
+                    break;
                 case TOFINO:
-                    return buildTofinoPipeconf(profile, platform);
+                    pipeconfBuilder = tofinoPipeconf(profile, platform);
+                    break;
                 default:
                     log.warn("Unknown target '{}', skipping pipeconf build...",
                              target);
                     return null;
             }
         } catch (FileNotFoundException e) {
-            log.warn("Unable to build pipeconf at {} because one or more p4c outputs are missing",
-                     path);
+            log.warn("Unable to build pipeconf at {} because file is missing: {}",
+                     path, e.getMessage());
             return null;
         }
+        // Add IntProgrammable behaviour for INT-enabled profiles.
+        if (profile.endsWith(INT_PROFILE_SUFFIX) || profile.endsWith(FULL_PROFILE_SUFFIX)) {
+            pipeconfBuilder.addBehaviour(IntProgrammable.class, IntProgrammableImpl.class);
+        }
+        return pipeconfBuilder.build();
     }
 
-    private static PiPipeconf buildBmv2Pipeconf(String profile, String platform)
+    private static DefaultPiPipeconf.Builder bmv2Pipeconf(
+            String profile, String platform)
             throws FileNotFoundException {
         final URL bmv2JsonUrl = PipeconfLoader.class.getResource(format(
                 P4C_RES_BASE_PATH + BMV2_JSON, profile, BMV2, platform));
         final URL p4InfoUrl = PipeconfLoader.class.getResource(format(
                 P4C_RES_BASE_PATH + P4INFO_TXT, profile, BMV2, platform));
-        if (bmv2JsonUrl == null || p4InfoUrl == null) {
-            throw new FileNotFoundException();
+        final URL cpuPortUrl = PipeconfLoader.class.getResource(format(
+                P4C_RES_BASE_PATH + CPU_PORT_TXT, profile, BMV2, platform));
+        if (bmv2JsonUrl == null) {
+            throw new FileNotFoundException(BMV2_JSON);
         }
-
-        DefaultPiPipeconf.Builder builder = basePipeconfBuilder(
-                profile, platform, p4InfoUrl, Bmv2FabricInterpreter.class)
+        if (p4InfoUrl == null) {
+            throw new FileNotFoundException(P4INFO_TXT);
+        }
+        if (cpuPortUrl == null) {
+            throw new FileNotFoundException(CPU_PORT_TXT);
+        }
+        return basePipeconfBuilder(profile, platform, p4InfoUrl, cpuPortUrl)
                 .addExtension(ExtensionType.BMV2_JSON, bmv2JsonUrl);
-        // Add IntProgrammable behaviour for INT-enabled profiles.
-        if (profile.endsWith(INT_PROFILE_SUFFIX) || profile.endsWith(FULL_PROFILE_SUFFIX)) {
-            builder.addBehaviour(IntProgrammable.class, IntProgrammableImpl.class);
-        }
-        return builder.build();
     }
 
-    private static PiPipeconf buildTofinoPipeconf(String profile, String platform)
+    private static DefaultPiPipeconf.Builder tofinoPipeconf(
+            String profile, String platform)
             throws FileNotFoundException {
         final URL tofinoBinUrl = PipeconfLoader.class.getResource(format(
                 P4C_RES_BASE_PATH + TOFINO_BIN, profile, TOFINO, platform));
@@ -177,19 +185,27 @@
                 P4C_RES_BASE_PATH + TOFINO_CTX_JSON, profile, TOFINO, platform));
         final URL p4InfoUrl = PipeconfLoader.class.getResource(format(
                 P4C_RES_BASE_PATH + P4INFO_TXT, profile, TOFINO, platform));
-        if (tofinoBinUrl == null || contextJsonUrl == null || p4InfoUrl == null) {
-            throw new FileNotFoundException();
+        final URL cpuPortUrl = PipeconfLoader.class.getResource(format(
+                P4C_RES_BASE_PATH + CPU_PORT_TXT, profile, TOFINO, platform));
+        if (tofinoBinUrl == null) {
+            throw new FileNotFoundException(TOFINO_BIN);
         }
-        return basePipeconfBuilder(
-                profile, platform, p4InfoUrl, FabricInterpreter.class)
+        if (contextJsonUrl == null) {
+            throw new FileNotFoundException(TOFINO_CTX_JSON);
+        }
+        if (p4InfoUrl == null) {
+            throw new FileNotFoundException(P4INFO_TXT);
+        }
+        if (cpuPortUrl == null) {
+            throw new FileNotFoundException(CPU_PORT_TXT);
+        }
+        return basePipeconfBuilder(profile, platform, p4InfoUrl, cpuPortUrl)
                 .addExtension(ExtensionType.TOFINO_BIN, tofinoBinUrl)
-                .addExtension(ExtensionType.TOFINO_CONTEXT_JSON, contextJsonUrl)
-                .build();
+                .addExtension(ExtensionType.TOFINO_CONTEXT_JSON, contextJsonUrl);
     }
 
     private static DefaultPiPipeconf.Builder basePipeconfBuilder(
-            String profile, String platform, URL p4InfoUrl,
-            Class<? extends FabricInterpreter> interpreterClass) {
+            String profile, String platform, URL p4InfoUrl, URL cpuPortUrl) {
         final String pipeconfId = platform.equals(DEFAULT_PLATFORM)
                 // Omit platform if default, e.g. with BMv2 pipeconf
                 ? format("%s.%s", BASE_PIPECONF_ID, profile)
@@ -199,12 +215,13 @@
                 .withId(new PiPipeconfId(pipeconfId))
                 .withPipelineModel(model)
                 .addBehaviour(PiPipelineInterpreter.class,
-                              interpreterClass)
+                              FabricInterpreter.class)
                 .addBehaviour(Pipeliner.class,
                               FabricPipeliner.class)
                 .addBehaviour(PortStatisticsDiscovery.class,
                               FabricPortStatisticsDiscovery.class)
-                .addExtension(ExtensionType.P4_INFO_TEXT, p4InfoUrl);
+                .addExtension(ExtensionType.P4_INFO_TEXT, p4InfoUrl)
+                .addExtension(ExtensionType.CPU_PORT_TXT, cpuPortUrl);
     }
 
     private static PiPipelineModel parseP4Info(URL p4InfoUrl) {
@@ -214,16 +231,4 @@
             throw new IllegalStateException(e);
         }
     }
-
-    // TODO: define interpreters with logical port mapping for Tofino platforms.
-    public static class Bmv2FabricInterpreter extends FabricInterpreter {
-        @Override
-        public Optional<Integer> mapLogicalPortNumber(PortNumber port) {
-            if (port.equals(PortNumber.CONTROLLER)) {
-                return Optional.of(BMV2_CPU_PORT);
-            } else {
-                return Optional.empty();
-            }
-        }
-    }
 }
diff --git a/pipelines/fabric/src/main/resources/bmv2-compile.sh b/pipelines/fabric/src/main/resources/bmv2-compile.sh
index d92a5c4..b0ac43e 100755
--- a/pipelines/fabric/src/main/resources/bmv2-compile.sh
+++ b/pipelines/fabric/src/main/resources/bmv2-compile.sh
@@ -18,3 +18,5 @@
         --p4runtime-file ${OUT_DIR}/p4info.txt \
         --p4runtime-format text \
         fabric.p4
+
+echo ${BMV2_CPU_PORT} > ${OUT_DIR}/cpu_port.txt
diff --git a/pipelines/fabric/src/main/resources/p4c-out/fabric-full/bmv2/default/cpu_port.txt b/pipelines/fabric/src/main/resources/p4c-out/fabric-full/bmv2/default/cpu_port.txt
new file mode 100644
index 0000000..ace9d03
--- /dev/null
+++ b/pipelines/fabric/src/main/resources/p4c-out/fabric-full/bmv2/default/cpu_port.txt
@@ -0,0 +1 @@
+255
diff --git a/pipelines/fabric/src/main/resources/p4c-out/fabric-int/bmv2/default/cpu_port.txt b/pipelines/fabric/src/main/resources/p4c-out/fabric-int/bmv2/default/cpu_port.txt
new file mode 100644
index 0000000..ace9d03
--- /dev/null
+++ b/pipelines/fabric/src/main/resources/p4c-out/fabric-int/bmv2/default/cpu_port.txt
@@ -0,0 +1 @@
+255
diff --git a/pipelines/fabric/src/main/resources/p4c-out/fabric-spgw/bmv2/default/cpu_port.txt b/pipelines/fabric/src/main/resources/p4c-out/fabric-spgw/bmv2/default/cpu_port.txt
new file mode 100644
index 0000000..ace9d03
--- /dev/null
+++ b/pipelines/fabric/src/main/resources/p4c-out/fabric-spgw/bmv2/default/cpu_port.txt
@@ -0,0 +1 @@
+255
diff --git a/pipelines/fabric/src/main/resources/p4c-out/fabric/bmv2/default/cpu_port.txt b/pipelines/fabric/src/main/resources/p4c-out/fabric/bmv2/default/cpu_port.txt
new file mode 100644
index 0000000..ace9d03
--- /dev/null
+++ b/pipelines/fabric/src/main/resources/p4c-out/fabric/bmv2/default/cpu_port.txt
@@ -0,0 +1 @@
+255