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>