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