Initial commit of SimpleFabric application
Change-Id: Ia491aa6ad1b15576a54803decfee54aa8c1d1d6a
diff --git a/apps/simplefabric/BUCK b/apps/simplefabric/BUCK
new file mode 100644
index 0000000..8125580
--- /dev/null
+++ b/apps/simplefabric/BUCK
@@ -0,0 +1,27 @@
+COMPILE_DEPS = [
+ '//lib:CORE_DEPS',
+ '//lib:JACKSON',
+ '//lib:concurrent-trees',
+]
+
+BUNDLES = [
+ '//apps/simplefabric:onos-apps-simplefabric',
+]
+
+TEST_DEPS = [
+ '//lib:TEST_ADAPTERS',
+]
+
+osgi_jar_with_tests (
+ deps = COMPILE_DEPS,
+ test_deps = TEST_DEPS,
+)
+
+onos_app (
+ title = 'SimpleFabric',
+ category = 'Traffic Steering',
+ url = 'http://onosproject.org',
+ included_bundles = BUNDLES,
+ description = 'Simple Fabric application',
+ required_apps = [ 'org.onosproject.openflow-base', 'org.onosproject.lldpprovider', 'org.onosproject.hostprovider' ],
+)
diff --git a/apps/simplefabric/app.xml b/apps/simplefabric/app.xml
new file mode 100644
index 0000000..df5cc60
--- /dev/null
+++ b/apps/simplefabric/app.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2017-present Open Networking Laboratory
+ ~
+ ~ 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.
+ -->
+<app name="org.onosproject.simplefabric" origin="ON.Lab" version="${project.version}"
+ category="Traffic Steering" url="http://onosproject.org" title="Simple Leaf-Spine Network App"
+ featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features"
+ features="${project.artifactId}" apps="org.onosproject.openflow-base,org.onosproject.lldpprovider,org.onosproject.hostprovider">
+ <description>${project.description}</description>
+ <artifact>mvn:${project.groupId}/${project.artifactId}/${project.version}</artifact>
+</app>
diff --git a/apps/simplefabric/features.xml b/apps/simplefabric/features.xml
new file mode 100644
index 0000000..9ab23de
--- /dev/null
+++ b/apps/simplefabric/features.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+ ~ Copyright 2017-present Open Networking Laboratory
+ ~
+ ~ 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.
+ -->
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}">
+ <feature name="onos-app-simplefabric" version="${project.version}"
+ description="${project.description}">
+ <feature>onos-api</feature>
+ <bundle>mvn:${project.groupId}/onos-app-simplefabric/${project.version}</bundle>
+ </feature>
+</features>
diff --git a/apps/simplefabric/mininet-simplefabric.py b/apps/simplefabric/mininet-simplefabric.py
new file mode 100755
index 0000000..dafbbc6
--- /dev/null
+++ b/apps/simplefabric/mininet-simplefabric.py
@@ -0,0 +1,94 @@
+#!/usr/bin/python
+# Mininet model for Simple Leaf-Spine Network
+
+# 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.
+
+from mininet.topo import Topo
+from mininet.net import Mininet
+from mininet.node import Host, OVSSwitch, RemoteController
+from mininet.log import setLogLevel
+from mininet.cli import CLI
+
+
+"Create custom topo."
+
+net = Mininet()
+
+# Add leaf switch and hosts in rack 1
+# subnet: 10.0.1.0/24
+s10 = net.addSwitch('s10', dpid='0000000000000011')
+h11 = net.addHost('h11', mac='00:00:10:00:01:11', ip='10.0.1.11/24', defaultRoute='via 10.0.1.1')
+h12 = net.addHost('h12', mac='00:00:10:00:01:12', ip='10.0.1.12/24', defaultRoute='via 10.0.1.1')
+h13 = net.addHost('h13', mac='00:00:10:00:01:13', ip='10.0.1.13/24', defaultRoute='via 10.0.1.1')
+h14 = net.addHost('h14', mac='00:00:10:00:01:14', ip='10.0.1.14/24', defaultRoute='via 10.0.1.1')
+d11 = net.addHost('d11', mac='00:00:10:00:01:a1', ip='10.0.1.111/24', defaultRoute='via 10.0.1.1')
+d12 = net.addHost('d12', mac='00:00:10:00:01:a2', ip='10.0.1.112/24', defaultRoute='via 10.0.1.1')
+net.addLink(s10, h11)
+net.addLink(s10, h12)
+net.addLink(s10, h13)
+net.addLink(s10, h14)
+net.addLink(s10, d11)
+net.addLink(s10, d12)
+
+# Add leaf switch and hosts in rack 2
+# subnet: 10.0.2.0/24
+s20 = net.addSwitch('s20', dpid='0000000000000012')
+h21 = net.addHost('h21', mac='00:00:10:00:02:21', ip='10.0.2.21/24', defaultRoute='via 10.0.2.1')
+h22 = net.addHost('h22', mac='00:00:10:00:02:22', ip='10.0.2.22/24', defaultRoute='via 10.0.2.1')
+h23 = net.addHost('h23', mac='00:00:10:00:02:23', ip='10.0.2.23/24', defaultRoute='via 10.0.2.1')
+h24 = net.addHost('h24', mac='00:00:10:00:02:24', ip='10.0.2.24/24', defaultRoute='via 10.0.2.1')
+d21 = net.addHost('d21', mac='00:00:10:00:02:b1', ip='10.0.2.221/24', defaultRoute='via 10.0.2.1')
+d22 = net.addHost('d22', mac='00:00:10:00:02:b2', ip='10.0.2.222/24', defaultRoute='via 10.0.2.1')
+net.addLink(s20, h21)
+net.addLink(s20, h22)
+net.addLink(s20, h23)
+net.addLink(s20, h24)
+net.addLink(s20, d21)
+net.addLink(s20, d22)
+
+# Add spine switches and nat
+# subnet: 10.0.0.0/16
+ss1 = net.addSwitch('ss1', dpid='0000000000000021')
+ss2 = net.addSwitch('ss2', dpid='0000000000000022')
+net.addLink(ss1, s10)
+net.addLink(ss1, s20)
+net.addLink(ss2, s10)
+net.addLink(ss2, s20)
+
+# Add External Router
+#h31 = net.addHost('h31', mac='00:00:10:00:00:31', ip='10.0.0.31/24', defaultRoute='via 10.0.0.1')
+#h32 = net.addHost('h32', mac='00:00:10:00:00:32', ip='10.0.0.32/24', defaultRoute='via 10.0.0.1')
+#net.addLink(ss1, h31);
+#net.addLink(ss2, h32);
+
+# Add ONOS/RemoteController; set your ONOS node ip address
+net.addController(RemoteController('c1', ip='10.10.108.140'))
+
+# Main
+setLogLevel('info')
+net.start()
+
+# reveal hosts to switches
+#for h in [h11, h12, h13, h14, d11, d12] :
+# net.ping(hosts=[h, h31], timeout='1')
+# net.ping(hosts=[h, h31], timeout='1')
+#for h in [h21, h22, h23, h24, d21, d22] :
+# net.ping(hosts=[h, h32], timeout='1')
+# net.ping(hosts=[h, h32], timeout='1')
+
+# do interactive shell
+CLI(net)
+net.stop()
+
diff --git a/apps/simplefabric/network-cfg.json b/apps/simplefabric/network-cfg.json
new file mode 100644
index 0000000..f988497
--- /dev/null
+++ b/apps/simplefabric/network-cfg.json
@@ -0,0 +1,56 @@
+{
+
+ "devices":{
+ "of:0000000000000011":{ "basic":{ "name":"LS1", "latitude":35, "longitude":-100 } },
+ "of:0000000000000012":{ "basic":{ "name":"LS2", "latitude":35, "longitude":-90 } },
+ "of:0000000000000021":{ "basic":{ "name":"SS1", "latitude":40, "longitude":-100 } },
+ "of:0000000000000022":{ "basic":{ "name":"SS2", "latitude":40, "longitude":-90 } }
+ },
+
+ "ports" : {
+ "of:0000000000000011/1" : { "interfaces" : [ { "name" : "h11" } ] },
+ "of:0000000000000011/2" : { "interfaces" : [ { "name" : "h12" } ] },
+ "of:0000000000000011/3" : { "interfaces" : [ { "name" : "h13" } ] },
+ "of:0000000000000011/4" : { "interfaces" : [ { "name" : "h14" } ] },
+ "of:0000000000000011/5" : { "interfaces" : [ { "name" : "d11" } ] },
+ "of:0000000000000011/6" : { "interfaces" : [ { "name" : "d12" } ] },
+ "of:0000000000000011/7" : { "interfaces" : [ { "name" : "LS1_SS1" } ] },
+ "of:0000000000000011/8" : { "interfaces" : [ { "name" : "LS1_SS2" } ] },
+
+ "of:0000000000000012/1" : { "interfaces" : [ { "name" : "h21" } ] } ,
+ "of:0000000000000012/2" : { "interfaces" : [ { "name" : "h22" } ] },
+ "of:0000000000000012/3" : { "interfaces" : [ { "name" : "h23" } ] },
+ "of:0000000000000012/4" : { "interfaces" : [ { "name" : "h24" } ] },
+ "of:0000000000000012/5" : { "interfaces" : [ { "name" : "d21" } ] },
+ "of:0000000000000012/6" : { "interfaces" : [ { "name" : "d22" } ] },
+ "of:0000000000000012/7" : { "interfaces" : [ { "name" : "LS2_SS1" } ] },
+ "of:0000000000000012/8" : { "interfaces" : [ { "name" : "LS2_SS2" } ] },
+
+ "of:0000000000000021/1" : { "interfaces" : [ { "name" : "SS1_LS1" } ] },
+ "of:0000000000000021/2" : { "interfaces" : [ { "name" : "SS1_LS2" } ] },
+
+ "of:0000000000000022/1" : { "interfaces" : [ { "name" : "SS2_LS1" } ] },
+ "of:0000000000000022/2" : { "interfaces" : [ { "name" : "SS2_LS2" } ] }
+
+ },
+
+ "apps" : {
+ "org.onosproject.simplefabric" : {
+ "simpleFabric" : {
+ "l2Networks" : [
+ { "name" : "LEAF1", "interfaces" : ["h11", "h12", "h13", "h14", "d11", "d12" ], "l2Forward" : true },
+ { "name" : "LEAF2", "interfaces" : ["h21", "h22", "h23", "h24", "d21", "d22" ], "l2Forward" : true }
+ ],
+ "ipSubnets" : [
+ { "ipPrefix" : "10.0.1.0/24", "gatewayIp" : "10.0.1.1", "gatewayMac" : "00:00:10:00:01:01", "l2NetworkName" : "LEAF1" },
+ { "ipPrefix" : "10.0.2.0/24", "gatewayIp" : "10.0.2.1", "gatewayMac" : "00:00:10:00:02:01", "l2NetworkName" : "LEAF2" }
+ ],
+ "borderRoutes" : [
+ { "ipPrefix" : "0.0.0.0/0", "nextHop" : "10.0.1.2" }
+ ]
+ }
+ }
+ }
+
+}
+
diff --git a/apps/simplefabric/pom.xml b/apps/simplefabric/pom.xml
new file mode 100644
index 0000000..ef36955
--- /dev/null
+++ b/apps/simplefabric/pom.xml
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2017-present Open Networking Laboratory
+ ~
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-apps</artifactId>
+ <version>1.12.0-SNAPSHOT</version>
+ <relativePath>../../onos</relativePath>
+ </parent>
+
+ <artifactId>onos-app-simplefabric</artifactId>
+ <packaging>bundle</packaging>
+
+ <description>Simple Fabric Application for Leaf-Spine L2/L3 Network</description>
+
+ <properties>
+ <onos.app.name>org.onosproject.simplefabric</onos.app.name>
+ <onos.app.category>Traffic Steering</onos.app.category>
+ <onos.app.title>Simple Fabric App</onos.app.title>
+ <onos.app.url>http://onosproject.org</onos.app.url>
+
+ <web.context>/onos/v1/simplefabric</web.context>
+ <api.version>1.0.0</api.version>
+ <api.title>Simple Fabric Application REST API</api.title>
+ <api.description>APIs for interacting with the Simple Fabric application.</api.description>
+ <api.package>org.onosproject.simplefabric</api.package>
+ </properties>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-misc</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-cli</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core-serializers</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core-net</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-rest</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.scr.annotations</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.karaf.shell</groupId>
+ <artifactId>org.apache.karaf.shell.console</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <_wab>src/main/webapp/</_wab>
+ <Include-Resource>
+ WEB-INF/classes/apidoc/swagger.json=target/swagger.json,
+ {maven-resources}
+ </Include-Resource>
+ <Bundle-SymbolicName>
+ ${project.groupId}.${project.artifactId}
+ </Bundle-SymbolicName>
+ <Import-Package>
+ *,org.glassfish.jersey.servlet
+ </Import-Package>
+ <Web-ContextPath>${web.context}</Web-ContextPath>
+ </instructions>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-scr-plugin</artifactId>
+ </plugin>
+
+ <plugin>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-maven-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/IpSubnet.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/IpSubnet.java
new file mode 100644
index 0000000..596c659
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/IpSubnet.java
@@ -0,0 +1,148 @@
+/*
+ * 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.
+ */
+package org.onosproject.simplefabric;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.EncapsulationType;
+
+import java.util.Objects;
+
+/**
+ * Configuration details for an ip subnet entry.
+ */
+public class IpSubnet {
+ private final IpPrefix ipPrefix;
+ private final IpAddress gatewayIp;
+ private final MacAddress gatewayMac;
+ private EncapsulationType encapsulation;
+ private final String l2NetworkName;
+
+ /**
+ * Creates a new ip subnet entry.
+ *
+ * @param ipPrefix an ip subnet
+ * @param gatewayIp IP of the virtual gateway
+ * @param gatewayMac MacAddress of the virtual gateway
+ * @param encapsulation EnacaptulatioType for routes related to this subnet
+ * @param l2NetworkName Name of L2 Network this subnet is bound
+ */
+ public IpSubnet(IpPrefix ipPrefix, IpAddress gatewayIp, MacAddress gatewayMac,
+ EncapsulationType encapsulation, String l2NetworkName) {
+ this.ipPrefix = ipPrefix;
+ this.gatewayIp = gatewayIp;
+ this.gatewayMac = gatewayMac;
+ this.encapsulation = EncapsulationType.NONE;
+ this.l2NetworkName = l2NetworkName;
+ }
+
+ /**
+ * Gets the ip subnet of the ip subnet entry.
+ *
+ * @return the ip subnet
+ */
+ public IpPrefix ipPrefix() {
+ return ipPrefix;
+ }
+
+ /**
+ * Gets the virtual gateway IP address of the ip subnet entry.
+ *
+ * @return the virtual gateway IP address
+ */
+ public IpAddress gatewayIp() {
+ return gatewayIp;
+ }
+
+ /**
+ * Gets the virtual gateway Mac address of the ip subnet entry.
+ *
+ * @return the virtuai gateway Mac address
+ */
+ public MacAddress gatewayMac() {
+ return gatewayMac;
+ }
+
+ /**
+ * Gets the encapsulation type of ip subnet entry.
+ *
+ * @return the encapsulation type
+ */
+ public EncapsulationType encapsulation() {
+ return encapsulation;
+ }
+
+ /**
+ * Gets the name of L2 Network this subnet is bound.
+ *
+ * @return the l2Network name this subnet is allocated
+ */
+ public String l2NetworkName() {
+ return l2NetworkName;
+ }
+
+ /**
+ * Tests whether the IP version of this entry is IPv4.
+ *
+ * @return true if the IP version of this entry is IPv4, otherwise false.
+ */
+ public boolean isIp4() {
+ return ipPrefix.isIp4();
+ }
+
+ /**
+ * Tests whether the IP version of this entry is IPv6.
+ *
+ * @return true if the IP version of this entry is IPv6, otherwise false.
+ */
+ public boolean isIp6() {
+ return ipPrefix.isIp6();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ipPrefix, gatewayIp, gatewayMac, encapsulation, l2NetworkName);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof IpSubnet)) {
+ return false;
+ }
+ IpSubnet that = (IpSubnet) obj;
+ return Objects.equals(this.ipPrefix, that.ipPrefix)
+ && Objects.equals(this.gatewayIp, that.gatewayIp)
+ && Objects.equals(this.gatewayMac, that.gatewayMac)
+ && Objects.equals(this.encapsulation, that.encapsulation)
+ && Objects.equals(this.l2NetworkName, that.l2NetworkName);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("ipPrefix", ipPrefix)
+ .add("gatewayIp", gatewayIp)
+ .add("gatewayMac", gatewayMac)
+ .add("encapsulation", encapsulation)
+ .add("l2NetworkName", l2NetworkName)
+ .toString();
+ }
+}
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/L2Network.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/L2Network.java
new file mode 100644
index 0000000..31e8cc0
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/L2Network.java
@@ -0,0 +1,287 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.simplefabric;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.EncapsulationType;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Set;
+
+
+/**
+ * Class stores a L2Network information.
+ */
+public final class L2Network {
+
+ private String name; // also for network configuration
+ private Set<String> interfaceNames; // also for network configuration
+ private EncapsulationType encapsulation; // also for network configuration
+ private boolean l2Forward; // do l2Forward (default:true) or not
+
+ /* status variables */
+ private Set<Interface> interfaces; // available interfaces from interfaceNames
+ private Set<HostId> hostIds; // available hosts from interfaces
+ private boolean dirty;
+
+ /**
+ * Constructs a L2Network data for Config value.
+ *
+ * @param name the given name
+ * @param ifaceNames the interface names
+ * @param encapsulation the encapsulation type
+ * @param l2Forward flag for l2Forward intents to be installed or not
+ */
+ L2Network(String name, Collection<String> ifaceNames, EncapsulationType encapsulation, boolean l2Forward) {
+ this.name = name;
+ this.interfaceNames = Sets.newHashSet();
+ this.interfaceNames.addAll(ifaceNames);
+ this.encapsulation = encapsulation;
+ this.l2Forward = (SimpleFabricService.ALLOW_ETH_ADDRESS_SELECTOR) ? l2Forward : false;
+ this.interfaces = Sets.newHashSet();
+ this.hostIds = Sets.newHashSet();
+ this.dirty = false;
+ }
+
+ /**
+ * Constructs a L2Network data by given name and encapsulation type.
+ *
+ * @param name the given name
+ * @param encapsulation the encapsulation type
+ */
+ private L2Network(String name, EncapsulationType encapsulation) {
+ this.name = name;
+ this.interfaceNames = Sets.newHashSet();
+ this.encapsulation = encapsulation;
+ this.l2Forward = (SimpleFabricService.ALLOW_ETH_ADDRESS_SELECTOR) ? true : false;
+ this.interfaces = Sets.newHashSet();
+ this.hostIds = Sets.newHashSet();
+ this.dirty = false;
+ }
+
+ /**
+ * Creates a L2Network data by given name.
+ * The encapsulation type of the L2Network will be NONE.
+ *
+ * @param name the given name
+ * @return the L2Network data
+ */
+ public static L2Network of(String name) {
+ Objects.requireNonNull(name);
+ return new L2Network(name, EncapsulationType.NONE);
+ }
+
+ /**
+ * Creates a copy of L2Network data.
+ *
+ * @param l2Network the L2Network data
+ * @return the copy of the L2Network data
+ */
+ public static L2Network of(L2Network l2Network) {
+ Objects.requireNonNull(l2Network);
+ L2Network l2NetworkCopy = new L2Network(l2Network.name(), l2Network.encapsulation());
+ l2NetworkCopy.interfaceNames.addAll(l2Network.interfaceNames());
+ l2NetworkCopy.l2Forward = (SimpleFabricService.ALLOW_ETH_ADDRESS_SELECTOR) ? l2Network.l2Forward() : false;
+ l2NetworkCopy.interfaces.addAll(l2Network.interfaces());
+ l2NetworkCopy.hostIds.addAll(l2Network.hostIds());
+ l2NetworkCopy.setDirty(l2Network.dirty());
+ return l2NetworkCopy;
+ }
+
+ // field queries
+
+ /**
+ * Gets L2Network name.
+ *
+ * @return the name of L2Network
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Gets L2Network interfaceNames.
+ *
+ * @return the interfaceNames of L2Network
+ */
+ public Set<String> interfaceNames() {
+ return ImmutableSet.copyOf(interfaceNames);
+ }
+
+ /**
+ * Gets L2Network encapsulation type.
+ *
+ * @return the encapsulation type of L2Network
+ */
+ public EncapsulationType encapsulation() {
+ return encapsulation;
+ }
+
+ /**
+ * Gets L2Network l2Forward flag.
+ *
+ * @return the l2Forward flag of L2Network
+ */
+ public boolean l2Forward() {
+ return l2Forward;
+ }
+
+ /**
+ * Gets L2Network interfaces.
+ *
+ * @return the interfaces of L2Network
+ */
+ public Set<Interface> interfaces() {
+ return ImmutableSet.copyOf(interfaces);
+ }
+
+ /**
+ * Gets L2Network hosts.
+ *
+ * @return the hosts of L2Network
+ */
+ public Set<HostId> hostIds() {
+ return ImmutableSet.copyOf(hostIds);
+ }
+
+ /**
+ * Gets L2Network dirty flag.
+ *
+ * @return the dirty flag of L2Network
+ */
+ public boolean dirty() {
+ return dirty;
+ }
+
+ /**
+ * Checks if the interface is of L2Network.
+ *
+ * @param iface the interface to be checked
+ * @return true if L2Network contains the interface
+ */
+ public boolean contains(Interface iface) {
+ return interfaces.contains(iface);
+ }
+
+ /**
+ * Checks if the ConnectPoint and Vlan is of L2Network.
+ *
+ * @param port the ConnectPoint to be checked
+ * @param vlanId the VlanId of the ConnectPoint to be checked
+ * @return true if L2Network contains the interface of the ConnnectPoint and VlanId
+ */
+ public boolean contains(ConnectPoint port, VlanId vlanId) {
+ for (Interface iface : interfaces) {
+ if (iface.connectPoint().equals(port) && iface.vlan().equals(vlanId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the DeviceId is of L2Network.
+ *
+ * @param deviceId the DeviceId to be checked
+ * @return true if L2Network contains any interface of the DeviceId
+ */
+ public boolean contains(DeviceId deviceId) {
+ for (Interface iface : interfaces) {
+ if (iface.connectPoint().deviceId().equals(deviceId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Adds interface to L2Network.
+ *
+ * @param iface the Interface to be added
+ */
+ public void addInterface(Interface iface) {
+ Objects.requireNonNull(iface);
+ if (interfaces.add(iface)) {
+ setDirty(true);
+ }
+ }
+
+ /**
+ * Adds host to L2Network.
+ *
+ * @param host the Host to be added
+ */
+ public void addHost(Host host) {
+ Objects.requireNonNull(host);
+ if (hostIds.add(host.id())) {
+ setDirty(true);
+ }
+ }
+
+ /**
+ * Sets L2Network dirty flag.
+ *
+ * @param newDirty the dirty flag to be set
+ */
+ public void setDirty(boolean newDirty) {
+ dirty = newDirty;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("name", name)
+ .add("interfaceNames", interfaceNames)
+ .add("encapsulation", encapsulation)
+ .add("l2Forward", l2Forward)
+ .add("interfaces", interfaces)
+ .add("hostIds", hostIds)
+ .add("dirty", dirty)
+ .toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof L2Network)) {
+ return false;
+ }
+ L2Network other = (L2Network) obj;
+ return Objects.equals(other.name, this.name)
+ && Objects.equals(other.interfaceNames, this.interfaceNames)
+ && Objects.equals(other.encapsulation, this.encapsulation)
+ && Objects.equals(other.l2Forward, this.l2Forward)
+ && Objects.equals(other.interfaces, this.interfaces)
+ && Objects.equals(other.hostIds, this.hostIds);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, interfaces, encapsulation, l2Forward);
+ }
+}
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/Route.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/Route.java
new file mode 100644
index 0000000..8eabb95
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/Route.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2016-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.
+ */
+/*
+ * local copy of onos/incubator/api/src/main/java/org/onosproject/incubator/net/routing/Route.java
+ * to remove dependency on onos.incubator.routing services, since 2017-08-09.
+ */
+
+package org.onosproject.simplefabric;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.cluster.NodeId;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Represents a route.
+ */
+public class Route {
+
+ private static final String VERSION_MISMATCH =
+ "Prefix and next hop must be in the same address family";
+
+ private static final NodeId UNDEFINED = new NodeId("-");
+
+ /**
+ * Source of the route.
+ */
+ public enum Source {
+ /**
+ * Route came from the iBGP route source.
+ */
+ BGP,
+
+ /**
+ * Route came from the FPM route source.
+ */
+ FPM,
+
+ /**
+ * Route can from the static route source.
+ */
+ STATIC,
+
+ /**
+ * Route source was not defined.
+ */
+ UNDEFINED
+ }
+
+ private final Source source;
+ private final IpPrefix prefix;
+ private final IpAddress nextHop;
+ private final NodeId sourceNode;
+
+ /**
+ * Creates a route.
+ *
+ * @param source route source
+ * @param prefix IP prefix
+ * @param nextHop next hop IP address
+ */
+ public Route(Source source, IpPrefix prefix, IpAddress nextHop) {
+ this(source, prefix, nextHop, UNDEFINED);
+ }
+
+ /**
+ * Creates a route.
+ *
+ * @param source route source
+ * @param prefix IP prefix
+ * @param nextHop next hop IP address
+ * @param sourceNode ONOS node the route was sourced from
+ */
+ public Route(Source source, IpPrefix prefix, IpAddress nextHop, NodeId sourceNode) {
+ checkNotNull(prefix);
+ checkNotNull(nextHop);
+ checkArgument(prefix.version().equals(nextHop.version()), VERSION_MISMATCH);
+
+ this.source = checkNotNull(source);
+ this.prefix = prefix;
+ this.nextHop = nextHop;
+ this.sourceNode = checkNotNull(sourceNode);
+ }
+
+ /**
+ * Returns the route source.
+ *
+ * @return route source
+ */
+ public Source source() {
+ return source;
+ }
+
+ /**
+ * Returns the IP prefix of the route.
+ *
+ * @return IP prefix
+ */
+ public IpPrefix prefix() {
+ return prefix;
+ }
+
+ /**
+ * Returns the next hop IP address.
+ *
+ * @return next hop
+ */
+ public IpAddress nextHop() {
+ return nextHop;
+ }
+
+ /**
+ * Returns the ONOS node the route was sourced from.
+ *
+ * @return ONOS node ID
+ */
+ public NodeId sourceNode() {
+ return sourceNode;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(prefix, nextHop);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (!(other instanceof Route)) {
+ return false;
+ }
+
+ Route that = (Route) other;
+
+ return Objects.equals(this.prefix, that.prefix) &&
+ Objects.equals(this.nextHop, that.nextHop);
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("prefix", prefix)
+ .add("nextHop", nextHop)
+ .toString();
+ }
+}
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/RouteTools.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/RouteTools.java
new file mode 100644
index 0000000..b14438a
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/RouteTools.java
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+/*
+ * local copy of onos/incubator/api/src/main/java/org/onosproject/incubator/net/routing/RouteTools.java
+ * to remove dependency on onos.incubator.routing services, since 2017-08-09.
+ */
+
+package org.onosproject.simplefabric;
+
+import org.onlab.packet.IpPrefix;
+
+/**
+ * Routing tools.
+ */
+public final class RouteTools {
+
+ private RouteTools() {
+ }
+
+ /**
+ * Creates a binary string representation of an IP prefix.
+ *
+ * For each string, we put a extra "0" in the front. The purpose of
+ * doing this is to store the default route inside InvertedRadixTree.
+ *
+ * @param ipPrefix the IP prefix to use
+ * @return the binary string representation
+ */
+ public static String createBinaryString(IpPrefix ipPrefix) {
+ byte[] octets = ipPrefix.address().toOctets();
+ StringBuilder result = new StringBuilder(ipPrefix.prefixLength());
+ result.append("0");
+ for (int i = 0; i < ipPrefix.prefixLength(); i++) {
+ int byteOffset = i / Byte.SIZE;
+ int bitOffset = i % Byte.SIZE;
+ int mask = 1 << (Byte.SIZE - 1 - bitOffset);
+ byte value = octets[byteOffset];
+ boolean isSet = ((value & mask) != 0);
+ result.append(isSet ? "1" : "0");
+ }
+ return result.toString();
+ }
+}
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricConfig.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricConfig.java
new file mode 100644
index 0000000..a6d83b1
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricConfig.java
@@ -0,0 +1,155 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.simplefabric;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.collect.Sets;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.EncapsulationType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Set;
+
+/**
+ * Configuration object for prefix config.
+ */
+public class SimpleFabricConfig extends Config<ApplicationId> {
+ public static final String KEY = "simpleFabric";
+
+ private static final String L2NETWORKS = "l2Networks";
+ private static final String NAME = "name";
+ private static final String INTERFACES = "interfaces";
+ private static final String ENCAPSULATION = "encapsulation";
+ private static final String L2FORWARD = "l2Forward";
+ private static final String IPSUBNETS = "ipSubnets";
+ private static final String BORDERROUTES = "borderRoutes";
+ private static final String IPPREFIX = "ipPrefix";
+ private static final String GATEWAYIP = "gatewayIp";
+ private static final String GATEWAYMAC = "gatewayMac";
+ private static final String L2NETWORKNAME = "l2NetworkName";
+ private static final String NEXTHOP = "nextHop";
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ /**
+ * Returns all l2Networks in this configuration.
+ *
+ * @return A set of L2Network.
+ */
+ public Set<L2Network> getL2Networks() {
+ Set<L2Network> l2Networks = Sets.newHashSet();
+ JsonNode l2NetworkNode = object.get(L2NETWORKS);
+ if (l2NetworkNode == null) {
+ return l2Networks;
+ }
+
+ l2NetworkNode.forEach(jsonNode -> {
+ Set<String> ifaces = Sets.newHashSet();
+ JsonNode l2NetworkIfaces = jsonNode.path(INTERFACES);
+ if (l2NetworkIfaces == null) {
+ log.warn("simple fabric network config cannot find {}; skip: jsonNode={}", INTERFACES, jsonNode);
+ } else if (!l2NetworkIfaces.toString().isEmpty()) {
+ l2NetworkIfaces.forEach(ifacesNode -> ifaces.add(new String(ifacesNode.asText())));
+ }
+ String encapsulation = "NONE"; // NONE or VLAN
+ if (jsonNode.hasNonNull(ENCAPSULATION)) {
+ encapsulation = jsonNode.get(ENCAPSULATION).asText();
+ }
+ boolean l2Forward = true;
+ if (jsonNode.hasNonNull(L2FORWARD)) {
+ l2Forward = jsonNode.get(L2FORWARD).asBoolean();
+ }
+ try {
+ l2Networks.add(new L2Network(
+ jsonNode.get(NAME).asText(),
+ ifaces,
+ EncapsulationType.enumFromString(encapsulation),
+ l2Forward));
+ } catch (Exception e) {
+ log.warn("simple fabric network config l2Network parse failed; skip: error={} jsonNode={}", jsonNode);
+ }
+ });
+ return l2Networks;
+ }
+
+ /**
+ * Gets the set of configured local IP subnets.
+ *
+ * @return IP Subnets
+ */
+ public Set<IpSubnet> ipSubnets() {
+ Set<IpSubnet> subnets = Sets.newHashSet();
+ JsonNode subnetsNode = object.get(IPSUBNETS);
+ if (subnetsNode == null) {
+ log.warn("simple fabric network config ipSubnets is null!");
+ return subnets;
+ }
+
+ subnetsNode.forEach(jsonNode -> {
+ String encapsulation = "NONE"; // NONE or VLAN
+ if (jsonNode.hasNonNull(ENCAPSULATION)) {
+ encapsulation = jsonNode.get(ENCAPSULATION).asText();
+ }
+ try {
+ subnets.add(new IpSubnet(
+ IpPrefix.valueOf(jsonNode.get(IPPREFIX).asText()),
+ IpAddress.valueOf(jsonNode.get(GATEWAYIP).asText()),
+ MacAddress.valueOf(jsonNode.get(GATEWAYMAC).asText()),
+ EncapsulationType.enumFromString(encapsulation),
+ jsonNode.get(L2NETWORKNAME).asText()));
+ } catch (Exception e) {
+ log.warn("simple fabric network config ipSubnet parse failed; skip: error={} jsonNode={}", jsonNode);
+ }
+ });
+
+ return subnets;
+ }
+
+ /**
+ * Returns all routes in this configuration.
+ *
+ * @return A set of route.
+ */
+ public Set<Route> borderRoutes() {
+ Set<Route> routes = Sets.newHashSet();
+
+ JsonNode routesNode = object.get(BORDERROUTES);
+ if (routesNode == null) {
+ //log.warn("simple fabric network config borderRoutes is null!");
+ return routes;
+ }
+
+ routesNode.forEach(jsonNode -> {
+ try {
+ routes.add(new Route(
+ Route.Source.STATIC,
+ IpPrefix.valueOf(jsonNode.path(IPPREFIX).asText()),
+ IpAddress.valueOf(jsonNode.path(NEXTHOP).asText())));
+ } catch (IllegalArgumentException e) {
+ log.warn("simple fabric network config parse error; skip: {}", jsonNode);
+ }
+ });
+
+ return routes;
+ }
+
+}
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricEvent.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricEvent.java
new file mode 100644
index 0000000..cac1276
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricEvent.java
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.simplefabric;
+
+import org.onosproject.event.AbstractEvent;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+/**
+ * Describes an interface event.
+ */
+public class SimpleFabricEvent extends AbstractEvent<SimpleFabricEvent.Type, String> {
+
+ public enum Type {
+ SIMPLE_FABRIC_UPDATED, /* Indicates topology info has been updated. */
+ SIMPLE_FABRIC_FLUSH, /* Indicates flush triggered. */
+ SIMPLE_FABRIC_IDLE, /* Indicates idle check. */
+ SIMPLE_FABRIC_DUMP /* Indicates to dump info on the subject to output stream. */
+ }
+
+ private PrintStream printStream; // output stream for SIMPLE_FABRIC_DUMP
+
+ /**
+ * Creates an interface event with type and subject.
+ *
+ * @param type event type
+ * @param subject subject for dump event or dummy
+ */
+ public SimpleFabricEvent(Type type, String subject) {
+ super(type, subject); /* subject is dummy */
+ }
+
+ /**
+ * Creates an interface event with type, subject and output stream for dump.
+ *
+ * @param type event type
+ * @param subject subject for dump event
+ * @param out output stream to dump out
+ */
+ public SimpleFabricEvent(Type type, String subject, OutputStream out) {
+ super(type, subject); /* subject is dummy */
+ printStream = new PrintStream(out, true);
+ }
+
+ public PrintStream out() {
+ return printStream;
+ }
+
+}
+
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricL2Forward.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricL2Forward.java
new file mode 100644
index 0000000..41f0253
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricL2Forward.java
@@ -0,0 +1,437 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.simplefabric;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+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.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.EncapsulationType;
+import org.onosproject.net.FilteredConnectPoint;
+import org.onosproject.net.Host;
+import org.onosproject.net.ResourceGroup;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.MultiPointToSinglePointIntent;
+import org.onosproject.net.intent.SinglePointToMultiPointIntent;
+import org.onosproject.net.intent.constraint.EncapsulationConstraint;
+import org.onosproject.net.intent.constraint.PartialFailureConstraint;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+
+/**
+ * An implementation of L2NetworkOperationService.
+ * Handles the execution order of the L2 Network operations generated by the
+ * application.
+ */
+@Component(immediate = true, enabled = false)
+public class SimpleFabricL2Forward {
+
+ public static final String BROADCAST = "BCAST";
+ public static final String UNICAST = "UNI";
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+ protected ApplicationId l2ForwardAppId;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected IntentService intentService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostService hostService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected SimpleFabricService simpleFabric;
+
+ public static final ImmutableList<Constraint> L2NETWORK_CONSTRAINTS =
+ ImmutableList.of(new PartialFailureConstraint());
+
+ private Map<Key, SinglePointToMultiPointIntent> bctIntentsMap = Maps.newConcurrentMap();
+ private Map<Key, MultiPointToSinglePointIntent> uniIntentsMap = Maps.newConcurrentMap();
+ private Set<Key> toBePurgedIntentKeys = new HashSet<>();
+
+ private final InternalSimpleFabricListener simpleFabricListener = new InternalSimpleFabricListener();
+
+ @Activate
+ public void activate() {
+ l2ForwardAppId = coreService.registerApplication(simpleFabric.L2FORWARD_APP_ID);
+ log.info("simple fabric l2 forwaring starting with l2net app id {}", l2ForwardAppId.toString());
+
+ simpleFabric.addListener(simpleFabricListener);
+
+ refresh();
+ checkIntentsPurge();
+
+ log.info("simple fabric l2forward started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("simple fabric l2forward stopping");
+
+ simpleFabric.removeListener(simpleFabricListener);
+
+ for (Intent intent : bctIntentsMap.values()) {
+ intentService.withdraw(intent);
+ toBePurgedIntentKeys.add(intent.key());
+ }
+ for (Intent intent : uniIntentsMap.values()) {
+ intentService.withdraw(intent);
+ toBePurgedIntentKeys.add(intent.key());
+ }
+ for (Key key : toBePurgedIntentKeys) {
+ Intent intentToPurge = intentService.getIntent(key);
+ if (intentToPurge != null) {
+ intentService.purge(intentToPurge);
+ }
+ }
+
+ // do not set clear for switch compatibility
+ //bctIntentsMap.clear();
+ //uniIntentsMap.clear();
+
+ log.info("simple fabric l2forward stopped");
+ }
+
+ private void refresh() {
+ log.debug("simple fabric l2forward refresh");
+
+ Map<Key, SinglePointToMultiPointIntent> newBctIntentsMap = Maps.newConcurrentMap();
+ Map<Key, MultiPointToSinglePointIntent> newUniIntentsMap = Maps.newConcurrentMap();
+
+ for (L2Network l2Network : simpleFabric.getL2Networks()) {
+ // scans all l2network regardless of dirty flag
+ // if l2Network.l2Forward == false or number of interfaces() < 2, no Intents generated
+ for (SinglePointToMultiPointIntent intent : buildBrcIntents(l2Network)) {
+ newBctIntentsMap.put(intent.key(), intent);
+ }
+ for (MultiPointToSinglePointIntent intent : buildUniIntents(l2Network, hostsFromL2Network(l2Network))) {
+ newUniIntentsMap.put(intent.key(), intent);
+ }
+ if (l2Network.dirty()) {
+ l2Network.setDirty(false);
+ }
+ }
+
+ boolean bctUpdated = false;
+ for (SinglePointToMultiPointIntent intent : bctIntentsMap.values()) {
+ SinglePointToMultiPointIntent newIntent = newBctIntentsMap.get(intent.key());
+ if (newIntent == null) {
+ log.info("simple fabric l2forward withdraw broadcast intent: {}", intent.key().toString());
+ toBePurgedIntentKeys.add(intent.key());
+ intentService.withdraw(intent);
+ bctUpdated = true;
+ }
+ }
+ for (SinglePointToMultiPointIntent intent : newBctIntentsMap.values()) {
+ SinglePointToMultiPointIntent oldIntent = bctIntentsMap.get(intent.key());
+ if (oldIntent == null ||
+ !oldIntent.filteredEgressPoints().equals(intent.filteredEgressPoints()) ||
+ !oldIntent.filteredIngressPoint().equals(intent.filteredIngressPoint()) ||
+ !oldIntent.selector().equals(intent.selector()) ||
+ !oldIntent.treatment().equals(intent.treatment()) ||
+ !oldIntent.constraints().equals(intent.constraints())) {
+ log.info("simple fabric l2forward submit broadcast intent: {}", intent.key().toString());
+ toBePurgedIntentKeys.remove(intent.key());
+ intentService.submit(intent);
+ bctUpdated = true;
+ }
+ }
+
+ boolean uniUpdated = false;
+ for (MultiPointToSinglePointIntent intent : uniIntentsMap.values()) {
+ MultiPointToSinglePointIntent newIntent = newUniIntentsMap.get(intent.key());
+ if (newIntent == null) {
+ log.info("simple fabric l2forward withdraw unicast intent: {}", intent.key().toString());
+ toBePurgedIntentKeys.add(intent.key());
+ intentService.withdraw(intent);
+ uniUpdated = true;
+ }
+ }
+ for (MultiPointToSinglePointIntent intent : newUniIntentsMap.values()) {
+ MultiPointToSinglePointIntent oldIntent = uniIntentsMap.get(intent.key());
+ if (oldIntent == null ||
+ !oldIntent.filteredEgressPoint().equals(intent.filteredEgressPoint()) ||
+ !oldIntent.filteredIngressPoints().equals(intent.filteredIngressPoints()) ||
+ !oldIntent.selector().equals(intent.selector()) ||
+ !oldIntent.treatment().equals(intent.treatment()) ||
+ !oldIntent.constraints().equals(intent.constraints())) {
+ log.info("simple fabric l2forward submit unicast intent: {}", intent.key().toString());
+ toBePurgedIntentKeys.remove(intent.key());
+ intentService.submit(intent);
+ uniUpdated = true;
+ }
+ }
+
+ if (bctUpdated) {
+ bctIntentsMap = newBctIntentsMap;
+ }
+ if (uniUpdated) {
+ uniIntentsMap = newUniIntentsMap;
+ }
+ }
+
+ private void checkIntentsPurge() {
+ // check intents to be purge
+ if (!toBePurgedIntentKeys.isEmpty()) {
+ Set<Key> purgedKeys = new HashSet<>();
+ for (Key key : toBePurgedIntentKeys) {
+ Intent intentToPurge = intentService.getIntent(key);
+ if (intentToPurge == null) {
+ log.info("simple fabric l2forward purged intent: key={}", key.toString());
+ purgedKeys.add(key);
+ } else {
+ switch (intentService.getIntentState(key)) {
+ case FAILED:
+ case WITHDRAWN:
+ log.info("simple fabric l2forward try to purge intent: key={}", key.toString());
+ intentService.purge(intentToPurge);
+ break;
+ case INSTALL_REQ:
+ case INSTALLED:
+ case INSTALLING:
+ case RECOMPILING:
+ case COMPILING:
+ log.warn("simple fabric l2forward withdraw intent to purge: key={}", key);
+ intentService.withdraw(intentToPurge);
+ break;
+ case WITHDRAW_REQ:
+ case WITHDRAWING:
+ case PURGE_REQ:
+ case CORRUPT:
+ default:
+ // no action
+ break;
+ }
+ }
+ }
+ toBePurgedIntentKeys.removeAll(purgedKeys);
+ }
+ }
+
+ // Generates Unicast Intents and broadcast Intents for the L2 Network.
+
+ private Set<Intent> generateL2NetworkIntents(L2Network l2Network) {
+ return new ImmutableSet.Builder<Intent>()
+ .addAll(buildBrcIntents(l2Network))
+ .addAll(buildUniIntents(l2Network, hostsFromL2Network(l2Network)))
+ .build();
+ }
+
+ // Build Boadcast Intents for a L2 Network.
+ private Set<SinglePointToMultiPointIntent> buildBrcIntents(L2Network l2Network) {
+ Set<Interface> interfaces = l2Network.interfaces();
+ if (!l2Network.l2Forward() || interfaces.size() < 2) {
+ return ImmutableSet.of();
+ }
+ Set<SinglePointToMultiPointIntent> brcIntents = Sets.newHashSet();
+ ResourceGroup resourceGroup = ResourceGroup.of(l2Network.name());
+
+ // Generates broadcast Intents from any network interface to other
+ // network interface from the L2 Network.
+ interfaces
+ .forEach(src -> {
+ FilteredConnectPoint srcFcp = buildFilteredConnectedPoint(src);
+ Set<FilteredConnectPoint> dstFcps = interfaces.stream()
+ .filter(iface -> !iface.equals(src))
+ .map(this::buildFilteredConnectedPoint)
+ .collect(Collectors.toSet());
+ Key key = buildKey(l2Network.name(), "BCAST", srcFcp.connectPoint(), MacAddress.BROADCAST);
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthDst(MacAddress.BROADCAST)
+ .build();
+ SinglePointToMultiPointIntent.Builder intentBuilder = SinglePointToMultiPointIntent.builder()
+ .appId(l2ForwardAppId)
+ .key(key)
+ .selector(selector)
+ .filteredIngressPoint(srcFcp)
+ .filteredEgressPoints(dstFcps)
+ .constraints(buildConstraints(L2NETWORK_CONSTRAINTS, l2Network.encapsulation()))
+ .priority(SimpleFabricService.PRI_L2NETWORK_BROADCAST)
+ .resourceGroup(resourceGroup);
+ brcIntents.add(intentBuilder.build());
+ });
+ return brcIntents;
+ }
+
+ // Builds unicast Intents for a L2 Network.
+ private Set<MultiPointToSinglePointIntent> buildUniIntents(L2Network l2Network, Set<Host> hosts) {
+ Set<Interface> interfaces = l2Network.interfaces();
+ if (!l2Network.l2Forward() || interfaces.size() < 2) {
+ return ImmutableSet.of();
+ }
+ Set<MultiPointToSinglePointIntent> uniIntents = Sets.newHashSet();
+ ResourceGroup resourceGroup = ResourceGroup.of(l2Network.name());
+ hosts.forEach(host -> {
+ FilteredConnectPoint hostFcp = buildFilteredConnectedPoint(host);
+ Set<FilteredConnectPoint> srcFcps = interfaces.stream()
+ .map(this::buildFilteredConnectedPoint)
+ .filter(fcp -> !fcp.equals(hostFcp))
+ .collect(Collectors.toSet());
+ Key key = buildKey(l2Network.name(), "UNI", hostFcp.connectPoint(), host.mac());
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthDst(host.mac()).build();
+ MultiPointToSinglePointIntent.Builder intentBuilder = MultiPointToSinglePointIntent.builder()
+ .appId(l2ForwardAppId)
+ .key(key)
+ .selector(selector)
+ .filteredIngressPoints(srcFcps)
+ .filteredEgressPoint(hostFcp)
+ .constraints(buildConstraints(L2NETWORK_CONSTRAINTS, l2Network.encapsulation()))
+ .priority(SimpleFabricService.PRI_L2NETWORK_UNICAST)
+ .resourceGroup(resourceGroup);
+ uniIntents.add(intentBuilder.build());
+ });
+
+ return uniIntents;
+ }
+
+ // Intent generate utilities
+
+ private Set<Host> hostsFromL2Network(L2Network l2Network) {
+ Set<Interface> interfaces = l2Network.interfaces();
+ return interfaces.stream()
+ .map(this::hostsFromInterface)
+ .flatMap(Collection::stream)
+ .collect(Collectors.toSet());
+ }
+
+ private Set<Host> hostsFromInterface(Interface iface) {
+ return hostService.getConnectedHosts(iface.connectPoint())
+ .stream()
+ .filter(host -> host.vlan().equals(iface.vlan()))
+ .collect(Collectors.toSet());
+ }
+
+ private Key buildKey(String l2NetworkName, String type, ConnectPoint cPoint, MacAddress dstMac) {
+ return Key.of(l2NetworkName + "-" + type + "-" + cPoint.toString() + "-" + dstMac, l2ForwardAppId);
+ }
+
+ private List<Constraint> buildConstraints(List<Constraint> constraints, EncapsulationType encapsulation) {
+ if (!encapsulation.equals(EncapsulationType.NONE)) {
+ List<Constraint> newConstraints = new ArrayList<>(constraints);
+ constraints.stream()
+ .filter(c -> c instanceof EncapsulationConstraint)
+ .forEach(newConstraints::remove);
+ newConstraints.add(new EncapsulationConstraint(encapsulation));
+ return ImmutableList.copyOf(newConstraints);
+ }
+ return constraints;
+ }
+
+ private FilteredConnectPoint buildFilteredConnectedPoint(Interface iface) {
+ Objects.requireNonNull(iface);
+ TrafficSelector.Builder trafficSelector = DefaultTrafficSelector.builder();
+
+ if (iface.vlan() != null && !iface.vlan().equals(VlanId.NONE)) {
+ trafficSelector.matchVlanId(iface.vlan());
+ }
+ return new FilteredConnectPoint(iface.connectPoint(), trafficSelector.build());
+ }
+
+ protected FilteredConnectPoint buildFilteredConnectedPoint(Host host) {
+ Objects.requireNonNull(host);
+ TrafficSelector.Builder trafficSelector = DefaultTrafficSelector.builder();
+
+ if (host.vlan() != null && !host.vlan().equals(VlanId.NONE)) {
+ trafficSelector.matchVlanId(host.vlan());
+ }
+ return new FilteredConnectPoint(host.location(), trafficSelector.build());
+ }
+
+ // Dump command handler
+ private void dump(String subject, PrintStream out) {
+ if (subject == "intents") {
+ out.println("L2Forward Broadcast Intents:\n");
+ for (SinglePointToMultiPointIntent intent: bctIntentsMap.values()) {
+ out.println(" " + intent.key().toString()
+ + ": " + intent.selector().criteria()
+ + ", [" + intent.filteredIngressPoint().connectPoint()
+ + "] -> " + intent.filteredEgressPoints().stream()
+ .map(FilteredConnectPoint::connectPoint).collect(Collectors.toSet()));
+ }
+ out.println("");
+ out.println("L2Forward Unicast Intents:\n");
+ for (MultiPointToSinglePointIntent intent: uniIntentsMap.values()) {
+ out.println(" " + intent.key().toString()
+ + ": " + intent.selector().criteria()
+ + ", [" + intent.filteredIngressPoints().stream()
+ .map(FilteredConnectPoint::connectPoint).collect(Collectors.toSet())
+ + "] -> " + intent.filteredEgressPoint().connectPoint());
+ }
+ out.println("");
+ out.println("L2Forward Intents to Be Purged:\n");
+ for (Key key: toBePurgedIntentKeys) {
+ out.println(" " + key.toString());
+ }
+ out.println("");
+ }
+ }
+
+ // Listener
+ private class InternalSimpleFabricListener implements SimpleFabricListener {
+ @Override
+ public void event(SimpleFabricEvent event) {
+ switch (event.type()) {
+ case SIMPLE_FABRIC_UPDATED:
+ refresh();
+ checkIntentsPurge();
+ break;
+ case SIMPLE_FABRIC_IDLE:
+ refresh();
+ checkIntentsPurge();
+ break;
+ case SIMPLE_FABRIC_DUMP:
+ dump(event.subject(), event.out());
+ break;
+ default:
+ // NOTE: nothing to do on SIMPLE_FABRIC_FLUSH
+ break;
+ }
+ }
+ }
+
+}
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricListener.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricListener.java
new file mode 100644
index 0000000..726d578
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricListener.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+package org.onosproject.simplefabric;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Entity capable of receiving alarm related events.
+ */
+public interface SimpleFabricListener extends EventListener<SimpleFabricEvent> {
+}
+
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricManager.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricManager.java
new file mode 100644
index 0000000..2bb53c9
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricManager.java
@@ -0,0 +1,687 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.simplefabric;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
+import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
+import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
+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.apache.felix.scr.annotations.Service;
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.packet.ndp.NeighborSolicitation;
+import org.onosproject.app.ApplicationService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.component.ComponentService;
+import org.onosproject.event.ListenerRegistry;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.intf.InterfaceService;
+import org.onosproject.net.intf.InterfaceListener;
+import org.onosproject.net.intf.InterfaceEvent;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.config.basics.SubjectFactories;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.Host;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.HashSet;
+import java.util.Collection;
+import java.util.Set;
+import java.util.Map;
+
+import static org.onosproject.simplefabric.RouteTools.createBinaryString;
+
+
+/**
+ * Reactive routing configuration manager.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleFabricManager extends ListenerRegistry<SimpleFabricEvent, SimpleFabricListener>
+ implements SimpleFabricService {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ApplicationService applicationService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected NetworkConfigService configService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected NetworkConfigRegistry registry;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected InterfaceService interfaceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostService hostService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected PacketService packetService;
+
+ // compoents to be activated within SimpleFabric
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ComponentService componentService;
+
+ // SimpleFabric variables
+ private ApplicationId appId = null;
+
+ // l2 broadcast networks
+ private Set<L2Network> l2Networks = new HashSet<>();
+ private Set<Interface> l2NetworkInterfaces = new HashSet<>();
+
+ // Subnet table
+ private Set<IpSubnet> ipSubnets = new HashSet<>();
+ private InvertedRadixTree<IpSubnet> ip4SubnetTable =
+ new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+ private InvertedRadixTree<IpSubnet> ip6SubnetTable =
+ new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+
+ // Border Route table
+ private Set<Route> borderRoutes = new HashSet<>();
+ private InvertedRadixTree<Route> ip4BorderRouteTable =
+ new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+ private InvertedRadixTree<Route> ip6BorderRouteTable =
+ new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+
+ // VirtialGateway
+ private Map<IpAddress, MacAddress> virtualGatewayIpMacMap = Maps.newConcurrentMap();
+
+ // Refresh monitor thread
+ private Object refreshMonitor = new Object();
+ private boolean doRefresh = false;
+ private boolean doFlush = false;
+ private InternalRefreshThread refreshThread;
+
+ // Listener for Service Events
+ private final InternalNetworkConfigListener configListener = new InternalNetworkConfigListener();
+ private final InternalDeviceListener deviceListener = new InternalDeviceListener();
+ private final InternalHostListener hostListener = new InternalHostListener();
+ private final InternalInterfaceListener interfaceListener = new InternalInterfaceListener();
+
+ private ConfigFactory<ApplicationId, SimpleFabricConfig> simpleFabricConfigFactory =
+ new ConfigFactory<ApplicationId, SimpleFabricConfig>(
+ SubjectFactories.APP_SUBJECT_FACTORY,
+ SimpleFabricConfig.class, SimpleFabricConfig.KEY) {
+ @Override
+ public SimpleFabricConfig createConfig() {
+ return new SimpleFabricConfig();
+ }
+ };
+
+ @Activate
+ public void activate() {
+ log.info("simple fabric starting");
+
+ if (appId == null) {
+ appId = coreService.registerApplication(APP_ID);
+ }
+
+ // initial refresh
+ refresh();
+
+ configService.addListener(configListener);
+ registry.registerConfigFactory(simpleFabricConfigFactory);
+ deviceService.addListener(deviceListener);
+ hostService.addListener(hostListener);
+
+ componentService.activate(appId, SimpleFabricNeighbour.class.getName());
+ componentService.activate(appId, SimpleFabricReactiveRouting.class.getName());
+ if (SimpleFabricService.ALLOW_ETH_ADDRESS_SELECTOR) {
+ componentService.activate(appId, SimpleFabricL2Forward.class.getName());
+ }
+
+ refreshThread = new InternalRefreshThread();
+ refreshThread.start();
+
+ log.info("simple fabric started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("simple fabric stopping");
+
+ componentService.deactivate(appId, SimpleFabricNeighbour.class.getName());
+ componentService.deactivate(appId, SimpleFabricReactiveRouting.class.getName());
+ if (SimpleFabricService.ALLOW_ETH_ADDRESS_SELECTOR) {
+ componentService.deactivate(appId, SimpleFabricL2Forward.class.getName());
+ }
+
+ deviceService.removeListener(deviceListener);
+ hostService.removeListener(hostListener);
+ registry.unregisterConfigFactory(simpleFabricConfigFactory);
+ configService.removeListener(configListener);
+
+ refreshThread.stop();
+ refreshThread = null;
+
+ log.info("simple fabric stopped");
+ }
+
+ // Set up from configuration
+ // returns found dirty and refresh listners are called (true) or not (false)
+ private boolean refresh() {
+ log.debug("simple fabric refresh");
+ boolean dirty = false;
+
+ SimpleFabricConfig config = configService.getConfig(coreService.registerApplication(APP_ID),
+ SimpleFabricConfig.class);
+ if (config == null) {
+ log.debug("No reactive routing config available!");
+ return false;
+ }
+
+ // l2Networks
+ Set<L2Network> newL2Networks = new HashSet<>();
+ Set<Interface> newL2NetworkInterfaces = new HashSet<>();
+ for (L2Network newL2NetworkConfig : config.getL2Networks()) {
+ L2Network newL2Network = L2Network.of(newL2NetworkConfig);
+
+ // fill up interfaces and Hosts with active port only
+ for (String ifaceName : newL2NetworkConfig.interfaceNames()) {
+ Interface iface = getInterfaceByName(ifaceName);
+ if (iface != null && deviceService.isAvailable(iface.connectPoint().deviceId())) {
+ newL2Network.addInterface(iface);
+ newL2NetworkInterfaces.add(iface);
+ }
+ }
+ for (Host host : hostService.getHosts()) {
+ // consider host with ip only
+ if (!host.ipAddresses().isEmpty()) {
+ Interface iface = getAvailableDeviceHostInterface(host);
+ if (iface != null && newL2Network.contains(iface)) {
+ newL2Network.addHost(host);
+ }
+ }
+ }
+ newL2Network.setDirty(true);
+
+ // update newL2Network's dirty flags if same entry already exists
+ for (L2Network prevL2Network : l2Networks) {
+ if (prevL2Network.equals(newL2Network)) {
+ newL2Network.setDirty(prevL2Network.dirty());
+ break;
+ }
+ }
+ newL2Networks.add(newL2Network);
+ }
+ if (!l2Networks.equals(newL2Networks)) {
+ l2Networks = newL2Networks;
+ dirty = true;
+ }
+ if (!l2NetworkInterfaces.equals(newL2NetworkInterfaces)) {
+ l2NetworkInterfaces = newL2NetworkInterfaces;
+ dirty = true;
+ }
+
+ // ipSubnets
+ Set<IpSubnet> newIpSubnets = config.ipSubnets();
+ InvertedRadixTree<IpSubnet> newIp4SubnetTable =
+ new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+ InvertedRadixTree<IpSubnet> newIp6SubnetTable =
+ new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+ Map<IpAddress, MacAddress> newVirtualGatewayIpMacMap = Maps.newConcurrentMap();
+ for (IpSubnet subnet : newIpSubnets) {
+ if (subnet.ipPrefix().isIp4()) {
+ newIp4SubnetTable.put(createBinaryString(subnet.ipPrefix()), subnet);
+ } else {
+ newIp6SubnetTable.put(createBinaryString(subnet.ipPrefix()), subnet);
+ }
+ newVirtualGatewayIpMacMap.put(subnet.gatewayIp(), subnet.gatewayMac());
+ }
+ if (!ipSubnets.equals(newIpSubnets)) {
+ ipSubnets = newIpSubnets;
+ ip4SubnetTable = newIp4SubnetTable;
+ ip6SubnetTable = newIp6SubnetTable;
+ dirty = true;
+ }
+ if (!virtualGatewayIpMacMap.equals(newVirtualGatewayIpMacMap)) {
+ virtualGatewayIpMacMap = newVirtualGatewayIpMacMap;
+ dirty = true;
+ }
+
+ // borderRoutes config handling
+ Set<Route> newBorderRoutes = config.borderRoutes();
+ if (!borderRoutes.equals(newBorderRoutes)) {
+ InvertedRadixTree<Route> newIp4BorderRouteTable =
+ new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+ InvertedRadixTree<Route> newIp6BorderRouteTable =
+ new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+ for (Route route : newBorderRoutes) {
+ if (route.prefix().isIp4()) {
+ newIp4BorderRouteTable.put(createBinaryString(route.prefix()), route);
+ } else {
+ newIp6BorderRouteTable.put(createBinaryString(route.prefix()), route);
+ }
+ }
+ borderRoutes = newBorderRoutes;
+ ip4BorderRouteTable = newIp4BorderRouteTable;
+ ip6BorderRouteTable = newIp6BorderRouteTable;
+ dirty = true;
+ }
+
+ // notify to SimpleFabric listeners
+ if (dirty) {
+ log.info("simple fabric refresh; notify events");
+ process(new SimpleFabricEvent(SimpleFabricEvent.Type.SIMPLE_FABRIC_UPDATED, "updated"));
+ }
+ return dirty;
+ }
+
+ private Interface getInterfaceByName(String interfaceName) {
+ Interface intf = interfaceService.getInterfaces().stream()
+ .filter(iface -> iface.name().equals(interfaceName))
+ .findFirst()
+ .orElse(null);
+ if (intf == null) {
+ log.warn("simple fabric unknown interface name: {}", interfaceName);
+ }
+ return intf;
+ }
+
+ @Override
+ public ApplicationId getAppId() {
+ if (appId == null) {
+ appId = coreService.registerApplication(APP_ID);
+ }
+ return appId;
+ }
+
+ @Override
+ public Collection<L2Network> getL2Networks() {
+ return ImmutableSet.copyOf(l2Networks);
+ }
+
+ @Override
+ public Set<IpSubnet> getIpSubnets() {
+ return ImmutableSet.copyOf(ipSubnets);
+ }
+
+ @Override
+ public Set<Route> getBorderRoutes() {
+ return ImmutableSet.copyOf(borderRoutes);
+ }
+
+ @Override
+ public MacAddress getVMacForIp(IpAddress ip) {
+ return virtualGatewayIpMacMap.get(ip);
+ }
+
+ @Override
+ public boolean isVMac(MacAddress mac) {
+ return virtualGatewayIpMacMap.containsValue(mac);
+ }
+
+ @Override
+ public boolean isL2NetworkInterface(Interface intf) {
+ return l2NetworkInterfaces.contains(intf);
+ }
+
+ @Override
+ public L2Network findL2Network(ConnectPoint port, VlanId vlanId) {
+ for (L2Network l2Network : l2Networks) {
+ if (l2Network.contains(port, vlanId)) {
+ return l2Network;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public L2Network findL2Network(String name) {
+ for (L2Network l2Network : l2Networks) {
+ if (l2Network.name().equals(name)) {
+ return l2Network;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public IpSubnet findIpSubnet(IpAddress ip) {
+ Iterator<IpSubnet> it;
+ if (ip.isIp4()) {
+ it = ip4SubnetTable.getValuesForKeysPrefixing(
+ createBinaryString(IpPrefix.valueOf(ip, Ip4Address.BIT_LENGTH))).iterator();
+ } else {
+ it = ip6SubnetTable.getValuesForKeysPrefixing(
+ createBinaryString(IpPrefix.valueOf(ip, Ip6Address.BIT_LENGTH))) .iterator();
+ }
+ return (it.hasNext()) ? it.next() : null;
+ }
+
+ @Override
+ public Route findBorderRoute(IpAddress ip) {
+ // ASSUME: ipAddress is out of ipSubnet
+ Iterator<Route> it;
+ if (ip.isIp4()) {
+ it = ip4BorderRouteTable.getValuesForKeysPrefixing(
+ createBinaryString(IpPrefix.valueOf(ip, Ip4Address.BIT_LENGTH))) .iterator();
+ } else {
+ it = ip6BorderRouteTable.getValuesForKeysPrefixing(
+ createBinaryString(IpPrefix.valueOf(ip, Ip6Address.BIT_LENGTH))) .iterator();
+ }
+ return (it.hasNext()) ? it.next() : null;
+ }
+
+
+ @Override
+ public Interface getHostInterface(Host host) {
+ return interfaceService.getInterfaces().stream()
+ .filter(iface -> iface.connectPoint().equals(host.location()) &&
+ iface.vlan().equals(host.vlan()))
+ .findFirst()
+ .orElse(null);
+ }
+
+ private Interface getAvailableDeviceHostInterface(Host host) {
+ return interfaceService.getInterfaces().stream()
+ .filter(iface -> iface.connectPoint().equals(host.location()) &&
+ iface.vlan().equals(host.vlan()))
+ .filter(iface -> deviceService.isAvailable(iface.connectPoint().deviceId()))
+ .findFirst()
+ .orElse(null);
+ }
+
+ @Override
+ public boolean isIpAddressLocal(IpAddress ip) {
+ boolean result;
+ if (ip.isIp4()) {
+ return ip4SubnetTable.getValuesForKeysPrefixing(
+ createBinaryString(IpPrefix.valueOf(ip, Ip4Address.BIT_LENGTH)))
+ .iterator().hasNext();
+ } else {
+ return ip6SubnetTable.getValuesForKeysPrefixing(
+ createBinaryString(IpPrefix.valueOf(ip, Ip6Address.BIT_LENGTH)))
+ .iterator().hasNext();
+ }
+ }
+
+ @Override
+ public boolean isIpPrefixLocal(IpPrefix ipPrefix) {
+ if (ipPrefix.isIp4()) {
+ return (ip4SubnetTable.getValueForExactKey(createBinaryString(ipPrefix)) != null);
+ } else {
+ return (ip6SubnetTable.getValueForExactKey(createBinaryString(ipPrefix)) != null);
+ }
+ }
+
+ @Override
+ public boolean requestMac(IpAddress ip) {
+ IpSubnet ipSubnet = findIpSubnet(ip);
+ if (ipSubnet == null) {
+ log.warn("simple fabric request mac failed for unknown IpSubnet: {}", ip);
+ return false;
+ }
+ L2Network l2Network = findL2Network(ipSubnet.l2NetworkName());
+ if (l2Network == null) {
+ log.warn("simple fabric request mac failed for unknown l2Network name {}: {}",
+ ipSubnet.l2NetworkName(), ip);
+ return false;
+ }
+ log.debug("simple fabric send request mac L2Network {}: {}", l2Network.name(), ip);
+ for (Interface iface : l2Network.interfaces()) {
+ Ethernet neighbourReq;
+ if (ip.isIp4()) {
+ neighbourReq = ARP.buildArpRequest(ipSubnet.gatewayMac().toBytes(),
+ ipSubnet.gatewayIp().toOctets(),
+ ip.toOctets(),
+ iface.vlan().toShort());
+ } else {
+ byte[] soliciteIp = IPv6.getSolicitNodeAddress(ip.toOctets());
+ neighbourReq = NeighborSolicitation.buildNdpSolicit(
+ ip.toOctets(),
+ ipSubnet.gatewayIp().toOctets(),
+ soliciteIp,
+ ipSubnet.gatewayMac().toBytes(),
+ IPv6.getMCastMacAddress(soliciteIp),
+ iface.vlan());
+ }
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(iface.connectPoint().port()).build();
+ OutboundPacket packet = new DefaultOutboundPacket(iface.connectPoint().deviceId(),
+ treatment, ByteBuffer.wrap(neighbourReq.serialize()));
+ packetService.emit(packet);
+ }
+ return true;
+ }
+
+ @Override
+ public void dumpToStream(String subject, OutputStream out) {
+ SimpleFabricEvent event = new SimpleFabricEvent(SimpleFabricEvent.Type.SIMPLE_FABRIC_DUMP, subject, out);
+ dump(event.subject(), event.out()); // dump in itself
+ process(event); // dump in sub modules
+ }
+
+ // Dump handler
+ protected void dump(String subject, PrintStream out) {
+ if (subject == "show") {
+ out.println("Static Configuration Flag:");
+ out.println(" ALLOW_ETH_ADDRESS_SELECTOR="
+ + SimpleFabricService.ALLOW_ETH_ADDRESS_SELECTOR);
+ out.println(" REACTIVE_SINGLE_TO_SINGLE="
+ + SimpleFabricService.REACTIVE_SINGLE_TO_SINGLE);
+ out.println(" REACTIVE_ALLOW_LINK_CP="
+ + SimpleFabricService.REACTIVE_ALLOW_LINK_CP);
+ out.println(" REACTIVE_HASHED_PATH_SELECTION="
+ + SimpleFabricService.REACTIVE_HASHED_PATH_SELECTION);
+ out.println(" REACTIVE_MATCH_IP_PROTO="
+ + SimpleFabricService.REACTIVE_MATCH_IP_PROTO);
+ out.println("");
+ out.println("SimpleFabricAppId:");
+ out.println(" " + getAppId());
+ out.println("");
+ out.println("l2Networks:");
+ for (L2Network l2Network : getL2Networks()) {
+ out.println(" " + l2Network);
+ }
+ out.println("");
+ out.println("ipSubnets:");
+ for (IpSubnet ipSubnet : getIpSubnets()) {
+ out.println(" " + ipSubnet);
+ }
+ out.println("");
+ out.println("borderRoutes:");
+ for (Route route : getBorderRoutes()) {
+ out.println(" " + route);
+ }
+ }
+ }
+
+ // Refresh action thread and notifier
+
+ private class InternalRefreshThread extends Thread {
+ public void run() {
+ while (true) {
+ boolean doRefreshMarked = false;
+ boolean doFlushMarked = false;
+ synchronized (refreshMonitor) {
+ if (!doRefresh && !doFlush) {
+ try {
+ refreshMonitor.wait(IDLE_INTERVAL_MSEC);
+ } catch (InterruptedException e) {
+ }
+ }
+ doRefreshMarked = doRefresh;
+ doRefresh = false;
+ doFlushMarked = doFlush;
+ doFlush = false;
+ }
+ if (doRefreshMarked) {
+ try {
+ refresh();
+ } catch (Exception e) {
+ log.warn("simple fabric refresh failed: exception={}", e);
+ }
+ }
+ if (doFlushMarked) {
+ try {
+ log.info("simple fabric flush execute");
+ process(new SimpleFabricEvent(SimpleFabricEvent.Type.SIMPLE_FABRIC_FLUSH, "flush"));
+ } catch (Exception e) {
+ log.warn("simple fabric flush failed: exception={}", e);
+ }
+ }
+ if (!doRefreshMarked && !doFlushMarked) {
+ try {
+ if (!refresh()) {
+ process(new SimpleFabricEvent(SimpleFabricEvent.Type.SIMPLE_FABRIC_IDLE, "idle"));
+ }
+ } catch (Exception e) {
+ log.warn("simple fabric idle failed: exception={}", e);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void triggerRefresh() {
+ synchronized (refreshMonitor) {
+ doRefresh = true;
+ refreshMonitor.notifyAll();
+ }
+ }
+
+ @Override
+ public void triggerFlush() {
+ synchronized (refreshMonitor) {
+ doFlush = true;
+ refreshMonitor.notifyAll();
+ }
+ }
+
+ // Service Listeners
+
+ private class InternalNetworkConfigListener implements NetworkConfigListener {
+ @Override
+ public void event(NetworkConfigEvent event) {
+ switch (event.type()) {
+ case CONFIG_REGISTERED:
+ case CONFIG_UNREGISTERED:
+ case CONFIG_ADDED:
+ case CONFIG_UPDATED:
+ case CONFIG_REMOVED:
+ if (event.configClass().equals(SimpleFabricConfig.class)) {
+ triggerRefresh();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private class InternalDeviceListener implements DeviceListener {
+ @Override
+ public void event(DeviceEvent event) {
+ switch (event.type()) {
+ case DEVICE_ADDED:
+ case DEVICE_AVAILABILITY_CHANGED:
+ case DEVICE_REMOVED:
+ case DEVICE_SUSPENDED:
+ case DEVICE_UPDATED:
+ case PORT_ADDED:
+ case PORT_REMOVED:
+ case PORT_UPDATED:
+ // case PORT_STATS_UPDATED: IGNORED
+ triggerRefresh();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private class InternalHostListener implements HostListener {
+ @Override
+ public void event(HostEvent event) {
+ Host host = event.subject();
+ Host prevHost = event.prevSubject();
+ switch (event.type()) {
+ case HOST_MOVED:
+ case HOST_REMOVED:
+ case HOST_ADDED:
+ case HOST_UPDATED:
+ triggerRefresh();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private class InternalInterfaceListener implements InterfaceListener {
+ @Override
+ public void event(InterfaceEvent event) {
+ Interface iface = event.subject();
+ Interface prevIface = event.prevSubject();
+ switch (event.type()) {
+ case INTERFACE_ADDED:
+ case INTERFACE_REMOVED:
+ case INTERFACE_UPDATED:
+ triggerRefresh();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+}
+
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricNeighbour.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricNeighbour.java
new file mode 100644
index 0000000..9d0b3fc
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricNeighbour.java
@@ -0,0 +1,240 @@
+/*
+ * 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.
+ */
+package org.onosproject.simplefabric;
+
+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.MacAddress;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.intf.InterfaceService;
+import org.onosproject.net.neighbour.NeighbourMessageContext;
+import org.onosproject.net.neighbour.NeighbourMessageHandler;
+import org.onosproject.net.neighbour.NeighbourResolutionService;
+import org.onosproject.net.Host;
+import org.onosproject.net.host.HostService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+
+/**
+ * Handles neighbour messages for on behalf of the L2 Network application. Handlers
+ * will be changed automatically by interface or network configuration events.
+ */
+@Component(immediate = true, enabled = false)
+public class SimpleFabricNeighbour {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+ protected ApplicationId appId;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostService hostService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected InterfaceService interfaceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected NeighbourResolutionService neighbourService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected SimpleFabricService simpleFabric;
+
+ private final InternalSimpleFabricListener simpleFabricListener =
+ new InternalSimpleFabricListener();
+
+ private L2NetworkNeighbourMessageHandler neighbourHandler =
+ new L2NetworkNeighbourMessageHandler();
+
+ private Set<Interface> monitoredInterfaces = new HashSet<>();
+
+ @Activate
+ public void activate() {
+ appId = simpleFabric.getAppId();
+ simpleFabric.addListener(simpleFabricListener);
+ refresh();
+ log.info("simple fabric neighbour started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ simpleFabric.removeListener(simpleFabricListener);
+ unregister();
+ monitoredInterfaces.clear();
+ log.info("simple fabric neighbour stoped");
+ }
+
+ /**
+ * Registers neighbour handler to all available interfaces.
+ */
+ protected void refresh() {
+ Set<Interface> interfaces = interfaceService.getInterfaces();
+ // check for new interfaces
+ for (Interface intf : interfaces) {
+ if (!monitoredInterfaces.contains(intf) && simpleFabric.isL2NetworkInterface(intf)) {
+ log.info("simple fabric neighbour register handler: {}", intf);
+ neighbourService.registerNeighbourHandler(intf, neighbourHandler, appId);
+ monitoredInterfaces.add(intf);
+ } else {
+ log.debug("simple fabric neighobur unknown interface: {}", intf);
+ }
+ }
+ // check for removed interfaces
+ Set<Interface> monitoredInterfacesToBeRemoved = new HashSet<>();
+ for (Interface intf : monitoredInterfaces) {
+ if (!interfaces.contains(intf)) {
+ log.info("simple fabric neighbour unregister handler: {}", intf);
+ neighbourService.unregisterNeighbourHandler(intf, neighbourHandler, appId);
+ monitoredInterfacesToBeRemoved.add(intf);
+ }
+ }
+ for (Interface intf : monitoredInterfacesToBeRemoved) {
+ monitoredInterfaces.remove(intf);
+ }
+ }
+
+ /**
+ * Unregisters neighbour handler to all available interfaces.
+ */
+ protected void unregister() {
+ log.info("simple fabric neighbour unregister handler");
+ neighbourService.unregisterNeighbourHandlers(appId);
+ }
+
+ /**
+ * Handles request messages.
+ *
+ * @param context the message context
+ */
+ protected void handleRequest(NeighbourMessageContext context) {
+ MacAddress mac = simpleFabric.getVMacForIp(context.target());
+ if (mac != null) {
+ log.trace("simple fabric neightbour request on virtualGatewayAddress {}; response to {} {} mac={}",
+ context.target(), context.inPort(), context.vlan(), mac);
+ context.reply(mac);
+ return;
+ }
+ // else forward to corresponding host
+
+ L2Network l2Network = simpleFabric.findL2Network(context.inPort(), context.vlan());
+ if (l2Network != null) {
+ int numForwards = 0;
+ if (!context.dstMac().isBroadcast() && !context.dstMac().isMulticast()) {
+ for (Host host : hostService.getHostsByMac(context.dstMac())) {
+ log.trace("simple fabric neightbour request forward unicast to {}", host.location());
+ context.forward(host.location()); // ASSUME: vlan is same
+ // TODO: may need to check host.location().time()
+ numForwards++;
+ }
+ if (numForwards > 0) {
+ return;
+ }
+ }
+ // else do broadcast to all host in the same l2 network
+ log.trace("simple fabric neightbour request forward broadcast: {} {}",
+ context.inPort(), context.vlan());
+ for (Interface iface : l2Network.interfaces()) {
+ if (!context.inPort().equals(iface.connectPoint())) {
+ log.trace("simple fabric forward neighbour request broadcast to {}", iface);
+ context.forward(iface);
+ }
+ }
+ } else {
+ log.warn("simple fabric neightbour request drop: {} {}",
+ context.inPort(), context.vlan());
+ context.drop();
+ }
+ }
+
+ /**
+ * Handles reply messages between VLAN tagged interfaces.
+ *
+ * @param context the message context
+ * @param hostService the host service
+ */
+ protected void handleReply(NeighbourMessageContext context,
+ HostService hostService) {
+ // Find target L2 Network, then reply to the host
+ L2Network l2Network = simpleFabric.findL2Network(context.inPort(), context.vlan());
+ if (l2Network != null) {
+ // TODO: need to check and update simpleFabric.L2Network
+ MacAddress mac = simpleFabric.getVMacForIp(context.target());
+ if (mac != null) {
+ log.trace("simple fabric neightbour response message to virtual gateway; drop: {} {} target={}",
+ context.inPort(), context.vlan(), context.target());
+ context.drop();
+ } else {
+ // forward reply to the hosts of the dstMac
+ Set<Host> hosts = hostService.getHostsByMac(context.dstMac());
+ log.trace("simple fabric neightbour response message forward: {} {} target={} -> {}",
+ context.inPort(), context.vlan(), context.target(), hosts);
+ hosts.stream()
+ .map(host -> simpleFabric.getHostInterface(host))
+ .filter(Objects::nonNull)
+ .forEach(context::forward);
+ }
+ } else {
+ // this might be happened when we remove an interface from L2 Network
+ // just ignore this message
+ log.warn("simple fabric neightbour response message drop for unknown l2Network: {} {}",
+ context.inPort(), context.vlan());
+ context.drop();
+ }
+ }
+
+ private class L2NetworkNeighbourMessageHandler implements NeighbourMessageHandler {
+ @Override
+ public void handleMessage(NeighbourMessageContext context,
+ HostService hostService) {
+ switch (context.type()) {
+ case REQUEST:
+ handleRequest(context);
+ break;
+ case REPLY:
+ handleReply(context, hostService);
+ break;
+ default:
+ log.warn("simple fabric neightor unknown context type: {}", context.type());
+ break;
+ }
+ }
+ }
+
+ private class InternalSimpleFabricListener implements SimpleFabricListener {
+ @Override
+ public void event(SimpleFabricEvent event) {
+ switch (event.type()) {
+ case SIMPLE_FABRIC_UPDATED:
+ refresh();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+}
+
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricReactiveRouting.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricReactiveRouting.java
new file mode 100644
index 0000000..10148c8
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricReactiveRouting.java
@@ -0,0 +1,977 @@
+/*
+ * 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.
+ */
+package org.onosproject.simplefabric;
+
+import com.google.common.collect.ImmutableList;
+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.EthType;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP;
+import org.onlab.packet.ICMP6;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.Ip6Prefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.intf.InterfaceService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.EncapsulationType;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+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.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.Host;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.intent.Constraint;
+import org.onosproject.net.intent.constraint.EncapsulationConstraint;
+import org.onosproject.net.intent.constraint.PartialFailureConstraint;
+import org.onosproject.net.intent.constraint.HashedPathSelectionConstraint;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.MultiPointToSinglePointIntent;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.net.Port;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.PrintStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+
+/**
+ * SimpleFabricReactiveRouting handles L3 Reactive Routing.
+ */
+@Component(immediate = true, enabled = false)
+public class SimpleFabricReactiveRouting {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+ private ApplicationId reactiveAppId;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected PacketService packetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected InterfaceService interfaceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected LinkService linkService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostService hostService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected IntentService intentService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected FlowRuleService flowRuleService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected SimpleFabricService simpleFabric;
+
+ private ImmutableList<Constraint> reactiveConstraints
+ = ImmutableList.of(new PartialFailureConstraint());
+ //= ImmutableList.of();
+ // NOTE: SHOULD NOT use HashedPathSelectionConstraint
+ // for unpredictable srcCp of Link appears as reactive packet traffic
+
+ private Set<FlowRule> interceptFlowRules = new HashSet<>();
+ private Set<Key> toBePurgedIntentKeys = new HashSet<>();
+ // NOTE: manage purged intents by key for intentService.getIntent() supports key only
+
+ private final InternalSimpleFabricListener simpleFabricListener = new InternalSimpleFabricListener();
+ private ReactiveRoutingProcessor processor = new ReactiveRoutingProcessor();
+
+ @Activate
+ public void activate() {
+ reactiveAppId = coreService.registerApplication(simpleFabric.REACTIVE_APP_ID);
+ log.info("simple fabric reactive routing starting with app id {}", reactiveAppId.toString());
+
+ // NOTE: may not clear at init for MIGHT generate pending_remove garbages
+ // use flush event from simple fabric cli command
+
+ if (simpleFabric.REACTIVE_HASHED_PATH_SELECTION) {
+ reactiveConstraints = ImmutableList.of(new PartialFailureConstraint(),
+ new HashedPathSelectionConstraint());
+ } else {
+ reactiveConstraints = ImmutableList.of(new PartialFailureConstraint());
+ }
+
+ processor = new ReactiveRoutingProcessor();
+ packetService.addProcessor(processor, PacketProcessor.director(2));
+ simpleFabric.addListener(simpleFabricListener);
+
+ registerIntercepts();
+ refreshIntercepts();
+
+ log.info("simple fabric reactive routing started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("simple fabric reactive routing stopping");
+
+ packetService.removeProcessor(processor);
+ simpleFabric.removeListener(simpleFabricListener);
+
+ withdrawIntercepts();
+
+ // NOTE: may not clear at init for MIGHT generate pending_remove garbages
+ // use flush event from simple fabric cli command
+
+ toBePurgedIntentKeys.clear();
+
+ flowRuleService.removeFlowRulesById(reactiveAppId);
+
+ processor = null;
+
+ log.info("simple fabric reactive routing stopped");
+ }
+
+ /**
+ * Request packet in via the PacketService.
+ */
+ private void registerIntercepts() {
+ // register default intercepts on packetService for broder routing intercepts
+
+ packetService.requestPackets(
+ DefaultTrafficSelector.builder().matchEthType(Ethernet.TYPE_IPV4).build(),
+ PacketPriority.REACTIVE, reactiveAppId);
+
+ if (simpleFabric.ALLOW_IPV6) {
+ packetService.requestPackets(
+ DefaultTrafficSelector.builder().matchEthType(Ethernet.TYPE_IPV6).build(),
+ PacketPriority.REACTIVE, reactiveAppId);
+ }
+
+ log.info("simple fabric reactive routing ip packet intercepts started");
+ }
+
+ /**
+ * Cancel request for packet in via PacketService.
+ */
+ private void withdrawIntercepts() {
+ // unregister default intercepts on packetService
+
+ packetService.cancelPackets(
+ DefaultTrafficSelector.builder().matchEthType(Ethernet.TYPE_IPV4).build(),
+ PacketPriority.REACTIVE, reactiveAppId);
+
+ if (simpleFabric.ALLOW_IPV6) {
+ packetService.cancelPackets(
+ DefaultTrafficSelector.builder().matchEthType(Ethernet.TYPE_IPV6).build(),
+ PacketPriority.REACTIVE, reactiveAppId);
+ }
+
+ log.info("simple fabric reactive routing ip packet intercepts stopped");
+ }
+
+ /**
+ * Refresh device flow rules for reative intercepts on local ipSubnets.
+ */
+ private void refreshIntercepts() {
+ Set<FlowRule> newInterceptFlowRules = new HashSet<>();
+ for (Device device : deviceService.getAvailableDevices()) {
+ for (IpSubnet subnet : simpleFabric.getIpSubnets()) {
+ newInterceptFlowRules.add(generateInterceptFlowRule(true, device.id(), subnet.ipPrefix()));
+ // check if this devices has the ipSubnet, then add ip broadcast flue rule
+ L2Network l2Network = simpleFabric.findL2Network(subnet.l2NetworkName());
+ if (l2Network != null && l2Network.contains(device.id())) {
+ newInterceptFlowRules.add(generateLocalSubnetIpBctFlowRule(device.id(), subnet.ipPrefix()));
+ }
+ // JUST FOR FLOW RULE TEST ONLY
+ //newInterceptFlowRules.add(generateTestFlowRule(device.id(), subnet.ipPrefix()));
+ }
+ for (Route route : simpleFabric.getBorderRoutes()) {
+ newInterceptFlowRules.add(generateInterceptFlowRule(false, device.id(), route.prefix()));
+ }
+ }
+
+ if (!newInterceptFlowRules.equals(interceptFlowRules)) {
+ // NOTE: DO NOT REMOVE INTERCEPT FLOW RULES FOR FAILED DEVICE FLOW UPDATE MIGHT BE BLOCKED
+ /*
+ interceptFlowRules.stream()
+ .filter(rule -> !newInterceptFlowRules.contains(rule))
+ .forEach(rule -> {
+ flowRuleService.removeFlowRules(rule);
+ log.info("simple fabric reactive routing remove intercept flow rule: {}", rule);
+ });
+ */
+ newInterceptFlowRules.stream()
+ .filter(rule -> !interceptFlowRules.contains(rule))
+ .forEach(rule -> {
+ flowRuleService.applyFlowRules(rule);
+ log.info("simple fabric reactive routing apply intercept flow rule: {}", rule);
+ });
+ interceptFlowRules = newInterceptFlowRules;
+ }
+ }
+
+ private FlowRule generateInterceptFlowRule(boolean isLocalSubnet, DeviceId deviceId, IpPrefix prefix) {
+ TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+ if (prefix.isIp4()) {
+ selector.matchEthType(Ethernet.TYPE_IPV4);
+ if (prefix.prefixLength() > 0) {
+ selector.matchIPDst(prefix);
+ }
+ } else {
+ selector.matchEthType(Ethernet.TYPE_IPV6);
+ if (prefix.prefixLength() > 0) {
+ selector.matchIPv6Dst(prefix);
+ }
+ }
+ FlowRule rule = DefaultFlowRule.builder()
+ .forDevice(deviceId)
+ .withPriority(reactivePriority(false, isLocalSubnet, prefix.prefixLength()))
+ .withSelector(selector.build())
+ .withTreatment(DefaultTrafficTreatment.builder().punt().build())
+ .fromApp(reactiveAppId)
+ .makePermanent()
+ .forTable(0).build();
+ return rule;
+ }
+
+ private FlowRule generateLocalSubnetIpBctFlowRule(DeviceId deviceId, IpPrefix prefix) {
+ TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+ IpPrefix bctPrefix;
+ if (prefix.isIp4()) {
+ bctPrefix = Ip4Prefix.valueOf(prefix.getIp4Prefix().address().toInt() |
+ ~Ip4Address.makeMaskPrefix(prefix.prefixLength()).toInt(),
+ Ip4Address.BIT_LENGTH);
+ selector.matchEthType(Ethernet.TYPE_IPV4);
+ selector.matchIPDst(bctPrefix);
+ } else {
+ byte[] p = prefix.getIp6Prefix().address().toOctets();
+ byte[] m = Ip6Address.makeMaskPrefix(prefix.prefixLength()).toOctets();
+ for (int i = 0; i < p.length; i++) {
+ p[i] |= ~m[i];
+ }
+ bctPrefix = Ip6Prefix.valueOf(p, Ip6Address.BIT_LENGTH);
+ selector.matchEthType(Ethernet.TYPE_IPV6);
+ selector.matchIPv6Dst(bctPrefix);
+ }
+ TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+ Set<ConnectPoint> newEgressPoints = new HashSet<>();
+ for (Port port : deviceService.getPorts(deviceId)) {
+ treatment.setOutput(port.number());
+ }
+ FlowRule rule = DefaultFlowRule.builder()
+ .forDevice(deviceId)
+ .withPriority(reactivePriority(true, true, bctPrefix.prefixLength()))
+ .withSelector(selector.build())
+ .withTreatment(treatment.build())
+ .fromApp(reactiveAppId)
+ .makePermanent()
+ .forTable(0).build();
+ return rule;
+ }
+
+ /**
+ * Refresh routes by examining network resource status.
+ */
+ private void refreshRouteIntents() {
+ for (Intent entry : intentService.getIntents()) {
+ if (!reactiveAppId.equals(entry.appId())) {
+ continue;
+ }
+
+ MultiPointToSinglePointIntent intent = (MultiPointToSinglePointIntent) entry;
+
+ if (!intentService.isLocal(intent.key())) {
+ if (toBePurgedIntentKeys.contains(intent.key())) {
+ toBePurgedIntentKeys.remove(intent.key()); // clear non local intent
+ }
+ continue;
+ }
+
+ try {
+ switch (intentService.getIntentState(intent.key())) {
+ //case FAILED: // failed intent is not auto removed
+ case WITHDRAWN:
+ log.warn("intent found failed or withdrawn; "
+ + "remove and try to purge intent: key={}", intent.key());
+ // purge intents here without withdraw
+ intentService.purge(intentService.getIntent(intent.key()));
+ toBePurgedIntentKeys.add(intent.key());
+ continue;
+ default: // no action
+ break;
+ }
+ } catch (Exception e) {
+ log.warn("intent status lookup failed: error={}", e);
+ continue; // this intent seems invalid; no action
+ }
+
+ // dummy loop to break on remove cases
+ if (!deviceService.isAvailable(intent.egressPoint().deviceId())) {
+ log.info("refresh route intents; remove intent for no device: key={}", intent.key());
+ intentService.withdraw(intentService.getIntent(intent.key()));
+ toBePurgedIntentKeys.add(intent.key());
+ continue;
+ }
+ if (!(simpleFabric.findL2Network(intent.egressPoint(), VlanId.NONE) != null ||
+ (simpleFabric.REACTIVE_ALLOW_LINK_CP &&
+ !linkService.getEgressLinks(intent.egressPoint()).isEmpty()))) {
+ log.info("refresh route intents; remove intent for egress point not available: key={}", intent.key());
+ intentService.withdraw(intentService.getIntent(intent.key()));
+ toBePurgedIntentKeys.add(intent.key());
+ continue;
+ }
+
+ // MAY NEED TO CHECK: intent.egressPoint and intent.treatment's dstMac is valid against hosts
+ if (simpleFabric.REACTIVE_SINGLE_TO_SINGLE && !simpleFabric.REACTIVE_ALLOW_LINK_CP) {
+ // single path intent only; no need to check ingress points
+ continue;
+ }
+
+ Set<ConnectPoint> newIngressPoints = new HashSet<>();
+ boolean ingressPointChanged = false;
+ for (ConnectPoint cp : intent.ingressPoints()) {
+ if (deviceService.isAvailable(cp.deviceId()) &&
+ (simpleFabric.findL2Network(cp, VlanId.NONE) != null ||
+ (simpleFabric.REACTIVE_ALLOW_LINK_CP &&
+ !linkService.getIngressLinks(cp).isEmpty()))) {
+ newIngressPoints.add(cp);
+ } else {
+ log.info("refresh route ingress cp of "
+ + "not in 2Networks nor links: {}", cp);
+ ingressPointChanged = true;
+ }
+ }
+ if (newIngressPoints.isEmpty()) {
+ log.info("refresh route intents; "
+ + "remove intent for no ingress nor egress point available: key={}", intent.key());
+ intentService.withdraw(intentService.getIntent(intent.key()));
+ toBePurgedIntentKeys.add(intent.key());
+ continue;
+ }
+ // update ingress points
+ if (ingressPointChanged) {
+ MultiPointToSinglePointIntent updatedIntent =
+ MultiPointToSinglePointIntent.builder()
+ .appId(reactiveAppId)
+ .key(intent.key())
+ .selector(intent.selector())
+ .treatment(intent.treatment())
+ .ingressPoints(newIngressPoints)
+ .egressPoint(intent.egressPoint())
+ .priority(intent.priority())
+ .constraints(intent.constraints())
+ .build();
+ log.info("refresh route update intent: key={} updatedIntent={}",
+ intent.key(), updatedIntent);
+ toBePurgedIntentKeys.remove(intent.key()); // may remove from old purged entry
+ intentService.submit(updatedIntent);
+ }
+ }
+ }
+
+ private void checkIntentsPurge() {
+ // check intents to be purge
+ if (!toBePurgedIntentKeys.isEmpty()) {
+ Set<Key> removeKeys = new HashSet<>();
+ for (Key key : toBePurgedIntentKeys) {
+ if (!intentService.isLocal(key)) {
+ removeKeys.add(key);
+ continue;
+ }
+ Intent intentToPurge = intentService.getIntent(key);
+ if (intentToPurge == null) {
+ log.info("purged intent: key={}", key);
+ removeKeys.add(key);
+ } else {
+ switch (intentService.getIntentState(key)) {
+ // case FAILED: // not auto removed
+ case WITHDRAWN:
+ log.info("try to purge intent: key={}", key);
+ intentService.purge(intentToPurge);
+ break;
+ case INSTALL_REQ:
+ case INSTALLED:
+ case INSTALLING:
+ case RECOMPILING:
+ case COMPILING:
+ log.warn("not to purge for active intent: key={}", key);
+ removeKeys.add(key);
+ break;
+ case WITHDRAW_REQ:
+ case WITHDRAWING:
+ case PURGE_REQ:
+ case CORRUPT:
+ default:
+ // no action
+ break;
+ }
+ }
+ }
+ toBePurgedIntentKeys.removeAll(removeKeys);
+ }
+ }
+
+ public void withdrawAllReactiveIntents() {
+ // check all intents of this app
+ // NOTE: cli calls are handling within the cli called node only; so should not user inents.isLocal()
+ Set<Intent> myIntents = new HashSet<>();
+ for (Intent intent : intentService.getIntents()) {
+ if (reactiveAppId.equals(intent.appId())) {
+ myIntents.add(intent);
+ }
+ }
+ // withdraw all my intents
+ for (Intent intent : myIntents) {
+ switch (intentService.getIntentState(intent.key())) {
+ case FAILED:
+ intentService.purge(intent);
+ toBePurgedIntentKeys.add(intent.key());
+ break;
+ case WITHDRAWN:
+ intentService.purge(intent);
+ toBePurgedIntentKeys.add(intent.key());
+ break;
+ case INSTALL_REQ:
+ case INSTALLED:
+ case INSTALLING:
+ case RECOMPILING:
+ case COMPILING:
+ intentService.withdraw(intent);
+ toBePurgedIntentKeys.add(intent.key());
+ break;
+ case WITHDRAW_REQ:
+ case WITHDRAWING:
+ toBePurgedIntentKeys.add(intent.key());
+ break;
+ case PURGE_REQ:
+ case CORRUPT:
+ default:
+ // no action
+ break;
+ }
+ }
+ }
+
+ /**
+ * Reactive Packet Handling.
+ */
+ private class ReactiveRoutingProcessor implements PacketProcessor {
+ @Override
+ public void process(PacketContext context) {
+ InboundPacket pkt = context.inPacket();
+ Ethernet ethPkt = pkt.parsed();
+ if (ethPkt == null) {
+ return;
+ }
+ ConnectPoint srcCp = pkt.receivedFrom();
+ IpAddress srcIp;
+ IpAddress dstIp;
+ byte ipProto = 0; /* 0 or tcp, udp */
+
+ switch (EthType.EtherType.lookup(ethPkt.getEtherType())) {
+ case IPV4:
+ IPv4 ipv4Packet = (IPv4) ethPkt.getPayload();
+ srcIp = IpAddress.valueOf(ipv4Packet.getSourceAddress());
+ dstIp = IpAddress.valueOf(ipv4Packet.getDestinationAddress());
+ ipProto = ipv4Packet.getProtocol();
+ break;
+ case IPV6:
+ IPv6 ipv6Packet = (IPv6) ethPkt.getPayload();
+ srcIp = IpAddress.valueOf(IpAddress.Version.INET6, ipv6Packet.getSourceAddress());
+ dstIp = IpAddress.valueOf(IpAddress.Version.INET6, ipv6Packet.getDestinationAddress());
+ ipProto = ipv6Packet.getNextHeader();
+ break;
+ default:
+ return; // ignore unknow ether type packets
+ }
+ if (ipProto != 6 && ipProto != 17) {
+ ipProto = 0; /* handle special for TCP and UDP only */
+ }
+
+ if (!checkVirtualGatewayIpPacket(pkt, srcIp, dstIp)) {
+ ipPacketReactiveProcessor(context, ethPkt, srcCp, srcIp, dstIp, ipProto);
+ // TODO: add ReactiveRouting for dstIp to srcIp with discovered egressCp as srcCp
+ }
+ }
+ }
+
+ /**
+ * handle Packet with dstIp=virtualGatewayIpAddresses.
+ * returns true(handled) or false(not for virtual gateway)
+ */
+ private boolean checkVirtualGatewayIpPacket(InboundPacket pkt, IpAddress srcIp, IpAddress dstIp) {
+ Ethernet ethPkt = pkt.parsed(); // assume valid
+
+ MacAddress mac = simpleFabric.getVMacForIp(dstIp);
+ if (mac == null || !ethPkt.getDestinationMAC().equals(mac)) {
+ return false;
+ } else if (dstIp.isIp4()) {
+ IPv4 ipv4Packet = (IPv4) ethPkt.getPayload();
+ if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_ICMP) {
+ ICMP icmpPacket = (ICMP) ipv4Packet.getPayload();
+
+ if (icmpPacket.getIcmpType() == ICMP.TYPE_ECHO_REQUEST) {
+ log.info("IPV4 ICMP ECHO request to virtual gateway: "
+ + "srcIp={} dstIp={} proto={}", srcIp, dstIp, ipv4Packet.getProtocol());
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(pkt.receivedFrom().port()).build();
+ OutboundPacket packet =
+ new DefaultOutboundPacket(pkt.receivedFrom().deviceId(), treatment,
+ ByteBuffer.wrap(icmpPacket.buildIcmpReply(pkt.parsed()).serialize()));
+ packetService.emit(packet);
+ return true;
+ }
+ }
+ log.warn("IPV4 packet to virtual gateway dropped: "
+ + "srcIp={} dstIp={} proto={}", srcIp, dstIp, ipv4Packet.getProtocol());
+ return true;
+
+ } else if (dstIp.isIp6()) {
+ // TODO: not tested yet (2017-07-20)
+ IPv6 ipv6Packet = (IPv6) ethPkt.getPayload();
+ if (ipv6Packet.getNextHeader() == IPv6.PROTOCOL_ICMP6) {
+ ICMP6 icmp6Packet = (ICMP6) ipv6Packet.getPayload();
+
+ if (icmp6Packet.getIcmpType() == ICMP6.ECHO_REQUEST) {
+ log.info("IPV6 ICMP6 ECHO request to virtual gateway: srcIp={} dstIp={} nextHeader={}",
+ srcIp, dstIp, ipv6Packet.getNextHeader());
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(pkt.receivedFrom().port()).build();
+ OutboundPacket packet =
+ new DefaultOutboundPacket(pkt.receivedFrom().deviceId(), treatment,
+ ByteBuffer.wrap(icmp6Packet.buildIcmp6Reply(pkt.parsed()).serialize()));
+ packetService.emit(packet);
+ return true;
+ }
+ }
+ log.warn("IPV6 packet to virtual gateway dropped: srcIp={} dstIp={} nextHeader={}",
+ srcIp, dstIp, ipv6Packet.getNextHeader());
+ return true;
+
+ }
+ return false; // unknown traffic
+ }
+
+ /**
+ * Routes packet reactively.
+ */
+ private void ipPacketReactiveProcessor(PacketContext context, Ethernet ethPkt, ConnectPoint srcCp,
+ IpAddress srcIp, IpAddress dstIp, byte ipProto) {
+ /* check reactive handling and forward packet */
+ log.trace("ip packet: srcCp={} srcIp={} dstIp={} ipProto={}",
+ srcCp, srcIp, dstIp, ipProto);
+
+ EncapsulationType encap = EncapsulationType.NONE;
+
+ // prefix and nextHop for local Subnet
+ IpPrefix srcPrefix = srcIp.toIpPrefix();
+ IpPrefix dstPrefix = dstIp.toIpPrefix();
+ IpAddress srcNextHop = srcIp;
+ IpAddress dstNextHop = dstIp;
+ MacAddress treatmentSrcMac = ethPkt.getDestinationMAC();
+ int borderRoutePrefixLength = 0;
+ boolean isLocalSubnet = true;
+ boolean updateMac = simpleFabric.isVMac(ethPkt.getDestinationMAC());
+
+ // check subnet local or route
+ IpSubnet srcSubnet = simpleFabric.findIpSubnet(srcIp);
+ if (srcSubnet == null) {
+ Route route = simpleFabric.findBorderRoute(srcIp);
+ if (route == null) {
+ log.warn("route unknown: srcIp={}", srcIp);
+ return;
+ }
+ srcPrefix = route.prefix();
+ srcNextHop = route.nextHop();
+ borderRoutePrefixLength = route.prefix().prefixLength();
+ isLocalSubnet = false;
+ }
+ IpSubnet dstSubnet = simpleFabric.findIpSubnet(dstIp);
+ if (dstSubnet == null) {
+ Route route = simpleFabric.findBorderRoute(dstIp);
+ if (route == null) {
+ log.warn("route unknown: dstIp={}", dstIp);
+ return;
+ }
+ dstPrefix = route.prefix();
+ dstNextHop = route.nextHop();
+ borderRoutePrefixLength = route.prefix().prefixLength();
+ isLocalSubnet = false;
+ }
+
+ if (dstSubnet != null) {
+ // destination is local subnet ip
+ if (SimpleFabricService.ALLOW_ETH_ADDRESS_SELECTOR && dstSubnet.equals(srcSubnet)) {
+ // NOTE: if ALLOW_ETH_ADDRESS_SELECTOR=false; l2Forward is always false
+ L2Network l2Network = simpleFabric.findL2Network(dstSubnet.l2NetworkName());
+ treatmentSrcMac = ethPkt.getSourceMAC();
+ if (l2Network != null && l2Network.l2Forward()) {
+ // NOTE: no reactive route action but do forward packet for L2Forward do not handle packet
+ // update mac only if dstMac is virtualGatewayMac, else assume valid mac already for the l2 network
+ log.info("LOCAL FORWARD ONLY: "
+ + "srcCp={} srcIp={} dstIp={} srcMac={} dstMac={} vlanId={} ipProto={} updateMac={}",
+ context.inPacket().receivedFrom(),
+ srcIp, dstIp, ethPkt.getSourceMAC(), ethPkt.getDestinationMAC(),
+ ethPkt.getVlanID(), ipProto, updateMac);
+ forwardPacketToDstIp(context, dstIp, treatmentSrcMac, updateMac);
+ return;
+ }
+ }
+ encap = dstSubnet.encapsulation();
+ if (encap == EncapsulationType.NONE && srcSubnet != null) {
+ encap = srcSubnet.encapsulation();
+ }
+ } else {
+ // destination is external network
+ if (srcSubnet == null) {
+ // both are externel network
+ log.warn("INVALID PACKET: srcIp and dstIp are both NON-LOCAL: "
+ + "srcCP={} srcIp={} dstIp={} srcMac={} dstMac={} vlanId={} ipProto={} updateMac={}",
+ context.inPacket().receivedFrom(),
+ srcIp, dstIp, ethPkt.getSourceMAC(), ethPkt.getDestinationMAC(),
+ ethPkt.getVlanID(), ipProto, updateMac);
+ return;
+ }
+ encap = srcSubnet.encapsulation();
+ }
+
+ log.info("REGI AND FORWARD: "
+ + "srcCP={} srcIp={} dstIp={} srcMac={} dstMac={} vlanId={} ipProto={} updateMac={}",
+ context.inPacket().receivedFrom(),
+ srcIp, dstIp, ethPkt.getSourceMAC(), ethPkt.getDestinationMAC(),
+ ethPkt.getVlanID(), ipProto, updateMac);
+ setUpConnectivity(srcCp, ipProto, srcPrefix, dstPrefix, dstNextHop, treatmentSrcMac, encap, updateMac,
+ isLocalSubnet, borderRoutePrefixLength);
+ forwardPacketToDstIp(context, dstNextHop, treatmentSrcMac, updateMac);
+ }
+
+ /**
+ * Emits the specified packet onto the network.
+ */
+ private void forwardPacketToDstIp(PacketContext context, IpAddress nextHopIp,
+ MacAddress srcMac, boolean updateMac) {
+ Set<Host> hosts = hostService.getHostsByIp(nextHopIp);
+ Host dstHost;
+ if (!hosts.isEmpty()) {
+ dstHost = hosts.iterator().next();
+ } else {
+ // NOTE: hostService.requestMac(nextHopIp); NOT IMPLEMENTED in ONOS HostManager.java; do it myself
+ log.warn("forward packet nextHopIp host_mac unknown: nextHopIp={}", nextHopIp);
+ hostService.startMonitoringIp(nextHopIp);
+ simpleFabric.requestMac(nextHopIp);
+ // CONSIDER: make flood on all port of the dstHost's L2Network
+ return;
+ }
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(dstHost.location().port()).build();
+ OutboundPacket outPacket;
+ if (updateMac) {
+ // NOTE: eth address update by treatment is NOT applied, so update mac myself
+ outPacket = new DefaultOutboundPacket(dstHost.location().deviceId(), treatment,
+ ByteBuffer.wrap(context.inPacket().parsed()
+ .setSourceMACAddress(srcMac)
+ .setDestinationMACAddress(dstHost.mac()).serialize()));
+ } else {
+ outPacket = new DefaultOutboundPacket(dstHost.location().deviceId(), treatment,
+ context.inPacket().unparsed());
+ }
+ // be quiet on normal situation
+ log.info("forward packet: nextHopIP={} srcCP={} dstCP={}",
+ nextHopIp, context.inPacket().receivedFrom(), dstHost.location());
+ packetService.emit(outPacket);
+ }
+
+ /**
+ * Update intents for connectivity.
+ *
+ * ToHost: dstPrefix = dstHostIp.toIpPrefix(), nextHopIp = destHostIp
+ * ToInternet: dstPrefix = route.prefix(), nextHopIp = route.nextHopIp
+ * returns intent submited or not
+ */
+ private boolean setUpConnectivity(ConnectPoint srcCp, byte ipProto, IpPrefix srcPrefix, IpPrefix dstPrefix,
+ IpAddress nextHopIp, MacAddress treatmentSrcMac,
+ EncapsulationType encap, boolean updateMac,
+ boolean isLocalSubnet, int borderRoutePrefixLength) {
+ Key key;
+ String keyProtoTag = "";
+ if (simpleFabric.REACTIVE_MATCH_IP_PROTO) {
+ keyProtoTag = "-p" + ipProto;
+ }
+ if (simpleFabric.REACTIVE_SINGLE_TO_SINGLE) {
+ key = Key.of(srcPrefix.toString() + "-to-" + dstPrefix.toString() + keyProtoTag, reactiveAppId);
+ } else {
+ key = Key.of(dstPrefix.toString() + keyProtoTag, reactiveAppId);
+ }
+
+ if (!(simpleFabric.findL2Network(srcCp, VlanId.NONE) != null ||
+ (simpleFabric.REACTIVE_ALLOW_LINK_CP && !linkService.getIngressLinks(srcCp).isEmpty()))) {
+ log.warn("NO REGI for srcCp not in L2Network; srcCp={} srcPrefix={} dstPrefix={} nextHopIp={}",
+ srcCp, srcPrefix, dstPrefix, nextHopIp);
+ return false;
+ }
+
+ MacAddress nextHopMac = null;
+ ConnectPoint egressPoint = null;
+ for (Host host : hostService.getHostsByIp(nextHopIp)) {
+ if (host.mac() != null) {
+ nextHopMac = host.mac();
+ egressPoint = host.location();
+ break;
+ }
+ }
+ if (nextHopMac == null || egressPoint == null) {
+ log.info("NO REGI for unknown nextHop Cp and Mac: srcPrefix={} dstPrefix={} nextHopIp={}",
+ srcPrefix, dstPrefix, nextHopIp);
+ hostService.startMonitoringIp(nextHopIp);
+ simpleFabric.requestMac(nextHopIp);
+ return false;
+ }
+ TrafficTreatment treatment;
+ if (updateMac && simpleFabric.ALLOW_ETH_ADDRESS_SELECTOR) {
+ treatment = generateSetMacTreatment(nextHopMac, treatmentSrcMac);
+ } else {
+ treatment = DefaultTrafficTreatment.builder().build();
+ }
+
+ TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+ if (dstPrefix.isIp4()) {
+ selector.matchEthType(Ethernet.TYPE_IPV4);
+ if (simpleFabric.REACTIVE_SINGLE_TO_SINGLE && srcPrefix.prefixLength() > 0) {
+ selector.matchIPSrc(srcPrefix);
+ }
+ if (dstPrefix.prefixLength() > 0) {
+ selector.matchIPDst(dstPrefix);
+ }
+ if (ipProto != 0 && simpleFabric.REACTIVE_MATCH_IP_PROTO) {
+ selector.matchIPProtocol(ipProto);
+ }
+ } else {
+ selector.matchEthType(Ethernet.TYPE_IPV6);
+ if (simpleFabric.REACTIVE_SINGLE_TO_SINGLE && srcPrefix.prefixLength() > 0) {
+ selector.matchIPv6Src(srcPrefix);
+ }
+ if (dstPrefix.prefixLength() > 0) {
+ selector.matchIPv6Dst(dstPrefix);
+ }
+ if (ipProto != 0 && simpleFabric.REACTIVE_MATCH_IP_PROTO) {
+ selector.matchIPProtocol(ipProto);
+ }
+ }
+
+ // check and merge already existing ingress points
+ Set<ConnectPoint> ingressPoints = new HashSet<>();
+ MultiPointToSinglePointIntent existingIntent = (MultiPointToSinglePointIntent) intentService.getIntent(key);
+ if (existingIntent != null) {
+ ingressPoints.addAll(existingIntent.ingressPoints());
+ if (!ingressPoints.add(srcCp) // alread exists and dst not changed
+ && egressPoint.equals(existingIntent.egressPoint())
+ && treatment.equals(existingIntent.treatment())) {
+ log.warn("srcCP is already in mp2p intent: srcPrefix={} dstPrefix={} srcCp={}",
+ srcPrefix, dstPrefix, srcCp);
+ return false;
+ }
+ log.info("update mp2p intent: srcPrefix={} dstPrefix={} srcCp={}",
+ srcPrefix, dstPrefix, srcCp);
+ } else {
+ log.info("create mp2p intent: srcPrefix={} dstPrefix={} srcCp={}",
+ srcPrefix, dstPrefix, srcCp);
+ ingressPoints.add(srcCp);
+ }
+
+ // priority for forwarding case
+ int priority = reactivePriority(true, isLocalSubnet, borderRoutePrefixLength);
+
+ MultiPointToSinglePointIntent newIntent = MultiPointToSinglePointIntent.builder()
+ .key(key)
+ .appId(reactiveAppId)
+ .selector(selector.build())
+ .treatment(treatment)
+ .ingressPoints(ingressPoints)
+ .egressPoint(egressPoint)
+ .priority(priority)
+ .constraints(buildConstraints(reactiveConstraints, encap))
+ .build();
+ log.info("submmit mp2p intent: srcPrefix={} dstPrefix={} srcCp={} "
+ + "newIntent={} nextHopIp={} nextHopMac={} priority={}",
+ srcPrefix, dstPrefix, ingressPoints, newIntent, nextHopIp, nextHopMac, priority);
+ toBePurgedIntentKeys.remove(newIntent.key());
+ intentService.submit(newIntent);
+ return true;
+ }
+
+ // generate treatment to target
+ private TrafficTreatment generateSetMacTreatment(MacAddress dstMac, MacAddress srcMac) {
+ return DefaultTrafficTreatment.builder()
+ // NOTE: Cisco Switch requires both src and dst mac set
+ .setEthDst(dstMac)
+ .setEthSrc(srcMac)
+ .build();
+ }
+
+ // monitor border peers for routeService lookup to be effective
+ private void monitorBorderPeers() {
+ for (Route route : simpleFabric.getBorderRoutes()) {
+ hostService.startMonitoringIp(route.nextHop());
+ simpleFabric.requestMac(route.nextHop());
+ }
+ }
+
+ // priority calculator
+ private int reactivePriority(boolean isForward, boolean isLocalSubnet, int borderRoutePrefixLength) {
+ if (isLocalSubnet) { // localSubnet <-> localSubnet
+ if (isForward) {
+ return simpleFabric.PRI_REACTIVE_LOCAL_FORWARD;
+ } else { // isInterncept
+ return simpleFabric.PRI_REACTIVE_LOCAL_INTERCEPT;
+ }
+ } else { // isBorder: localSubnet <-> boarderRouteGateway
+ int offset;
+ if (isForward) {
+ offset = simpleFabric.PRI_REACTIVE_BORDER_FORWARD;
+ } else { // isIntercept
+ offset = simpleFabric.PRI_REACTIVE_BORDER_INTERCEPT;
+ }
+ return simpleFabric.PRI_REACTIVE_BORDER_BASE
+ + borderRoutePrefixLength * simpleFabric.PRI_REACTIVE_BORDER_STEP + offset;
+ }
+ }
+
+ // constraints generator
+ private List<Constraint> buildConstraints(List<Constraint> constraints, EncapsulationType encap) {
+ if (!encap.equals(EncapsulationType.NONE)) {
+ List<Constraint> newConstraints = new ArrayList<>(constraints);
+ constraints.stream()
+ .filter(c -> c instanceof EncapsulationConstraint)
+ .forEach(newConstraints::remove);
+ newConstraints.add(new EncapsulationConstraint(encap));
+ return ImmutableList.copyOf(newConstraints);
+ }
+ return constraints;
+ }
+
+ // Dump Cli Handler
+ private void dump(String subject, PrintStream out) {
+ if (subject == "intents") {
+ out.println("Reactive Routing Route Intents:\n");
+ for (Intent entry : intentService.getIntents()) {
+ if (reactiveAppId.equals(entry.appId())) {
+ MultiPointToSinglePointIntent intent = (MultiPointToSinglePointIntent) entry;
+ out.println(" " + intent.key().toString()
+ + " to " + intent.egressPoint().toString()
+ + " set " + intent.treatment().immediate().toString()
+ + " from " + intent.ingressPoints().toString());
+ }
+ }
+ out.println("");
+
+ out.println("Reactive Routing Intercept Flow Rules:\n");
+ List<FlowRule> rules = new ArrayList(interceptFlowRules);
+ Collections.sort(rules, new Comparator<FlowRule>() {
+ @Override
+ public int compare(FlowRule a, FlowRule b) {
+ int r = a.deviceId().toString().compareTo(b.deviceId().toString());
+ return (r != 0) ? r : Integer.compare(b.priority(), a.priority()); // descending on priority
+ }
+ });
+ for (FlowRule rule : rules) {
+ out.println(" device=" + rule.deviceId().toString()
+ + " priority=" + rule.priority()
+ + " selector=" + rule.selector().criteria().toString());
+ }
+ out.println("");
+ out.println("Reactive Routing Intents to Be Purged:\n");
+ for (Key key: toBePurgedIntentKeys) {
+ out.println(" " + key.toString());
+ }
+ out.println("");
+
+ } else if (subject == "reactive-intents") {
+ for (Intent entry : intentService.getIntents()) {
+ if (reactiveAppId.equals(entry.appId())) {
+ MultiPointToSinglePointIntent intent = (MultiPointToSinglePointIntent) entry;
+ out.println(intent.key().toString()
+ + " to " + intent.egressPoint().toString()
+ + " set " + intent.treatment().immediate().toString()
+ + " from " + intent.ingressPoints().toString());
+ }
+ }
+ }
+ }
+
+ // Listener
+ private class InternalSimpleFabricListener implements SimpleFabricListener {
+ @Override
+ public void event(SimpleFabricEvent event) {
+ switch (event.type()) {
+ case SIMPLE_FABRIC_UPDATED:
+ refreshIntercepts();
+ refreshRouteIntents();
+ checkIntentsPurge();
+ break;
+ case SIMPLE_FABRIC_FLUSH:
+ withdrawAllReactiveIntents();
+ checkIntentsPurge();
+ break;
+ case SIMPLE_FABRIC_IDLE:
+ refreshIntercepts();
+ refreshRouteIntents();
+ checkIntentsPurge();
+ monitorBorderPeers();
+ break;
+ case SIMPLE_FABRIC_DUMP:
+ dump(event.subject(), event.out());
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+}
+
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricService.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricService.java
new file mode 100644
index 0000000..c27a828
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/SimpleFabricService.java
@@ -0,0 +1,206 @@
+/*
+ * 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.
+ */
+package org.onosproject.simplefabric;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.event.ListenerService;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Host;
+
+import java.io.OutputStream;
+import java.util.Set;
+import java.util.Collection;
+
+/**
+ * Provides information about the routing configuration.
+ */
+public interface SimpleFabricService
+ extends ListenerService<SimpleFabricEvent, SimpleFabricListener> {
+
+ // App symbols
+ static final String APP_ID = "org.onosproject.simplefabric";
+ static final String L2FORWARD_APP_ID = "org.onosproject.simplefabric.l2forward";
+ static final String REACTIVE_APP_ID = "org.onosproject.simplefabric.reactive";
+
+ // Priority for l2NetworkRouting: L2NETWORK_UNICAST or L2NETWORK_BROADCAST
+ static final int PRI_L2NETWORK_UNICAST = 601;
+ static final int PRI_L2NETWORK_BROADCAST = 600;
+
+ // Reactive Routing within Local Subnets
+ // ASSUME: local subnets NEVER overlaps each other
+ static final int PRI_REACTIVE_LOCAL_FORWARD = 501;
+ static final int PRI_REACTIVE_LOCAL_INTERCEPT = 500;
+ // Reactive Routing for Border Routes with local subnet
+ // Priority: REACTIVE_BROUTE_BASE + routeIpPrefix * REACTIVE_BROUTE_STEP
+ // + REACTIVE_BROUTE_FORWARD or REACTIVE_BROUTE_INTERCEPT
+ static final int PRI_REACTIVE_BORDER_BASE = 100;
+ static final int PRI_REACTIVE_BORDER_STEP = 2;
+ static final int PRI_REACTIVE_BORDER_FORWARD = 1;
+ static final int PRI_REACTIVE_BORDER_INTERCEPT = 0;
+
+ // Simple fabric event related timers
+ static final long IDLE_INTERVAL_MSEC = 5000;
+
+ // Feature control parameters
+ static final boolean ALLOW_IPV6 = false;
+ static final boolean ALLOW_ETH_ADDRESS_SELECTOR = true;
+ static final boolean REACTIVE_SINGLE_TO_SINGLE = false;
+ static final boolean REACTIVE_ALLOW_LINK_CP = false; // MUST BE false (yjlee, 2017-10-18)
+ static final boolean REACTIVE_HASHED_PATH_SELECTION = false;
+ static final boolean REACTIVE_MATCH_IP_PROTO = false;
+
+ /**
+ * Gets appId.
+ *
+ * @return appId of simple fabric app
+ */
+ ApplicationId getAppId();
+
+ /**
+ * Gets all the l2Networks.
+ *
+ * @return all the l2Networks
+ */
+ Collection<L2Network> getL2Networks();
+
+ /**
+ * Retrieves the entire set of ipSubnets configuration.
+ *
+ * @return all the ipSubnets
+ */
+ Set<IpSubnet> getIpSubnets();
+
+ /**
+ * Retrieves the entire set of static routes to outer networks.
+ *
+ * @return the set of static routes to outer networks.
+ */
+ Set<Route> getBorderRoutes();
+
+ /**
+ * Gets Virtual Gateway Mac Address for Local Subnet Virtual Gateway Ip.
+ *
+ * @param ip the ip to check for Virtual Gateway Ip
+ * @return mac address of virtual gateway
+ */
+ MacAddress getVMacForIp(IpAddress ip);
+
+ /**
+ * Evaluates whether a mac is of Virtual Gateway Mac Addresses.
+ *
+ * @param mac the MacAddress to evaluate
+ * @return true if the mac is of any Vitrual Gateway Mac Address of ipSubnets
+ */
+ boolean isVMac(MacAddress mac);
+
+ /**
+ * Evaluates whether an Interface belongs to l2Networks.
+ *
+ * @param intf the interface to evaluate
+ * @return true if the inteface belongs to l2Networks configed, otherwise false
+ */
+ boolean isL2NetworkInterface(Interface intf);
+
+ /**
+ * Finds the L2 Network with given port and vlanId.
+ *
+ * @param port the port to be matched
+ * @param vlanId the vlanId to be matched
+ * @return the L2 Network for specific port and vlanId or null
+ */
+ L2Network findL2Network(ConnectPoint port, VlanId vlanId);
+
+ /**
+ * Finds the L2 Network of the name.
+ *
+ * @param name the name to be matched
+ * @return the L2 Network for specific name
+ */
+ L2Network findL2Network(String name);
+
+ /**
+ * Finds the IpSubnet containing the ipAddress.
+ *
+ * @param ipAddress the ipAddress to be matched
+ * @return the IpSubnet for specific ipAddress
+ */
+ IpSubnet findIpSubnet(IpAddress ipAddress);
+
+ /**
+ * Finds the Border Route containing the ipAddress.
+ * ASSUME: ipAddress is out of ipSubnets
+ *
+ * @param ipAddress the ipAddress to be matched
+ * @return the IpSubnet for specific ipAddress
+ */
+ Route findBorderRoute(IpAddress ipAddress);
+
+ /**
+ * Finds the network interface related to the host.
+ *
+ * @param host the host
+ * @return the interface related to the host
+ */
+ Interface getHostInterface(Host host);
+
+ /**
+ * Evaluates whether an IP address belongs to local SDN network.
+ *
+ * @param ipAddress the IP address to evaluate
+ * @return true if the IP address belongs to local SDN network, otherwise false
+ */
+ boolean isIpAddressLocal(IpAddress ipAddress);
+
+ /**
+ * Evaluates whether an IP prefix belongs to local SDN network.
+ *
+ * @param ipPrefix the IP prefix to evaluate
+ * @return true if the IP prefix belongs to local SDN network, otherwise false
+ */
+ boolean isIpPrefixLocal(IpPrefix ipPrefix);
+
+ /**
+ * Sends Neighbour Query (ARP or NDP) to Find Host Location.
+ *
+ * @param ip the ip address to resolve
+ * @return true if request mac packets are emitted. otherwise false
+ */
+ boolean requestMac(IpAddress ip);
+
+ /**
+ * Sends Dump Event to all SimpleFabricListeners to Dump Info on the Subject.
+ *
+ * @param subject the subject to dump
+ * @param out the output stream to dump
+ */
+ void dumpToStream(String subject, OutputStream out);
+
+ /**
+ * Triggers to send Refresh Notification to all sub modules.
+ */
+ void triggerRefresh();
+
+ /**
+ * Triggers to send Flush Notification to all sub modules.
+ */
+ void triggerFlush();
+
+}
diff --git a/apps/simplefabric/src/main/java/org/onosproject/simplefabric/package-info.java b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/package-info.java
new file mode 100644
index 0000000..1e591a4
--- /dev/null
+++ b/apps/simplefabric/src/main/java/org/onosproject/simplefabric/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Simple Leaf-Spine Network application.
+ */
+package org.onosproject.simplefabric;