INT demo app for ONS 2018

Change-Id: I6a061fb6f22a82fe9ed1b63782eb5a2bd7f82764
diff --git a/apps/int-demo/BUCK b/apps/int-demo/BUCK
index 3b2259a..d7df7f9 100644
--- a/apps/int-demo/BUCK
+++ b/apps/int-demo/BUCK
@@ -1,6 +1,5 @@
 COMPILE_DEPS = [
     '//lib:CORE_DEPS',
-
 ]
 
 osgi_jar (
diff --git a/apps/int-demo/src/main/java/org/onosproject/intdemo/IntDemoApp.java b/apps/int-demo/src/main/java/org/onosproject/intdemo/IntDemoApp.java
new file mode 100644
index 0000000..e883fdc
--- /dev/null
+++ b/apps/int-demo/src/main/java/org/onosproject/intdemo/IntDemoApp.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.intdemo;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.felix.scr.annotations.Activate;
+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.util.ImmutableByteSequence;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.criteria.PiCriterion;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionParamId;
+import org.onosproject.net.pi.model.PiMatchFieldId;
+import org.onosproject.net.pi.model.PiPipeconfId;
+import org.onosproject.net.pi.model.PiTableId;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.service.PiPipeconfService;
+import org.slf4j.Logger;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Optional;
+
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of an upgradable fabric app for the Basic pipeconf (basic.p4)
+ * with ECMP support.
+ */
+@Component(immediate = true)
+public class IntDemoApp {
+
+    private static final int DEFAULT_TEID = 0;
+    protected final Logger log = getLogger(getClass());
+
+    private static final String SPGW_INT_PIPECONF_KEYWORD = "spgw-int";
+    private static final String SPGW_DEVICE_KEYWORD = "leaf2";
+    private static final Ip4Address S1U_SGW_ADDR = Ip4Address.valueOf("140.0.0.2");
+
+    private static final Collection<String> UE_ADDRS = ImmutableList.of(
+            "160.0.2.1", "160.0.2.2", "160.0.2.3", "160.0.2.4", "160.0.2.5");
+
+    private static final Map<String, Integer> SWITCH_ID_MAP = new ImmutableMap.Builder<String, Integer>()
+            .put("leaf1", 0)
+            .put("leaf2", 1)
+            .build();
+
+    private static final PiTableId TBL_ID_INT_INST = PiTableId
+            .of("int_egress.int_metadata_insert.int_inst_0003");
+    private static final PiTableId TBL_ID_INT_PREP = PiTableId
+            .of("int_egress.int_prep");
+
+    private static final PiMatchFieldId FM_ID_INST_MASK = PiMatchFieldId
+            .of("hdr.int_header.instruction_mask_0003");
+
+    private static final PiActionId ACT_ID_INST_ALL = PiActionId
+            .of("int_egress.int_metadata_insert.int_set_header_0003_i15");
+    private static final PiActionId ACT_ID_INT_TRANSIT = PiActionId
+            .of("int_egress.int_transit");
+
+    private static final PiActionParamId ACTP_ID_SWITCH_ID = PiActionParamId
+            .of("switch_id");
+
+    private static final PiTableId TBL_ID_UE_FILTER = PiTableId
+            .of("spgw_ingress.ue_filter_table");
+    private static final PiTableId TBL_ID_DL_SESS_LOOKUP = PiTableId
+            .of("spgw_ingress.dl_sess_lookup");
+    private static final PiMatchFieldId MF_ID_IPV4_DST = PiMatchFieldId
+            .of("ipv4.dst_addr");
+    private static final PiActionId ACT_ID_SET_DL_SESS_INFO = PiActionId
+            .of("spgw_ingress.set_dl_sess_info");
+    private static final PiActionParamId PARAM_S1U_ENB_ADDR = PiActionParamId
+            .of("s1u_enb_addr");
+    private static final PiActionParamId PARAM_ID_S1U_SGW_ADDR = PiActionParamId
+            .of("s1u_sgw_addr");
+    private static final PiAction NO_ACTION = PiAction.builder()
+            .withId(PiActionId.of("NoAction"))
+            .build();
+    private static final PiActionParamId ACTP_TEID = PiActionParamId
+            .of("teid");
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private FlowRuleService flowRuleService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private PiPipeconfService piPipeconfService;
+
+    private final DeviceListener deviceListener = new InternalDeviceListener();
+
+    private ApplicationId appId;
+
+    @Activate
+    public void activate() {
+        log.info("Starting...");
+        appId = coreService.registerApplication("org.onosproject.int-demo");
+        deviceService.getDevices().forEach(d -> checkDevice(d.id()));
+        deviceService.addListener(deviceListener);
+        log.info("STARTED", appId.id());
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("Stopping...");
+        deviceService.removeListener(deviceListener);
+        flowRuleService.removeFlowRulesById(appId);
+        log.info("STOPPED");
+    }
+
+    private void confIntSwitch(DeviceId deviceId) {
+        Optional<Integer> switchId = SWITCH_ID_MAP.keySet().stream()
+                .filter(k -> deviceId.toString().contains(k))
+                .findFirst()
+                .map(SWITCH_ID_MAP::get);
+
+        if (!switchId.isPresent()) {
+            log.error("Unknown INT switch ID for device {}", deviceId);
+            return;
+        }
+
+        log.info("Configuring device {} for INT transit capability: switchId={}...", deviceId, switchId.get());
+
+        FlowRule intPrepRule = flowRuleBuilder(deviceId, TBL_ID_INT_PREP)
+                .withSelector(DefaultTrafficSelector.emptySelector())
+                .withTreatment(
+                        DefaultTrafficTreatment.builder()
+                                .piTableAction(
+                                        PiAction.builder()
+                                                .withId(ACT_ID_INT_TRANSIT)
+                                                .withParameter(new PiActionParam(
+                                                        ACTP_ID_SWITCH_ID, copyFrom(0)))
+                                                .build())
+                                .build())
+                .build();
+
+        FlowRule intIntrRule = flowRuleBuilder(deviceId, TBL_ID_INT_INST)
+                .withSelector(
+                        DefaultTrafficSelector.builder()
+                                .matchPi(
+                                        PiCriterion.builder()
+                                                .matchExact(FM_ID_INST_MASK, (byte) 0x0F)
+                                                .build())
+                                .build())
+                .withTreatment(
+                        DefaultTrafficTreatment.builder()
+                                .piTableAction(PiAction.builder()
+                                                       .withId(ACT_ID_INST_ALL)
+                                                       .build())
+                                .build())
+                .build();
+
+        flowRuleService.applyFlowRules(intPrepRule, intIntrRule);
+    }
+
+    private void confSpgw(DeviceId deviceId) {
+        log.info("Configuring device {} for SPGW downlink processing...", deviceId);
+        UE_ADDRS.stream()
+                .map(Ip4Address::valueOf)
+                .forEach(ueAddr -> {
+                    flowRuleService.applyFlowRules(ueFilterRule(deviceId, ueAddr));
+                    flowRuleService.applyFlowRules(
+                            dlSessLookupRule(deviceId, ueAddr, DEFAULT_TEID, ueAddr, S1U_SGW_ADDR)
+                    );
+                });
+    }
+
+    private FlowRule ueFilterRule(DeviceId deviceId, Ip4Address ueAddr) {
+        final PiCriterion piCriterion = PiCriterion.builder()
+                .matchLpm(MF_ID_IPV4_DST, ueAddr.toOctets(), 32)
+                .build();
+
+        return flowRuleBuilder(deviceId, TBL_ID_UE_FILTER)
+                .withSelector(DefaultTrafficSelector.builder()
+                                      .matchPi(piCriterion)
+                                      .build())
+                .withTreatment(DefaultTrafficTreatment.builder()
+                                       .piTableAction(NO_ACTION)
+                                       .build())
+                .build();
+    }
+
+    private FlowRule dlSessLookupRule(DeviceId deviceId, Ip4Address ueAddr, long teid,
+                                      Ip4Address enbAddr, Ip4Address s1uAddr) {
+
+        // FIXME: the long teid is obtained from the wrong byte order
+        ImmutableByteSequence teidBs = copyFrom(teid);
+        byte[] byteArray = teidBs.asArray();
+        ArrayUtils.reverse(byteArray);
+        byte[] trimmedArray = Arrays.copyOfRange(byteArray, 0, 4);
+        ImmutableByteSequence revTeidBs = copyFrom(trimmedArray);
+
+        // Add rule to
+        final PiAction action = PiAction.builder()
+                .withId(ACT_ID_SET_DL_SESS_INFO)
+                .withParameter(new PiActionParam(ACTP_TEID, revTeidBs))
+                .withParameter(new PiActionParam(PARAM_S1U_ENB_ADDR,
+                                                 copyFrom(enbAddr.toOctets())))
+                .withParameter(new PiActionParam(PARAM_ID_S1U_SGW_ADDR,
+                                                 copyFrom(s1uAddr.toOctets())))
+                .build();
+
+        final PiCriterion piCriterion = PiCriterion.builder()
+                .matchExact(MF_ID_IPV4_DST, ueAddr.toOctets())
+                .build();
+
+        return flowRuleBuilder(deviceId, TBL_ID_DL_SESS_LOOKUP)
+                .withSelector(DefaultTrafficSelector.builder()
+                                      .matchPi(piCriterion)
+                                      .build())
+                .withTreatment(DefaultTrafficTreatment.builder()
+                                       .piTableAction(action)
+                                       .build())
+                .build();
+    }
+
+    /**
+     * Returns a new, pre-configured flow rule builder.
+     *
+     * @param did     a device id
+     * @param tableId a table id
+     * @return a new flow rule builder
+     */
+    private FlowRule.Builder flowRuleBuilder(DeviceId did, PiTableId tableId) {
+        return DefaultFlowRule.builder()
+                .forDevice(did)
+                .forTable(tableId)
+                .fromApp(appId)
+                .withPriority(0)
+                .makePermanent();
+    }
+
+    private void checkDevice(DeviceId deviceId) {
+        // If device has pipeconf spgw-int
+        Optional<PiPipeconfId> pipeconf = piPipeconfService.ofDevice(deviceId);
+
+        if (!pipeconf.isPresent() || !pipeconf.get().id().contains(SPGW_INT_PIPECONF_KEYWORD)) {
+            log.info("Device {} has no pipeconf associated or pipeconf is not SPGW/INT-capable, ignoring", deviceId);
+            return;
+        }
+
+        confIntSwitch(deviceId);
+
+        if (deviceId.toString().contains(SPGW_DEVICE_KEYWORD)) {
+            confSpgw(deviceId);
+        }
+    }
+
+    private class InternalDeviceListener implements DeviceListener {
+
+        @Override
+        public void event(DeviceEvent event) {
+            if (event.type() != DeviceEvent.Type.DEVICE_ADDED) {
+                return;
+            }
+            DeviceId deviceId = event.subject().id();
+            checkDevice(deviceId);
+
+        }
+    }
+}
diff --git a/mwc.defs b/mwc.defs
index 94d2b407..7c62006 100644
--- a/mwc.defs
+++ b/mwc.defs
@@ -7,7 +7,7 @@
     '//drivers/mellanox-pro:onos-drivers-mellanox-pro-oar',
     '//fpcagent/apps/fpcagent:onos-fpcagent-apps-fpcagent-oar',
     '//fpcagent/models/fpcagent:onos-fpcagent-models-fpcagent-oar',
-    '//apps/int-demo:onos-apps-int-demo',
+    '//apps/int-demo:onos-apps-int-demo-oar',
 ]
 
 APPS = APPS + MWC_APPS
diff --git a/tools/package/bin/onos-service b/tools/package/bin/onos-service
index 9ed86bd..89a6793 100755
--- a/tools/package/bin/onos-service
+++ b/tools/package/bin/onos-service
@@ -22,7 +22,7 @@
 ONOS_APPS=${ONOS_APPS:-}  # Empty means don't activate any new apps
 
 # MWC-2018
-ONOS_APPS="drivers.barefoot-pro,drivers.cavium-pro,drivers.mellanox-pro,pipelines.fabric-pro,netcfghostprovider,hostprovider,lldpprovider,proxyarp,route-service,segmentrouting,org.onosproject.fpcagent"
+ONOS_APPS="drivers.barefoot-pro,drivers.cavium-pro,drivers.mellanox-pro,pipelines.fabric-pro,netcfghostprovider,hostprovider,lldpprovider,proxyarp,route-service,segmentrouting,org.onosproject.fpcagent,int-demo"
 
 cd $ONOS_HOME