Refactor: split api from SONA simple fabric

Change-Id: Icbdb10b730af29057097b14df8ddc377f4107853
diff --git a/apps/simplefabric/app/BUCK b/apps/simplefabric/app/BUCK
new file mode 100644
index 0000000..104aee1
--- /dev/null
+++ b/apps/simplefabric/app/BUCK
@@ -0,0 +1,23 @@
+COMPILE_DEPS = [
+    '//lib:CORE_DEPS',
+    '//lib:JACKSON',
+    '//lib:concurrent-trees',
+    '//lib:javax.ws.rs-api',
+    '//lib:org.apache.karaf.shell.console',
+    '//cli:onos-cli',
+    '//utils/rest:onlab-rest',
+    '//apps/simplefabric/api:onos-apps-simplefabric-api',
+]
+
+TEST_DEPS = [
+    '//lib:TEST_ADAPTERS',
+]
+
+osgi_jar_with_tests (
+    api_description = 'REST API for Simple Fabric',
+    api_package = 'org.onosproject.simplefabric.web',
+    api_title = 'Simple Fabric API',
+    deps = COMPILE_DEPS,
+    test_deps = TEST_DEPS,
+    web_context = '/onos/v1/simplefabric',
+)
\ No newline at end of file
diff --git a/apps/simplefabric/app/BUILD b/apps/simplefabric/app/BUILD
new file mode 100644
index 0000000..9d93d06
--- /dev/null
+++ b/apps/simplefabric/app/BUILD
@@ -0,0 +1,12 @@
+COMPILE_DEPS = CORE_DEPS + JACKSON + CLI + REST + [
+    "//apps/simplefabric/api:onos-apps-simplefabric-api",
+    "@concurrent_trees//jar",
+]
+
+osgi_jar_with_tests(
+    api_description = "REST API for Simple Fabric",
+    api_package = "org.onosproject.simplefabric.web",
+    api_title = "Simple Fabric API",
+    web_context = "/onos/v1/simplefabric",
+    deps = COMPILE_DEPS,
+)
diff --git a/apps/simplefabric/app/app.xml b/apps/simplefabric/app/app.xml
new file mode 100644
index 0000000..3898661
--- /dev/null
+++ b/apps/simplefabric/app/app.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<app name="org.onosproject.simplefabric" origin="Open Networking Foundation" 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/app/features.xml b/apps/simplefabric/app/features.xml
new file mode 100644
index 0000000..c9ac9f5
--- /dev/null
+++ b/apps/simplefabric/app/features.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+  ~ 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.
+  -->
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}">
+    <feature name="onos-apps-simplefabric" version="${project.version}"
+             description="${project.description}">
+        <feature>onos-api</feature>
+        <bundle>mvn:${project.groupId}/onos-apps-simplefabric/${project.version}</bundle>
+    </feature>
+</features>
diff --git a/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/RouteTools.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/RouteTools.java
new file mode 100644
index 0000000..b14438a
--- /dev/null
+++ b/apps/simplefabric/app/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/app/src/main/java/org/onosproject/simplefabric/SimpleFabricCommand.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricCommand.java
new file mode 100644
index 0000000..75db695
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricCommand.java
@@ -0,0 +1,69 @@
+/*
+ * 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.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.simplefabric.api.SimpleFabricService;
+
+/**
+ * CLI to interact with the SIMPLE_FABRIC application.
+ */
+@Command(scope = "onos", name = "simpleFabric",
+         description = "Manages the SimpleFabric application")
+public class SimpleFabricCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "command",
+              description = "Command: show|intents|reactive-intents|refresh|flush",
+              required = true, multiValued = false)
+    String command = null;
+
+    @Override
+    protected void execute() {
+
+        SimpleFabricService simpleFabric = get(SimpleFabricService.class);
+
+        if (command == null) {
+            print("command not found", command);
+            return;
+        }
+        switch (command) {
+        case "show":
+            simpleFabric.dumpToStream("show", System.out);
+            break;
+        case "intents":
+            simpleFabric.dumpToStream("intents", System.out);
+            break;
+        case "reactive-intents":
+            simpleFabric.dumpToStream("reactive-intents", System.out);
+            break;
+        case "refresh":
+            simpleFabric.triggerRefresh();
+            System.out.println("simple fabric refresh triggered");
+            break;
+        case "flush":
+            simpleFabric.triggerFlush();
+            System.out.println("simple fabric flush triggered");
+            break;
+        default:
+            System.out.println("unknown command: " + command);
+            break;
+        }
+    }
+
+}
+
diff --git a/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricCommandCompleter.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricCommandCompleter.java
new file mode 100644
index 0000000..f497721
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricCommandCompleter.java
@@ -0,0 +1,52 @@
+/*
+ * 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.Lists;
+import org.apache.karaf.shell.console.completer.ArgumentCompleter;
+import org.onosproject.cli.AbstractChoicesCompleter;
+
+import java.util.List;
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * SimpleFabric command completer.
+ */
+public class SimpleFabricCommandCompleter extends AbstractChoicesCompleter {
+
+    private static final List<String> COMMAND_LIST =
+        Arrays.asList("show", "intents", "reactive-intents", "refresh", "flush");
+
+    @Override
+    public List<String> choices() {
+        ArgumentCompleter.ArgumentList argumentList = getArgumentList();
+        if (argumentList == null) {
+            return Collections.emptyList();
+        }
+        List<String> argList = Lists.newArrayList(argumentList.getArguments());
+        String argOne = null;
+        if (argList.size() > 1) {
+            argOne = argList.get(1);
+        }
+        if (COMMAND_LIST.contains(argOne)) {
+            return Collections.emptyList();
+        } else {
+            return COMMAND_LIST;
+        }
+    }
+}
+
diff --git a/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricConfig.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricConfig.java
new file mode 100644
index 0000000..fada766
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricConfig.java
@@ -0,0 +1,161 @@
+/*
+ * 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.onosproject.simplefabric.api.IpSubnet;
+import org.onosproject.simplefabric.api.L2Network;
+import org.onosproject.simplefabric.api.Route;
+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 L2BROADCAST = "l2Broadcast";
+    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();
+            }
+            boolean l2Broadcast = true;
+            if (jsonNode.hasNonNull(L2BROADCAST)) {
+                l2Broadcast = jsonNode.get(L2BROADCAST).asBoolean();
+            }
+            try {
+                l2Networks.add(new L2Network(
+                        jsonNode.get(NAME).asText(), ifaces, EncapsulationType.enumFromString(encapsulation),
+                        l2Forward, l2Broadcast));
+            } 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/app/src/main/java/org/onosproject/simplefabric/SimpleFabricL2Forward.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricL2Forward.java
new file mode 100644
index 0000000..45a9fdd
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricL2Forward.java
@@ -0,0 +1,441 @@
+/*
+ * 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.onosproject.simplefabric.api.L2Network;
+import org.onosproject.simplefabric.api.SimpleFabricEvent;
+import org.onosproject.simplefabric.api.SimpleFabricListener;
+import org.onosproject.simplefabric.api.SimpleFabricService;
+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 (interfaces.size() < 2 || !l2Network.l2Forward() || !l2Network.l2Broadcast()) {
+            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 ("intents".equals(subject)) {
+            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/app/src/main/java/org/onosproject/simplefabric/SimpleFabricManager.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricManager.java
new file mode 100644
index 0000000..445fb71
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricManager.java
@@ -0,0 +1,670 @@
+/*
+ * 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.onosproject.simplefabric.api.IpSubnet;
+import org.onosproject.simplefabric.api.L2Network;
+import org.onosproject.simplefabric.api.Route;
+import org.onosproject.simplefabric.api.SimpleFabricEvent;
+import org.onosproject.simplefabric.api.SimpleFabricListener;
+import org.onosproject.simplefabric.api.SimpleFabricService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.ByteBuffer;
+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 simple fabric 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 = findAvailableDeviceHostInterface(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 boolean isVMac(MacAddress mac) {
+        return virtualGatewayIpMacMap.containsValue(mac);
+    }
+
+    @Override
+    public boolean isL2NetworkInterface(Interface intf) {
+        return l2NetworkInterfaces.contains(intf);
+    }
+
+    @Override
+    public MacAddress findVMacForIp(IpAddress ip) {
+        return virtualGatewayIpMacMap.get(ip);
+    }
+
+    @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) {
+        if (ip.isIp4()) {
+            return ip4SubnetTable.getValueForLongestKeyPrefixing(
+                     createBinaryString(IpPrefix.valueOf(ip, Ip4Address.BIT_LENGTH)));
+        } else {
+            return ip6SubnetTable.getValueForLongestKeyPrefixing(
+                     createBinaryString(IpPrefix.valueOf(ip, Ip6Address.BIT_LENGTH)));
+        }
+    }
+
+    @Override
+    public Route findBorderRoute(IpAddress ip) {
+        // ASSUME: ipAddress is out of ipSubnet
+        if (ip.isIp4()) {
+            return ip4BorderRouteTable.getValueForLongestKeyPrefixing(
+                     createBinaryString(IpPrefix.valueOf(ip, Ip4Address.BIT_LENGTH)));
+        } else {
+            return ip6BorderRouteTable.getValueForLongestKeyPrefixing(
+                     createBinaryString(IpPrefix.valueOf(ip, Ip6Address.BIT_LENGTH)));
+        }
+    }
+
+
+    @Override
+    public Interface findHostInterface(Host host) {
+        return interfaceService.getInterfaces().stream()
+                .filter(iface -> iface.connectPoint().equals(host.location()) &&
+                                 iface.vlan().equals(host.vlan()))
+                .findFirst()
+                .orElse(null);
+    }
+
+    private Interface findAvailableDeviceHostInterface(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 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 ("show".equals(subject)) {
+            out.println("Static Configuration Flag:");
+            out.println("    ALLOW_IPV6="
+                        + SimpleFabricService.ALLOW_IPV6);
+            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 {
+        @Override
+        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) {
+                            log.warn("run thread interrupted", e);
+                            Thread.currentThread().interrupt();
+                        }
+                    }
+                    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/app/src/main/java/org/onosproject/simplefabric/SimpleFabricNeighbour.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricNeighbour.java
new file mode 100644
index 0000000..1b5330c
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricNeighbour.java
@@ -0,0 +1,244 @@
+/*
+ * 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.onosproject.simplefabric.api.L2Network;
+import org.onosproject.simplefabric.api.SimpleFabricEvent;
+import org.onosproject.simplefabric.api.SimpleFabricListener;
+import org.onosproject.simplefabric.api.SimpleFabricService;
+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.findVMacForIp(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.findVMacForIp(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.findHostInterface(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/app/src/main/java/org/onosproject/simplefabric/SimpleFabricReactiveRouting.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricReactiveRouting.java
new file mode 100644
index 0000000..4f83ae3
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricReactiveRouting.java
@@ -0,0 +1,989 @@
+/*
+ * 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.FilteredConnectPoint;
+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.intf.Interface;
+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.simplefabric.api.IpSubnet;
+import org.onosproject.simplefabric.api.L2Network;
+import org.onosproject.simplefabric.api.Route;
+import org.onosproject.simplefabric.api.SimpleFabricEvent;
+import org.onosproject.simplefabric.api.SimpleFabricListener;
+import org.onosproject.simplefabric.api.SimpleFabricService;
+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(),
+                                                                               l2Network));
+                }
+                // 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 isDstLocalSubnet, 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, isDstLocalSubnet, 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, L2Network l2Network) {
+        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 (Interface iface : l2Network.interfaces()) {
+            if (iface.connectPoint().deviceId().equals(deviceId)) {
+                treatment.setOutput(iface.connectPoint().port());
+            }
+        }
+        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<FilteredConnectPoint> newIngressPoints = new HashSet<>();
+            boolean ingressPointChanged = false;
+            for (FilteredConnectPoint cp : intent.filteredIngressPoints()) {
+                if (deviceService.isAvailable(cp.connectPoint().deviceId()) &&
+                    (simpleFabric.findL2Network(cp.connectPoint(), VlanId.NONE) != null ||
+                     (simpleFabric.REACTIVE_ALLOW_LINK_CP &&
+                      !linkService.getIngressLinks(cp.connectPoint()).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())
+                        .filteredIngressPoints(newIngressPoints)
+                        .filteredEgressPoint(intent.filteredEgressPoint())
+                        .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.findVMacForIp(dstIp);
+        if (mac == null || !simpleFabric.isVMac(ethPkt.getDestinationMAC())) {
+            /* Destination MAC should be any of virtual gateway macs */
+            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 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("unknown srcIp; drop: srcCp={} srcIp={} dstIp={} ipProto={}",
+                         srcCp, srcIp, dstIp, ipProto);
+                return;
+            }
+            srcPrefix = route.prefix();
+            srcNextHop = route.nextHop();
+            borderRoutePrefixLength = route.prefix().prefixLength();
+        }
+        IpSubnet dstSubnet = simpleFabric.findIpSubnet(dstIp);
+        if (dstSubnet == null) {
+            Route route = simpleFabric.findBorderRoute(dstIp);
+            if (route == null) {
+                log.warn("unknown dstIp; drop: srcCp={} srcIp={} dstIp={} ipProto={}",
+                         srcCp, srcIp, dstIp, ipProto);
+                return;
+            }
+            dstPrefix = route.prefix();
+            dstNextHop = route.nextHop();
+            borderRoutePrefixLength = route.prefix().prefixLength();
+        }
+
+        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,
+                          dstSubnet != null, 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 isDstLocalSubnet, int borderRoutePrefixLength) {
+        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);
+            }
+        }
+
+        Key key;
+        String keyProtoTag = "";
+        if (simpleFabric.REACTIVE_MATCH_IP_PROTO) {
+            keyProtoTag = "-p" + ipProto;
+        }
+        if (simpleFabric.REACTIVE_SINGLE_TO_SINGLE) {
+            // allocate intent per (srcPrefix, dstPrefix)
+            key = Key.of(srcPrefix.toString() + "-to-" + dstPrefix.toString() + keyProtoTag, reactiveAppId);
+        } else {
+            // allocate intent per (srcDeviceId, dstPrefix)
+            key = Key.of(srcCp.deviceId().toString() + "-to-" +  dstPrefix.toString() + keyProtoTag, reactiveAppId);
+        }
+
+        // check and merge already existing ingress points
+        Set<FilteredConnectPoint> ingressPoints = new HashSet<>();
+        MultiPointToSinglePointIntent existingIntent = (MultiPointToSinglePointIntent) intentService.getIntent(key);
+        if (existingIntent != null) {
+            ingressPoints.addAll(existingIntent.filteredIngressPoints());
+            if (!ingressPoints.add(new FilteredConnectPoint(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(new FilteredConnectPoint(srcCp));
+        }
+
+        // priority for forwarding case
+        int priority = reactivePriority(true, isDstLocalSubnet, borderRoutePrefixLength);
+
+        MultiPointToSinglePointIntent newIntent = MultiPointToSinglePointIntent.builder()
+            .key(key)
+            .appId(reactiveAppId)
+            .selector(selector.build())
+            .treatment(treatment)
+            .filteredIngressPoints(ingressPoints)
+            .filteredEgressPoint(new FilteredConnectPoint(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 isDstLocalSubnet, int borderRoutePrefixLength) {
+        if (isDstLocalSubnet) {  // -> dst:localSubnet
+            if (isForward) {
+                return simpleFabric.PRI_REACTIVE_LOCAL_FORWARD;
+            } else {  // isInterncept
+                return simpleFabric.PRI_REACTIVE_LOCAL_INTERCEPT;
+            }
+        } else {  // -> dst:boarderRouteNextHop
+            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 ("intents".equals(subject)) {
+            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 ("reactive-intents".equals(subject)) {
+            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/app/src/main/java/org/onosproject/simplefabric/SimpleFabricWebApplication.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricWebApplication.java
new file mode 100644
index 0000000..d82de73
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricWebApplication.java
@@ -0,0 +1,32 @@
+/*
+ * 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.rest.AbstractWebApplication;
+
+import java.util.Set;
+
+/**
+ * SIMPLE_FABRIC REST API web application.
+ */
+public class SimpleFabricWebApplication extends AbstractWebApplication {
+    @Override
+    public Set<Class<?>> getClasses() {
+        return getClasses(SimpleFabricWebResource.class);
+    }
+}
+
diff --git a/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricWebResource.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricWebResource.java
new file mode 100644
index 0000000..3274a3e
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/SimpleFabricWebResource.java
@@ -0,0 +1,109 @@
+/*
+ * 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.rest.AbstractWebResource;
+import org.onosproject.simplefabric.api.SimpleFabricService;
+
+import java.io.ByteArrayOutputStream;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Response;
+
+/**
+ * Manage SIMPLE_FABRIC Status.
+ */
+@Path("")
+public class SimpleFabricWebResource extends AbstractWebResource {
+
+    /**
+     * SIMPLE_FABRIC Show Status; dummy for now.
+     *
+     * @return 200 OK
+     */
+    @GET
+    @Path("status")
+    public Response queryStatus() {
+        return Response.ok("ok").build();
+    }
+
+    /**
+     * SIMPLE_FABRIC Show Configurations.
+     *
+     * @return 200 OK
+     */
+    @GET
+    @Path("show")
+    public Response queryShow() {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        get(SimpleFabricService.class).dumpToStream("show", outputStream);
+        return Response.ok(outputStream.toString()).build();
+    }
+
+    /**
+     * SIMPLE_FABRIC Intents Infos.
+     *
+     * @return 200 OK
+     */
+    @GET
+    @Path("intents")
+    public Response queryIntents() {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        get(SimpleFabricService.class).dumpToStream("intents", outputStream);
+        return Response.ok(outputStream.toString()).build();
+    }
+
+    /**
+     * SIMPLE_FABRIC Reactive Intents Infos.
+     *
+     * @return 200 OK
+     */
+    @GET
+    @Path("reactive-intents")
+    public Response queryReactiveIntents() {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        get(SimpleFabricService.class).dumpToStream("reactive-intents", outputStream);
+        return Response.ok(outputStream.toString()).build();
+    }
+
+    /**
+     * Trigger SimpleFabric Service Refresh.
+     *
+     * @return 204 No Content
+     */
+    @PUT
+    @Path("refresh")
+    public Response triggerRefresh() {
+        get(SimpleFabricService.class).triggerRefresh();
+        return Response.status(204).build();
+    }
+
+    /**
+     * Trigger SimpleFabric Service Flush Reactive Intents.
+     *
+     * @return 204 No Content
+     */
+    @PUT
+    @Path("flush")
+    public Response triggerFlush() {
+        get(SimpleFabricService.class).triggerFlush();
+        return Response.status(204).build();
+    }
+
+}
+
diff --git a/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/package-info.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/package-info.java
new file mode 100644
index 0000000..1e591a4
--- /dev/null
+++ b/apps/simplefabric/app/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;
diff --git a/apps/simplefabric/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/simplefabric/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..7b6893b
--- /dev/null
+++ b/apps/simplefabric/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,28 @@
+<!--
+  ~ 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.
+  -->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+
+    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
+        <command>
+            <action class="org.onosproject.simplefabric.SimpleFabricCommand"/>
+            <completers>
+                <ref component-id="SimpleFabricCommandCompleter"/>
+            </completers>
+        </command>
+    </command-bundle>
+ 
+    <bean id="SimpleFabricCommandCompleter" class="org.onosproject.simplefabric.SimpleFabricCommandCompleter"/>
+</blueprint>
diff --git a/apps/simplefabric/app/src/main/webapp/WEB-INF/web.xml b/apps/simplefabric/app/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..e136671
--- /dev/null
+++ b/apps/simplefabric/app/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+         id="ONOS" version="2.5">
+    <display-name>Simple Fabric application REST API</display-name>
+
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>Secured</web-resource-name>
+            <url-pattern>/*</url-pattern>
+        </web-resource-collection>
+        <auth-constraint>
+            <role-name>admin</role-name>
+            <role-name>viewer</role-name>
+        </auth-constraint>
+    </security-constraint>
+
+    <security-role>
+        <role-name>admin</role-name>
+        <role-name>viewer</role-name>
+    </security-role>
+
+    <login-config>
+        <auth-method>BASIC</auth-method>
+        <realm-name>karaf</realm-name>
+    </login-config>
+
+    <servlet>
+        <servlet-name>JAX-RS Service</servlet-name>
+        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
+        <init-param>
+            <param-name>javax.ws.rs.Application</param-name>
+            <param-value>org.onosproject.simplefabric.SimpleFabricWebApplication</param-value>
+        </init-param>
+        <load-on-startup>1</load-on-startup>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>JAX-RS Service</servlet-name>
+        <url-pattern>/*</url-pattern>
+    </servlet-mapping>
+
+</web-app>