Merge branch 'master' into merge
Change-Id: Id77bb2de77dd288404c83e331b076aaf9aafacc5
diff --git a/apps/simplefabric/app/BUCK b/apps/simplefabric/app/BUCK
new file mode 100644
index 0000000..6aefd2c
--- /dev/null
+++ b/apps/simplefabric/app/BUCK
@@ -0,0 +1,24 @@
+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',
+ api_version = '1.0',
+ 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..69d12f7
--- /dev/null
+++ b/apps/simplefabric/app/BUILD
@@ -0,0 +1,21 @@
+COMPILE_DEPS = CORE_DEPS + JACKSON + CLI + REST + [
+ "//apps/simplefabric/api:onos-apps-simplefabric-api",
+ "@concurrent_trees//jar",
+]
+
+TEST_DEPS = TEST_ADAPTERS + TEST_REST + [
+ "//core/api:onos-api-tests",
+ "//core/common:onos-core-common-tests",
+ "//web/api:onos-rest-tests",
+]
+
+osgi_jar_with_tests(
+ api_description = "REST API for Simple Fabric",
+ api_package = "org.onosproject.simplefabric.web",
+ api_title = "Simple Fabric API",
+ api_version = "1.0",
+ karaf_command_packages = ["org.onosproject.simplefabric.cli"],
+ test_deps = TEST_DEPS,
+ 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/cli/SimpleFabricCommand.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/cli/SimpleFabricCommand.java
new file mode 100644
index 0000000..3271e8d
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/cli/SimpleFabricCommand.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.simplefabric.cli;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.simplefabric.api.SimpleFabricService;
+
+/**
+ * CLI to interact with the SIMPLE_FABRIC application.
+ */
+@Service
+@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)
+ @Completion(SimpleFabricCommandCompleter.class)
+ String command = null;
+
+ @Override
+ protected void doExecute() {
+
+ 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/cli/SimpleFabricCommandCompleter.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/cli/SimpleFabricCommandCompleter.java
new file mode 100644
index 0000000..a643539
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/cli/SimpleFabricCommandCompleter.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.simplefabric.cli;
+
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractChoicesCompleter;
+
+import java.util.List;
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * SimpleFabric command completer.
+ */
+@Service
+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() {
+ if (commandLine.getArguments() == null) {
+ return Collections.emptyList();
+ }
+ List<String> argList = Lists.newArrayList(commandLine.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/cli/package-info.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/cli/package-info.java
new file mode 100644
index 0000000..1a92219
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/cli/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Simple fabric CLI package.
+ */
+package org.onosproject.simplefabric.cli;
\ No newline at end of file
diff --git a/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/DefaultFabricNetwork.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/DefaultFabricNetwork.java
new file mode 100644
index 0000000..386a345
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/DefaultFabricNetwork.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.simplefabric.impl;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.EncapsulationType;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.simplefabric.api.FabricNetwork;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.onosproject.simplefabric.api.Constants.ALLOW_ETH_ADDRESS_SELECTOR;
+
+
+/**
+ * Class stores a DefaultFabricNetwork information.
+ */
+public final class DefaultFabricNetwork implements FabricNetwork {
+
+ private static final String NOT_NULL_MSG = "FabricNetwork % cannot be null";
+
+ private final String name;
+ private final Set<String> interfaceNames;
+ private final EncapsulationType encapsulation;
+ private boolean forward;
+ private boolean broadcast;
+
+ /* status variables */
+ private final Set<Interface> interfaces;
+ private final Set<HostId> hostIds;
+ private boolean dirty;
+
+ /**
+ * Constructs a DefaultFabricNetwork instance.
+ *
+ * @param name fabric name name
+ * @param interfaceNames a collection of interface names
+ * @param encapsulation encapsulation type
+ * @param forward flag for forward intents to be installed or not
+ * @param broadcast flag for broadcast intents to be installed or not
+ */
+ private DefaultFabricNetwork(String name, Collection<String> interfaceNames,
+ EncapsulationType encapsulation,
+ boolean forward, boolean broadcast) {
+ this.name = name;
+ this.interfaceNames = Sets.newHashSet();
+
+ if (interfaceNames != null) {
+ this.interfaceNames.addAll(interfaceNames);
+ }
+
+ this.encapsulation = encapsulation;
+ this.forward = (ALLOW_ETH_ADDRESS_SELECTOR) && forward;
+ this.broadcast = (ALLOW_ETH_ADDRESS_SELECTOR) && broadcast;
+ this.interfaces = Sets.newHashSet();
+ this.hostIds = Sets.newHashSet();
+ this.dirty = false;
+ }
+
+ /**
+ * Constructs a DefaultFabricNetwork instance.
+ *
+ * @param name fabric network name
+ * @param encapsulation encapsulation type
+ */
+ private DefaultFabricNetwork(String name, EncapsulationType encapsulation) {
+ this.name = name;
+ this.interfaceNames = Sets.newHashSet();
+ this.encapsulation = encapsulation;
+ this.forward = ALLOW_ETH_ADDRESS_SELECTOR;
+ this.broadcast = ALLOW_ETH_ADDRESS_SELECTOR;
+ this.interfaces = Sets.newHashSet();
+ this.hostIds = Sets.newHashSet();
+ this.dirty = false;
+ }
+
+ /**
+ * Creates a DefaultFabricNetwork data by given name.
+ * The encapsulation type of the DefaultFabricNetwork will be NONE.
+ *
+ * @param name fabric network name
+ * @return DefaultFabricNetwork instance
+ */
+ public static FabricNetwork of(String name) {
+ Objects.requireNonNull(name);
+ return new DefaultFabricNetwork(name, EncapsulationType.NONE);
+ }
+
+ /**
+ * Creates a copy of DefaultFabricNetwork instance.
+ *
+ * @param fabricNetwork DefaultFabricNetwork instance
+ * @return the copy of the DefaultFabricNetwork instance
+ */
+ public static FabricNetwork of(FabricNetwork fabricNetwork) {
+ Objects.requireNonNull(fabricNetwork);
+ DefaultFabricNetwork fabricNetworkCopy =
+ new DefaultFabricNetwork(fabricNetwork.name(), fabricNetwork.encapsulation());
+ fabricNetworkCopy.interfaceNames.addAll(fabricNetwork.interfaceNames());
+ fabricNetworkCopy.forward = (ALLOW_ETH_ADDRESS_SELECTOR) && fabricNetwork.isForward();
+ fabricNetworkCopy.broadcast = (ALLOW_ETH_ADDRESS_SELECTOR) && fabricNetwork.isBroadcast();
+ fabricNetworkCopy.interfaces.addAll(fabricNetwork.interfaces());
+ fabricNetworkCopy.hostIds.addAll(fabricNetwork.hostIds());
+ fabricNetworkCopy.setDirty(fabricNetwork.isDirty());
+ return fabricNetworkCopy;
+ }
+
+ // field queries
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public Set<String> interfaceNames() {
+ return ImmutableSet.copyOf(interfaceNames);
+ }
+
+ @Override
+ public EncapsulationType encapsulation() {
+ return encapsulation;
+ }
+
+ @Override
+ public boolean isForward() {
+ return forward;
+ }
+
+ @Override
+ public boolean isBroadcast() {
+ return broadcast;
+ }
+
+ @Override
+ public Set<Interface> interfaces() {
+ return ImmutableSet.copyOf(interfaces);
+ }
+
+ @Override
+ public Set<HostId> hostIds() {
+ return ImmutableSet.copyOf(hostIds);
+ }
+
+ @Override
+ public boolean isDirty() {
+ return dirty;
+ }
+
+ @Override
+ public boolean contains(Interface iface) {
+ return interfaces.contains(iface);
+ }
+
+ @Override
+ public boolean contains(ConnectPoint port, VlanId vlanId) {
+ for (Interface iface : interfaces) {
+ if (iface.connectPoint().equals(port) && iface.vlan().equals(vlanId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean contains(DeviceId deviceId) {
+ for (Interface iface : interfaces) {
+ if (iface.connectPoint().deviceId().equals(deviceId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void addInterface(Interface iface) {
+ Objects.requireNonNull(iface);
+ if (interfaces.add(iface)) {
+ setDirty(true);
+ }
+ }
+
+ @Override
+ public void addHost(Host host) {
+ Objects.requireNonNull(host);
+ if (hostIds.add(host.id())) {
+ setDirty(true);
+ }
+ }
+
+ @Override
+ public void setDirty(boolean newDirty) {
+ dirty = newDirty;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("name", name)
+ .add("interfaceNames", interfaceNames)
+ .add("encapsulation", encapsulation)
+ .add("forward", forward)
+ .add("broadcast", broadcast)
+ .add("interfaces", interfaces)
+ .add("hostIds", hostIds)
+ .add("isDirty", dirty)
+ .toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof DefaultFabricNetwork)) {
+ return false;
+ }
+ DefaultFabricNetwork other = (DefaultFabricNetwork) obj;
+ return Objects.equals(other.name, this.name)
+ && Objects.equals(other.interfaceNames, this.interfaceNames)
+ && Objects.equals(other.encapsulation, this.encapsulation)
+ && Objects.equals(other.forward, this.forward)
+ && Objects.equals(other.broadcast, this.broadcast)
+ && Objects.equals(other.interfaces, this.interfaces)
+ && Objects.equals(other.hostIds, this.hostIds);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, interfaces, encapsulation, forward, broadcast);
+ }
+
+ /**
+ * Returns new builder instance.
+ *
+ * @return fabric network builder
+ */
+ public static DefaultFabricNetworkBuilder builder() {
+ return new DefaultFabricNetworkBuilder();
+ }
+
+ /**
+ * A builder class for fabric network.
+ */
+ public static final class DefaultFabricNetworkBuilder implements Builder {
+ private String name;
+ private Set<String> interfaceNames;
+ private EncapsulationType encapsulation;
+ private boolean forward;
+ private boolean broadcast;
+
+ private DefaultFabricNetworkBuilder() {
+ }
+
+ @Override
+ public Builder name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ @Override
+ public Builder interfaceNames(Set<String> interfaceNames) {
+ this.interfaceNames = interfaceNames;
+ return this;
+ }
+
+ @Override
+ public Builder encapsulation(EncapsulationType encapsulation) {
+ this.encapsulation = encapsulation;
+ return this;
+ }
+
+ @Override
+ public Builder forward(boolean forward) {
+ this.forward = forward;
+ return this;
+ }
+
+ @Override
+ public Builder broadcast(boolean broadcast) {
+ this.broadcast = broadcast;
+ return this;
+ }
+
+ @Override
+ public FabricNetwork build() {
+ checkArgument(name != null, NOT_NULL_MSG, "name");
+ return new DefaultFabricNetwork(name, interfaceNames,
+ encapsulation, forward, broadcast);
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/DefaultFabricRoute.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/DefaultFabricRoute.java
new file mode 100644
index 0000000..6b9a132
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/DefaultFabricRoute.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.simplefabric.impl;
+
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.simplefabric.api.FabricRoute;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Represents a route.
+ */
+public final class DefaultFabricRoute implements FabricRoute {
+
+ private static final String VERSION_MISMATCH =
+ "Prefix and next hop must be in the same address family";
+
+ private static final NodeId UNDEFINED = new NodeId("-");
+
+ private final Source source;
+ private final IpPrefix prefix;
+ private final IpAddress nextHop;
+ private final NodeId sourceNode;
+
+ /**
+ * Creates a route.
+ *
+ * @param source route source
+ * @param prefix IP prefix
+ * @param nextHop next hop IP address
+ */
+ private DefaultFabricRoute(Source source, IpPrefix prefix, IpAddress nextHop) {
+ this(source, prefix, nextHop, UNDEFINED);
+ }
+
+ /**
+ * Creates a route.
+ *
+ * @param source route source
+ * @param prefix IP prefix
+ * @param nextHop next hop IP address
+ * @param sourceNode ONOS node the route was sourced from
+ */
+ private DefaultFabricRoute(Source source, IpPrefix prefix,
+ IpAddress nextHop, NodeId sourceNode) {
+ this.source = checkNotNull(source);
+ this.prefix = prefix;
+ this.nextHop = nextHop;
+ this.sourceNode = checkNotNull(sourceNode);
+ }
+
+ @Override
+ public Source source() {
+ return source;
+ }
+
+ @Override
+ public IpPrefix prefix() {
+ return prefix;
+ }
+
+ @Override
+ public IpAddress nextHop() {
+ return nextHop;
+ }
+
+ @Override
+ public NodeId sourceNode() {
+ return sourceNode;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(prefix, nextHop);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (!(other instanceof DefaultFabricRoute)) {
+ return false;
+ }
+
+ DefaultFabricRoute that = (DefaultFabricRoute) other;
+
+ return Objects.equals(this.prefix, that.prefix) &&
+ Objects.equals(this.nextHop, that.nextHop);
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("prefix", prefix)
+ .add("nextHop", nextHop)
+ .toString();
+ }
+
+ /**
+ * Returns new builder instance.
+ *
+ * @return fabric route builder
+ */
+ public static DefaultFabricRouteBuilder builder() {
+ return new DefaultFabricRouteBuilder();
+ }
+
+ /**
+ * A builder class for fabric route.
+ */
+ public static final class DefaultFabricRouteBuilder implements Builder {
+ private Source source;
+ private IpPrefix prefix;
+ private IpAddress nextHop;
+ private NodeId sourceNode;
+
+ private DefaultFabricRouteBuilder() {
+ }
+
+ @Override
+ public Builder source(Source source) {
+ this.source = source;
+ return this;
+ }
+
+ @Override
+ public Builder prefix(IpPrefix prefix) {
+ this.prefix = prefix;
+ return this;
+ }
+
+ @Override
+ public Builder nextHop(IpAddress nextHop) {
+ this.nextHop = nextHop;
+ return this;
+ }
+
+ @Override
+ public Builder sourceNode(NodeId sourceNode) {
+ this.sourceNode = sourceNode;
+ return this;
+ }
+
+ @Override
+ public FabricRoute build() {
+
+ checkNotNull(prefix);
+ checkNotNull(nextHop);
+ checkArgument(prefix.version().equals(nextHop.version()), VERSION_MISMATCH);
+
+ if (sourceNode != null) {
+ return new DefaultFabricRoute(source, prefix, nextHop, sourceNode);
+ } else {
+ return new DefaultFabricRoute(source, prefix, nextHop);
+ }
+ }
+ }
+}
diff --git a/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/DefaultFabricSubnet.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/DefaultFabricSubnet.java
new file mode 100644
index 0000000..b7fec47
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/DefaultFabricSubnet.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.simplefabric.impl;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.EncapsulationType;
+import org.onosproject.simplefabric.api.FabricSubnet;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Configuration details for an ip subnet entry.
+ */
+public final class DefaultFabricSubnet implements FabricSubnet {
+
+ private static final String NOT_NULL_MSG = "FabricSubnet % cannot be null";
+
+ private final IpPrefix prefix;
+ private final IpAddress gatewayIp;
+ private final MacAddress gatewayMac;
+ private EncapsulationType encapsulation;
+ private final String networkName;
+
+ /**
+ * Creates a new subnet entry.
+ *
+ * @param prefix an ip subnet
+ * @param gatewayIp IP of the virtual gateway
+ * @param gatewayMac MacAddress of the virtual gateway
+ * @param encapsulation encapsulation type
+ * @param networkName network name
+ */
+ private DefaultFabricSubnet(IpPrefix prefix, IpAddress gatewayIp,
+ MacAddress gatewayMac, EncapsulationType encapsulation,
+ String networkName) {
+ this.prefix = prefix;
+ this.gatewayIp = gatewayIp;
+ this.gatewayMac = gatewayMac;
+ this.encapsulation = encapsulation;
+ this.networkName = networkName;
+ }
+
+ @Override
+ public IpPrefix prefix() {
+ return prefix;
+ }
+
+ @Override
+ public IpAddress gatewayIp() {
+ return gatewayIp;
+ }
+
+ @Override
+ public MacAddress gatewayMac() {
+ return gatewayMac;
+ }
+
+ @Override
+ public EncapsulationType encapsulation() {
+ return encapsulation;
+ }
+
+ @Override
+ public String networkName() {
+ return networkName;
+ }
+
+ @Override
+ public boolean isIp4() {
+ return prefix.isIp4();
+ }
+
+ @Override
+ public boolean isIp6() {
+ return prefix.isIp6();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(prefix, gatewayIp, gatewayMac, encapsulation, networkName);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof DefaultFabricSubnet)) {
+ return false;
+ }
+ DefaultFabricSubnet that = (DefaultFabricSubnet) obj;
+ return Objects.equals(this.prefix, that.prefix)
+ && Objects.equals(this.gatewayIp, that.gatewayIp)
+ && Objects.equals(this.gatewayMac, that.gatewayMac)
+ && Objects.equals(this.encapsulation, that.encapsulation)
+ && Objects.equals(this.networkName, that.networkName);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("prefix", prefix)
+ .add("gatewayIp", gatewayIp)
+ .add("gatewayMac", gatewayMac)
+ .add("encapsulation", encapsulation)
+ .add("networkName", networkName)
+ .toString();
+ }
+
+ /**
+ * Returns new builder instance.
+ *
+ * @return fabric IP subnet builder
+ */
+ public static DefaultSubnetBuilder builder() {
+ return new DefaultSubnetBuilder();
+ }
+
+ /**
+ * A builder class for Ip Subnet.
+ */
+ public static final class DefaultSubnetBuilder implements Builder {
+ private IpPrefix prefix;
+ private IpAddress gatewayIp;
+ private MacAddress gatewayMac;
+ private EncapsulationType encapsulation;
+ private String networkName;
+
+ private DefaultSubnetBuilder() {
+ }
+
+ @Override
+ public Builder prefix(IpPrefix prefix) {
+ this.prefix = prefix;
+ return this;
+ }
+
+ @Override
+ public Builder gatewayIp(IpAddress gatewayIp) {
+ this.gatewayIp = gatewayIp;
+ return this;
+ }
+
+ @Override
+ public Builder gatewayMac(MacAddress gatewayMac) {
+ this.gatewayMac = gatewayMac;
+ return this;
+ }
+
+ @Override
+ public Builder encapsulation(EncapsulationType encapsulation) {
+ this.encapsulation = encapsulation;
+ return this;
+ }
+
+ @Override
+ public Builder networkName(String networkName) {
+ this.networkName = networkName;
+ return this;
+ }
+
+ @Override
+ public FabricSubnet build() {
+ checkArgument(prefix != null, NOT_NULL_MSG, "prefix");
+ checkArgument(gatewayIp != null, NOT_NULL_MSG, "gatewayIp");
+ checkArgument(gatewayMac != null, NOT_NULL_MSG, "gatewayMac");
+ checkArgument(networkName != null, NOT_NULL_MSG, "name");
+
+ if (this.encapsulation == null) {
+ encapsulation = EncapsulationType.NONE;
+ }
+
+ return new DefaultFabricSubnet(prefix, gatewayIp, gatewayMac,
+ encapsulation, networkName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/SimpleFabricConfig.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/SimpleFabricConfig.java
new file mode 100644
index 0000000..1d4b97c
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/SimpleFabricConfig.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.simplefabric.impl;
+
+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.EncapsulationType;
+import org.onosproject.net.config.Config;
+import org.onosproject.simplefabric.api.FabricNetwork;
+import org.onosproject.simplefabric.api.FabricRoute;
+import org.onosproject.simplefabric.api.FabricSubnet;
+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 FABRIC_NETWORKS = "fabricNetworks";
+ private static final String NAME = "name";
+ private static final String INTERFACES = "interfaces";
+ private static final String ENCAPSULATION = "encapsulation";
+ private static final String IS_FORWARD = "isForward";
+ private static final String IS_BROADCAST = "isBroadcast";
+
+ private static final String FABRIC_SUBNETS = "fabricSubnets";
+ private static final String PREFIX = "prefix";
+ private static final String GATEWAY_IP = "gatewayIp";
+ private static final String GATEWAY_MAC = "gatewayMac";
+ private static final String NETWORK_NAME = "networkName";
+
+ private static final String FABRIC_ROUTES = "fabricRoutes";
+ private static final String NEXT_HOP = "nextHop";
+
+ private static final String NONE_ENCAPSULATION = "NONE";
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ /**
+ * Returns all fabric networks in this configuration.
+ *
+ * @return a set of fabric networks
+ */
+ public Set<FabricNetwork> fabricNetworks() {
+ Set<FabricNetwork> fabricNetworks = Sets.newHashSet();
+ JsonNode fabricNetworkNodes = object.get(FABRIC_NETWORKS);
+ if (fabricNetworkNodes == null) {
+ return fabricNetworks;
+ }
+
+ fabricNetworkNodes.forEach(jsonNode -> {
+ Set<String> ifaces = Sets.newHashSet();
+ JsonNode fabricNetworkIfaces = jsonNode.path(INTERFACES);
+ if (fabricNetworkIfaces == null) {
+ log.warn("Fabric network interfaces cannot find {}; skip: jsonNode={}", INTERFACES, jsonNode);
+ } else if (!fabricNetworkIfaces.toString().isEmpty()) {
+ fabricNetworkIfaces.forEach(ifacesNode -> ifaces.add(ifacesNode.asText()));
+ }
+ String encapsulation = NONE_ENCAPSULATION; // NONE or VLAN
+ if (jsonNode.hasNonNull(ENCAPSULATION)) {
+ encapsulation = jsonNode.get(ENCAPSULATION).asText();
+ }
+ boolean isForward = true;
+ if (jsonNode.hasNonNull(IS_FORWARD)) {
+ isForward = jsonNode.get(IS_FORWARD).asBoolean();
+ }
+ boolean isBroadcast = true;
+ if (jsonNode.hasNonNull(IS_BROADCAST)) {
+ isBroadcast = jsonNode.get(IS_BROADCAST).asBoolean();
+ }
+ try {
+ fabricNetworks.add(DefaultFabricNetwork.builder()
+ .name(jsonNode.get(NAME).asText())
+ .interfaceNames(ifaces)
+ .encapsulation(EncapsulationType.enumFromString(encapsulation))
+ .forward(isForward)
+ .broadcast(isBroadcast)
+ .build());
+ } catch (Exception e) {
+ log.warn("Fabric network parse failed; skip: jsonNode={}", jsonNode);
+ }
+ });
+ return fabricNetworks;
+ }
+
+ /**
+ * Gets the set of configured local subnets.
+ *
+ * @return a set of subnets
+ */
+ public Set<FabricSubnet> fabricSubnets() {
+ Set<FabricSubnet> subnets = Sets.newHashSet();
+ JsonNode subnetsNode = object.get(FABRIC_SUBNETS);
+ if (subnetsNode == null) {
+ log.warn("FabricSubnets is null!");
+ return subnets;
+ }
+
+ subnetsNode.forEach(jsonNode -> {
+ String encapsulation = NONE_ENCAPSULATION; // NONE or VLAN
+ if (jsonNode.hasNonNull(ENCAPSULATION)) {
+ encapsulation = jsonNode.get(ENCAPSULATION).asText();
+ }
+ try {
+ subnets.add(DefaultFabricSubnet.builder()
+ .prefix(IpPrefix.valueOf(jsonNode.get(PREFIX).asText()))
+ .gatewayIp(IpAddress.valueOf(jsonNode.get(GATEWAY_IP).asText()))
+ .gatewayMac(MacAddress.valueOf(jsonNode.get(GATEWAY_MAC).asText()))
+ .encapsulation(EncapsulationType.enumFromString(encapsulation))
+ .networkName(jsonNode.get(NETWORK_NAME).asText())
+ .build());
+ } catch (Exception e) {
+ log.warn("Fabric subnet parse failed; skip: jsonNode={}", jsonNode);
+ }
+ });
+
+ return subnets;
+ }
+
+ /**
+ * Returns all routes in this configuration.
+ *
+ * @return a set of routes.
+ */
+ public Set<FabricRoute> fabricRoutes() {
+ Set<FabricRoute> routes = Sets.newHashSet();
+
+ JsonNode routesNode = object.get(FABRIC_ROUTES);
+ if (routesNode == null) {
+ return routes;
+ }
+
+ routesNode.forEach(jsonNode -> {
+ try {
+ routes.add(DefaultFabricRoute.builder()
+ .source(FabricRoute.Source.STATIC)
+ .prefix(IpPrefix.valueOf(jsonNode.path(PREFIX).asText()))
+ .nextHop(IpAddress.valueOf(jsonNode.path(NEXT_HOP).asText()))
+ .build());
+ } catch (IllegalArgumentException e) {
+ log.warn("Fabric router parse error; skip: jsonNode={}", jsonNode);
+ }
+ });
+
+ return routes;
+ }
+}
diff --git a/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/SimpleFabricForwarding.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/SimpleFabricForwarding.java
new file mode 100644
index 0000000..48fdf34
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/SimpleFabricForwarding.java
@@ -0,0 +1,462 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.simplefabric.impl;
+
+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.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.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.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.net.intf.Interface;
+import org.onosproject.simplefabric.api.FabricNetwork;
+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.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.onosproject.simplefabric.api.Constants.FORWARDING_APP_ID;
+import static org.onosproject.simplefabric.api.Constants.PRI_L2NETWORK_BROADCAST;
+import static org.onosproject.simplefabric.api.Constants.PRI_L2NETWORK_UNICAST;
+
+
+/**
+ * An implementation of L2NetworkOperationService.
+ * Handles the execution order of the L2 Network operations generated by the
+ * application.
+ */
+@Component(immediate = true, enabled = false)
+public class SimpleFabricForwarding {
+
+ public static final String BROADCAST = "BCAST";
+ public static final String UNICAST = "UNI";
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+ protected ApplicationId appId;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected IntentService intentService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected HostService hostService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ 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() {
+ appId = coreService.registerApplication(FORWARDING_APP_ID);
+ log.info("simple fabric forwarding starting with l2net app id {}", appId.toString());
+
+ simpleFabric.addListener(simpleFabricListener);
+
+ refresh();
+ checkIntentsPurge();
+
+ log.info("simple fabric forwarding started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("simple fabric forwarding 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 forwarding stopped");
+ }
+
+ private void refresh() {
+ log.debug("simple fabric forwarding refresh");
+
+ Map<Key, SinglePointToMultiPointIntent> newBctIntentsMap = Maps.newConcurrentMap();
+ Map<Key, MultiPointToSinglePointIntent> newUniIntentsMap = Maps.newConcurrentMap();
+
+ for (FabricNetwork fabricNetwork : simpleFabric.fabricNetworks()) {
+ // scans all l2network regardless of isDirty flag
+ // if fabricNetwork.isForward == false or number of interfaces() < 2, no Intents generated
+ for (SinglePointToMultiPointIntent intent : buildBrcIntents(fabricNetwork)) {
+ newBctIntentsMap.put(intent.key(), intent);
+ }
+ for (MultiPointToSinglePointIntent intent :
+ buildUniIntents(fabricNetwork, hostsFromL2Network(fabricNetwork))) {
+ newUniIntentsMap.put(intent.key(), intent);
+ }
+ if (fabricNetwork.isDirty()) {
+ fabricNetwork.setDirty(false);
+ }
+ }
+
+ boolean bctUpdated = false;
+ for (SinglePointToMultiPointIntent intent : bctIntentsMap.values()) {
+ SinglePointToMultiPointIntent newIntent = newBctIntentsMap.get(intent.key());
+ if (newIntent == null) {
+ log.info("simple fabric forwarding 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 forwarding 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 forwarding 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 forwarding 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 forwarding purged intent: key={}", key.toString());
+ purgedKeys.add(key);
+ } else {
+ switch (intentService.getIntentState(key)) {
+ case FAILED:
+ case WITHDRAWN:
+ log.info("simple fabric forwarding 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 forwarding 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(FabricNetwork fabricNetwork) {
+ return new ImmutableSet.Builder<Intent>()
+ .addAll(buildBrcIntents(fabricNetwork))
+ .addAll(buildUniIntents(fabricNetwork, hostsFromL2Network(fabricNetwork)))
+ .build();
+ }
+
+ // Build Boadcast Intents for a L2 Network.
+ private Set<SinglePointToMultiPointIntent> buildBrcIntents(FabricNetwork fabricNetwork) {
+ Set<Interface> interfaces = fabricNetwork.interfaces();
+ if (interfaces.size() < 2 || !fabricNetwork.isForward() || !fabricNetwork.isBroadcast()) {
+ return ImmutableSet.of();
+ }
+ Set<SinglePointToMultiPointIntent> brcIntents = Sets.newHashSet();
+ ResourceGroup resourceGroup = ResourceGroup.of(fabricNetwork.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(fabricNetwork.name(), "BCAST",
+ srcFcp.connectPoint(), MacAddress.BROADCAST);
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthDst(MacAddress.BROADCAST)
+ .build();
+ SinglePointToMultiPointIntent.Builder
+ intentBuilder = SinglePointToMultiPointIntent.builder()
+ .appId(appId)
+ .key(key)
+ .selector(selector)
+ .filteredIngressPoint(srcFcp)
+ .filteredEgressPoints(dstFcps)
+ .constraints(buildConstraints(L2NETWORK_CONSTRAINTS,
+ fabricNetwork.encapsulation()))
+ .priority(PRI_L2NETWORK_BROADCAST)
+ .resourceGroup(resourceGroup);
+ brcIntents.add(intentBuilder.build());
+ });
+ return brcIntents;
+ }
+
+ // Builds unicast Intents for a L2 Network.
+ private Set<MultiPointToSinglePointIntent> buildUniIntents(FabricNetwork fabricNetwork,
+ Set<Host> hosts) {
+ Set<Interface> interfaces = fabricNetwork.interfaces();
+ if (!fabricNetwork.isForward() || interfaces.size() < 2) {
+ return ImmutableSet.of();
+ }
+ Set<MultiPointToSinglePointIntent> uniIntents = Sets.newHashSet();
+ ResourceGroup resourceGroup = ResourceGroup.of(fabricNetwork.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(fabricNetwork.name(), "UNI", hostFcp.connectPoint(), host.mac());
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthDst(host.mac()).build();
+ MultiPointToSinglePointIntent.Builder
+ intentBuilder = MultiPointToSinglePointIntent.builder()
+ .appId(appId)
+ .key(key)
+ .selector(selector)
+ .filteredIngressPoints(srcFcps)
+ .filteredEgressPoint(hostFcp)
+ .constraints(buildConstraints(L2NETWORK_CONSTRAINTS,
+ fabricNetwork.encapsulation()))
+ .priority(PRI_L2NETWORK_UNICAST)
+ .resourceGroup(resourceGroup);
+ uniIntents.add(intentBuilder.build());
+ });
+
+ return uniIntents;
+ }
+
+ // Intent generate utilities
+
+ private Set<Host> hostsFromL2Network(FabricNetwork fabricNetwork) {
+ Set<Interface> interfaces = fabricNetwork.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, appId);
+ }
+
+ 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("Forwarding 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("Forwarding 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("Forwarding 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/impl/SimpleFabricManager.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/SimpleFabricManager.java
new file mode 100644
index 0000000..7a4e5d8
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/SimpleFabricManager.java
@@ -0,0 +1,669 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.simplefabric.impl;
+
+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.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv6;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.packet.ndp.NeighborSolicitation;
+import org.onosproject.app.ApplicationService;
+import org.onosproject.component.ComponentService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.event.ListenerRegistry;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Host;
+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.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.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.intf.InterfaceEvent;
+import org.onosproject.net.intf.InterfaceListener;
+import org.onosproject.net.intf.InterfaceService;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketService;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.onosproject.simplefabric.api.FabricNetwork;
+import org.onosproject.simplefabric.api.FabricRoute;
+import org.onosproject.simplefabric.api.FabricSubnet;
+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.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import static org.onosproject.simplefabric.api.Constants.ALLOW_ETH_ADDRESS_SELECTOR;
+import static org.onosproject.simplefabric.api.Constants.ALLOW_IPV6;
+import static org.onosproject.simplefabric.api.Constants.APP_ID;
+import static org.onosproject.simplefabric.api.Constants.IDLE_INTERVAL_MSEC;
+import static org.onosproject.simplefabric.api.Constants.REACTIVE_ALLOW_LINK_CP;
+import static org.onosproject.simplefabric.api.Constants.REACTIVE_HASHED_PATH_SELECTION;
+import static org.onosproject.simplefabric.api.Constants.REACTIVE_MATCH_IP_PROTO;
+import static org.onosproject.simplefabric.api.Constants.REACTIVE_SINGLE_TO_SINGLE;
+import static org.onosproject.simplefabric.util.RouteTools.createBinaryString;
+
+
+/**
+ * Reactive routing configuration manager.
+ */
+@Component(immediate = true, service = SimpleFabricService.class)
+public class SimpleFabricManager extends ListenerRegistry<SimpleFabricEvent, SimpleFabricListener>
+ implements SimpleFabricService {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected ApplicationService applicationService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected NetworkConfigService configService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected NetworkConfigRegistry registry;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected InterfaceService interfaceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected HostService hostService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected PacketService packetService;
+
+ // compoents to be activated within SimpleFabric
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected ComponentService componentService;
+
+ // SimpleFabric variables
+ private ApplicationId appId = null;
+
+ // fabric networks
+ private Set<FabricNetwork> fabricNetworks = new HashSet<>();
+ private Set<Interface> networkInterfaces = new HashSet<>();
+
+ // Subnet table
+ private Set<FabricSubnet> fabricSubnets = new HashSet<>();
+ private InvertedRadixTree<FabricSubnet> ip4SubnetTable =
+ new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+ private InvertedRadixTree<FabricSubnet> ip6SubnetTable =
+ new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+
+ // Fabric Route table
+ private Set<FabricRoute> fabricRoutes = new HashSet<>();
+ private InvertedRadixTree<FabricRoute> ip4BorderRouteTable =
+ new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+ private InvertedRadixTree<FabricRoute> ip6BorderRouteTable =
+ new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+
+ // Virtual gateway
+ 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 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, SimpleFabricRouting.class.getName());
+ if (ALLOW_ETH_ADDRESS_SELECTOR) {
+ componentService.activate(appId, SimpleFabricForwarding.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, SimpleFabricRouting.class.getName());
+ if (ALLOW_ETH_ADDRESS_SELECTOR) {
+ componentService.deactivate(appId, SimpleFabricForwarding.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 isDirty and refresh listeners 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;
+ }
+
+ // fabricNetworks
+ Set<FabricNetwork> newFabricNetworks = new HashSet<>();
+ Set<Interface> newInterfaces = new HashSet<>();
+ for (FabricNetwork newFabricNetworkConfig : config.fabricNetworks()) {
+ FabricNetwork newFabricNetwork = DefaultFabricNetwork.of(newFabricNetworkConfig);
+
+ // fill up interfaces and Hosts with active port only
+ for (String ifaceName : newFabricNetworkConfig.interfaceNames()) {
+ Interface iface = getInterfaceByName(ifaceName);
+ if (iface != null && deviceService.isAvailable(iface.connectPoint().deviceId())) {
+ newFabricNetwork.addInterface(iface);
+ newInterfaces.add(iface);
+ }
+ }
+ for (Host host : hostService.getHosts()) {
+ // consider host with ip only
+ if (!host.ipAddresses().isEmpty()) {
+ Interface iface = findAvailableDeviceHostInterface(host);
+ if (iface != null && newFabricNetwork.contains(iface)) {
+ newFabricNetwork.addHost(host);
+ }
+ }
+ }
+ newFabricNetwork.setDirty(true);
+
+ // update newFabricNetwork's isDirty flags if same entry already exists
+ for (FabricNetwork prevFabricNetwork : fabricNetworks) {
+ if (prevFabricNetwork.equals(newFabricNetwork)) {
+ newFabricNetwork.setDirty(prevFabricNetwork.isDirty());
+ break;
+ }
+ }
+ newFabricNetworks.add(newFabricNetwork);
+ }
+ if (!fabricNetworks.equals(newFabricNetworks)) {
+ fabricNetworks = newFabricNetworks;
+ dirty = true;
+ }
+ if (!networkInterfaces.equals(newInterfaces)) {
+ networkInterfaces = newInterfaces;
+ dirty = true;
+ }
+
+ // default Fabric Subnets
+ Set<FabricSubnet> newFabricSubnets = config.fabricSubnets();
+ InvertedRadixTree<FabricSubnet> newIp4SubnetTable =
+ new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+ InvertedRadixTree<FabricSubnet> newIp6SubnetTable =
+ new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+ Map<IpAddress, MacAddress> newVirtualGatewayIpMacMap = Maps.newConcurrentMap();
+ for (FabricSubnet subnet : newFabricSubnets) {
+ if (subnet.prefix().isIp4()) {
+ newIp4SubnetTable.put(createBinaryString(subnet.prefix()), subnet);
+ } else {
+ newIp6SubnetTable.put(createBinaryString(subnet.prefix()), subnet);
+ }
+ newVirtualGatewayIpMacMap.put(subnet.gatewayIp(), subnet.gatewayMac());
+ }
+ if (!fabricSubnets.equals(newFabricSubnets)) {
+ fabricSubnets = newFabricSubnets;
+ ip4SubnetTable = newIp4SubnetTable;
+ ip6SubnetTable = newIp6SubnetTable;
+ dirty = true;
+ }
+ if (!virtualGatewayIpMacMap.equals(newVirtualGatewayIpMacMap)) {
+ virtualGatewayIpMacMap = newVirtualGatewayIpMacMap;
+ dirty = true;
+ }
+
+ // fabricRoutes config handling
+ Set<FabricRoute> newFabricRoutes = config.fabricRoutes();
+ if (!fabricRoutes.equals(newFabricRoutes)) {
+ InvertedRadixTree<FabricRoute> newIp4BorderRouteTable =
+ new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+ InvertedRadixTree<FabricRoute> newIp6BorderRouteTable =
+ new ConcurrentInvertedRadixTree<>(new DefaultByteArrayNodeFactory());
+ for (FabricRoute route : newFabricRoutes) {
+ if (route.prefix().isIp4()) {
+ newIp4BorderRouteTable.put(createBinaryString(route.prefix()), route);
+ } else {
+ newIp6BorderRouteTable.put(createBinaryString(route.prefix()), route);
+ }
+ }
+ fabricRoutes = newFabricRoutes;
+ 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 appId() {
+ if (appId == null) {
+ appId = coreService.registerApplication(APP_ID);
+ }
+ return appId;
+ }
+
+ @Override
+ public Collection<FabricNetwork> fabricNetworks() {
+ return ImmutableSet.copyOf(fabricNetworks);
+ }
+
+ @Override
+ public Set<FabricSubnet> defaultFabricSubnets() {
+ return ImmutableSet.copyOf(fabricSubnets);
+ }
+
+ @Override
+ public Set<FabricRoute> fabricRoutes() {
+ return ImmutableSet.copyOf(fabricRoutes);
+ }
+
+ @Override
+ public boolean isVirtualGatewayMac(MacAddress mac) {
+ return virtualGatewayIpMacMap.containsValue(mac);
+ }
+
+ @Override
+ public boolean isFabricNetworkInterface(Interface intf) {
+ return networkInterfaces.contains(intf);
+ }
+
+ @Override
+ public MacAddress vMacForIp(IpAddress ip) {
+ return virtualGatewayIpMacMap.get(ip);
+ }
+
+ @Override
+ public FabricNetwork fabricNetwork(ConnectPoint port, VlanId vlanId) {
+ for (FabricNetwork fabricNetwork : fabricNetworks) {
+ if (fabricNetwork.contains(port, vlanId)) {
+ return fabricNetwork;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public FabricNetwork fabricNetwork(String name) {
+ for (FabricNetwork fabricNetwork : fabricNetworks) {
+ if (fabricNetwork.name().equals(name)) {
+ return fabricNetwork;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public FabricSubnet fabricSubnet(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 FabricRoute fabricRoute(IpAddress ip) {
+ // ASSUME: ipAddress is out of fabricSubnet
+ 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 hostInterface(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) {
+ FabricSubnet fabricSubnet = fabricSubnet(ip);
+ if (fabricSubnet == null) {
+ log.warn("simple fabric request mac failed for unknown fabricSubnet: {}", ip);
+ return false;
+ }
+ FabricNetwork fabricNetwork = fabricNetwork(fabricSubnet.networkName());
+ if (fabricNetwork == null) {
+ log.warn("simple fabric request mac failed for unknown fabricNetwork name {}: {}",
+ fabricSubnet.networkName(), ip);
+ return false;
+ }
+ log.debug("simple fabric send request mac fabricNetwork {}: {}", fabricNetwork.name(), ip);
+ for (Interface iface : fabricNetwork.interfaces()) {
+ Ethernet neighbourReq;
+ if (ip.isIp4()) {
+ neighbourReq = ARP.buildArpRequest(fabricSubnet.gatewayMac().toBytes(),
+ fabricSubnet.gatewayIp().toOctets(),
+ ip.toOctets(),
+ iface.vlan().toShort());
+ } else {
+ byte[] soliciteIp = IPv6.getSolicitNodeAddress(ip.toOctets());
+ neighbourReq = NeighborSolicitation.buildNdpSolicit(
+ ip.toOctets(),
+ fabricSubnet.gatewayIp().toOctets(),
+ soliciteIp,
+ fabricSubnet.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=" + ALLOW_IPV6);
+ out.println(" ALLOW_ETH_ADDRESS_SELECTOR=" + ALLOW_ETH_ADDRESS_SELECTOR);
+ out.println(" REACTIVE_SINGLE_TO_SINGLE=" + REACTIVE_SINGLE_TO_SINGLE);
+ out.println(" REACTIVE_ALLOW_LINK_CP=" + REACTIVE_ALLOW_LINK_CP);
+ out.println(" REACTIVE_HASHED_PATH_SELECTION=" + REACTIVE_HASHED_PATH_SELECTION);
+ out.println(" REACTIVE_MATCH_IP_PROTO=" + REACTIVE_MATCH_IP_PROTO);
+ out.println("");
+ out.println("SimpleFabricAppId:");
+ out.println(" " + appId());
+ out.println("");
+ out.println("fabricNetworks:");
+ for (FabricNetwork fabricNetwork : fabricNetworks()) {
+ out.println(" " + fabricNetwork);
+ }
+ out.println("");
+ out.println("fabricSubnets:");
+ for (FabricSubnet fabricIpSubnet : defaultFabricSubnets()) {
+ out.println(" " + fabricIpSubnet);
+ }
+ out.println("");
+ out.println("fabricRoutes:");
+ for (FabricRoute route : fabricRoutes()) {
+ 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/impl/SimpleFabricNeighbour.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/SimpleFabricNeighbour.java
new file mode 100644
index 0000000..5d7c8b0
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/SimpleFabricNeighbour.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.simplefabric.impl;
+
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.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.FabricNetwork;
+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)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected HostService hostService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected InterfaceService interfaceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected NeighbourResolutionService neighbourService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ 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.appId();
+ 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.isFabricNetworkInterface(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.vMacForIp(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
+
+ FabricNetwork fabricNetwork = simpleFabric.fabricNetwork(context.inPort(), context.vlan());
+ if (fabricNetwork != 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 : fabricNetwork.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
+ FabricNetwork fabricNetwork = simpleFabric.fabricNetwork(context.inPort(), context.vlan());
+ if (fabricNetwork != null) {
+ // TODO: need to check and update simpleFabric.DefaultFabricNetwork
+ MacAddress mac = simpleFabric.vMacForIp(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.hostInterface(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 fabricNetwork: {} {}",
+ 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/impl/SimpleFabricRouting.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/SimpleFabricRouting.java
new file mode 100644
index 0000000..cbca9a5
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/SimpleFabricRouting.java
@@ -0,0 +1,1003 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.simplefabric.impl;
+
+import com.google.common.collect.ImmutableList;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.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.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.Ip6Prefix;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.EncapsulationType;
+import org.onosproject.net.FilteredConnectPoint;
+import org.onosproject.net.Host;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+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.constraint.EncapsulationConstraint;
+import org.onosproject.net.intent.constraint.HashedPathSelectionConstraint;
+import org.onosproject.net.intent.constraint.PartialFailureConstraint;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.intf.InterfaceService;
+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.FabricNetwork;
+import org.onosproject.simplefabric.api.FabricSubnet;
+import org.onosproject.simplefabric.api.FabricRoute;
+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;
+
+import static org.onosproject.simplefabric.api.Constants.ALLOW_ETH_ADDRESS_SELECTOR;
+import static org.onosproject.simplefabric.api.Constants.ALLOW_IPV6;
+import static org.onosproject.simplefabric.api.Constants.PRI_REACTIVE_BORDER_BASE;
+import static org.onosproject.simplefabric.api.Constants.PRI_REACTIVE_BORDER_FORWARD;
+import static org.onosproject.simplefabric.api.Constants.PRI_REACTIVE_BORDER_INTERCEPT;
+import static org.onosproject.simplefabric.api.Constants.PRI_REACTIVE_BORDER_STEP;
+import static org.onosproject.simplefabric.api.Constants.PRI_REACTIVE_LOCAL_FORWARD;
+import static org.onosproject.simplefabric.api.Constants.PRI_REACTIVE_LOCAL_INTERCEPT;
+import static org.onosproject.simplefabric.api.Constants.REACTIVE_ALLOW_LINK_CP;
+import static org.onosproject.simplefabric.api.Constants.ROUTING_APP_ID;
+import static org.onosproject.simplefabric.api.Constants.REACTIVE_HASHED_PATH_SELECTION;
+import static org.onosproject.simplefabric.api.Constants.REACTIVE_MATCH_IP_PROTO;
+import static org.onosproject.simplefabric.api.Constants.REACTIVE_SINGLE_TO_SINGLE;
+
+
+/**
+ * SimpleFabricRouting handles Routing.
+ */
+@Component(immediate = true, enabled = false)
+public class SimpleFabricRouting {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+ private ApplicationId appId;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected PacketService packetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected InterfaceService interfaceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected LinkService linkService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected HostService hostService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected IntentService intentService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected FlowRuleService flowRuleService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ 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 InternalRoutingProcessor processor = new InternalRoutingProcessor();
+
+ @Activate
+ public void activate() {
+ appId = coreService.registerApplication(ROUTING_APP_ID);
+ log.info("simple fabric routing starting with app id {}", appId.toString());
+
+ // NOTE: may not clear at init for MIGHT generate pending_remove garbages
+ // use flush event from simple fabric cli command
+
+ if (REACTIVE_HASHED_PATH_SELECTION) {
+ reactiveConstraints = ImmutableList.of(new PartialFailureConstraint(),
+ new HashedPathSelectionConstraint());
+ } else {
+ reactiveConstraints = ImmutableList.of(new PartialFailureConstraint());
+ }
+
+ processor = new InternalRoutingProcessor();
+ packetService.addProcessor(processor, PacketProcessor.director(2));
+ simpleFabric.addListener(simpleFabricListener);
+
+ registerIntercepts();
+ refreshIntercepts();
+
+ log.info("simple fabric routing started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("simple fabric 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(appId);
+
+ processor = null;
+
+ log.info("simple fabric 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, appId);
+
+ if (ALLOW_IPV6) {
+ packetService.requestPackets(
+ DefaultTrafficSelector.builder().matchEthType(Ethernet.TYPE_IPV6).build(),
+ PacketPriority.REACTIVE, appId);
+ }
+
+ log.info("simple fabric 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, appId);
+
+ if (ALLOW_IPV6) {
+ packetService.cancelPackets(
+ DefaultTrafficSelector.builder().matchEthType(Ethernet.TYPE_IPV6).build(),
+ PacketPriority.REACTIVE, appId);
+ }
+
+ log.info("simple fabric routing ip packet intercepts stopped");
+ }
+
+ /**
+ * Refresh device flow rules for intercepts on local fabricSubnets.
+ */
+ private void refreshIntercepts() {
+ Set<FlowRule> newInterceptFlowRules = new HashSet<>();
+ for (Device device : deviceService.getAvailableDevices()) {
+ for (FabricSubnet subnet : simpleFabric.defaultFabricSubnets()) {
+ newInterceptFlowRules.add(generateInterceptFlowRule(true, device.id(), subnet.prefix()));
+ // check if this devices has the fabricSubnet, then add ip broadcast flue rule
+ FabricNetwork fabricNetwork = simpleFabric.fabricNetwork(subnet.networkName());
+ if (fabricNetwork != null && fabricNetwork.contains(device.id())) {
+ newInterceptFlowRules.add(generateLocalSubnetIpBctFlowRule(device.id(), subnet.prefix(),
+ fabricNetwork));
+ }
+ // JUST FOR FLOW RULE TEST ONLY
+ //newInterceptFlowRules.add(generateTestFlowRule(device.id(), subnet.ipPrefix()));
+ }
+ for (FabricRoute route : simpleFabric.fabricRoutes()) {
+ 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 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(appId)
+ .makePermanent()
+ .forTable(0).build();
+ return rule;
+ }
+
+ private FlowRule generateLocalSubnetIpBctFlowRule(DeviceId deviceId, IpPrefix prefix, FabricNetwork fabricNetwork) {
+ 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 : fabricNetwork.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(appId)
+ .makePermanent()
+ .forTable(0).build();
+ return rule;
+ }
+
+ /**
+ * Refresh routes by examining network resource status.
+ */
+ private void refreshRouteIntents() {
+ for (Intent entry : intentService.getIntents()) {
+ if (!appId.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.fabricNetwork(intent.egressPoint(), VlanId.NONE) != null ||
+ (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 (REACTIVE_SINGLE_TO_SINGLE && !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.fabricNetwork(cp.connectPoint(), VlanId.NONE) != null ||
+ (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(appId)
+ .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 (appId.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;
+ }
+ }
+ }
+
+ /**
+ * Internal Packet Handling.
+ */
+ private class InternalRoutingProcessor 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.vMacForIp(dstIp);
+ if (mac == null || !simpleFabric.isVirtualGatewayMac(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.isVirtualGatewayMac(ethPkt.getDestinationMAC());
+
+ // check subnet local or route
+ FabricSubnet srcSubnet = simpleFabric.fabricSubnet(srcIp);
+ if (srcSubnet == null) {
+ FabricRoute route = simpleFabric.fabricRoute(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();
+ }
+ FabricSubnet dstSubnet = simpleFabric.fabricSubnet(dstIp);
+ if (dstSubnet == null) {
+ FabricRoute route = simpleFabric.fabricRoute(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 (ALLOW_ETH_ADDRESS_SELECTOR && dstSubnet.equals(srcSubnet)) {
+ // NOTE: if ALLOW_ETH_ADDRESS_SELECTOR=false; isForward is always false
+ FabricNetwork fabricNetwork = simpleFabric.fabricNetwork(dstSubnet.networkName());
+ treatmentSrcMac = ethPkt.getSourceMAC();
+ if (fabricNetwork != null && fabricNetwork.isForward()) {
+ // 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 DefaultFabricNetwork
+ 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 submitted 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.fabricNetwork(srcCp, VlanId.NONE) != null ||
+ (REACTIVE_ALLOW_LINK_CP && !linkService.getIngressLinks(srcCp).isEmpty()))) {
+ log.warn("NO REGI for srcCp not in DefaultFabricNetwork; 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 && 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 (REACTIVE_SINGLE_TO_SINGLE && srcPrefix.prefixLength() > 0) {
+ selector.matchIPSrc(srcPrefix);
+ }
+ if (dstPrefix.prefixLength() > 0) {
+ selector.matchIPDst(dstPrefix);
+ }
+ if (ipProto != 0 && REACTIVE_MATCH_IP_PROTO) {
+ selector.matchIPProtocol(ipProto);
+ }
+ } else {
+ selector.matchEthType(Ethernet.TYPE_IPV6);
+ if (REACTIVE_SINGLE_TO_SINGLE && srcPrefix.prefixLength() > 0) {
+ selector.matchIPv6Src(srcPrefix);
+ }
+ if (dstPrefix.prefixLength() > 0) {
+ selector.matchIPv6Dst(dstPrefix);
+ }
+ if (ipProto != 0 && REACTIVE_MATCH_IP_PROTO) {
+ selector.matchIPProtocol(ipProto);
+ }
+ }
+
+ Key key;
+ String keyProtoTag = "";
+ if (REACTIVE_MATCH_IP_PROTO) {
+ keyProtoTag = "-p" + ipProto;
+ }
+ if (REACTIVE_SINGLE_TO_SINGLE) {
+ // allocate intent per (srcPrefix, dstPrefix)
+ key = Key.of(srcPrefix.toString() + "-to-" + dstPrefix.toString() + keyProtoTag, appId);
+ } else {
+ // allocate intent per (srcDeviceId, dstPrefix)
+ key = Key.of(srcCp.deviceId().toString() + "-to-" + dstPrefix.toString() + keyProtoTag, appId);
+ }
+
+ // 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(appId)
+ .selector(selector.build())
+ .treatment(treatment)
+ .filteredIngressPoints(ingressPoints)
+ .filteredEgressPoint(new FilteredConnectPoint(egressPoint))
+ .priority(priority)
+ .constraints(buildConstraints(reactiveConstraints, encap))
+ .build();
+ log.info("submit 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 (FabricRoute route : simpleFabric.fabricRoutes()) {
+ 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 PRI_REACTIVE_LOCAL_FORWARD;
+ } else { // isInterncept
+ return PRI_REACTIVE_LOCAL_INTERCEPT;
+ }
+ } else { // -> dst:boarderRouteNextHop
+ int offset;
+ if (isForward) {
+ offset = PRI_REACTIVE_BORDER_FORWARD;
+ } else { // isIntercept
+ offset = PRI_REACTIVE_BORDER_INTERCEPT;
+ }
+ return PRI_REACTIVE_BORDER_BASE
+ + borderRoutePrefixLength * 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("Routing Route Intents:\n");
+ for (Intent entry : intentService.getIntents()) {
+ if (appId.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("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("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 (appId.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/impl/package-info.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/package-info.java
new file mode 100644
index 0000000..d190b08
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Simple fabric implementation package.
+ */
+package org.onosproject.simplefabric.impl;
\ No newline at end of file
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/java/org/onosproject/simplefabric/util/RouteTools.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/util/RouteTools.java
new file mode 100644
index 0000000..d02fed2
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/util/RouteTools.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * 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.util;
+
+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/util/package-info.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/util/package-info.java
new file mode 100644
index 0000000..9b3dde3
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/util/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Simple fabric utility package.
+ */
+package org.onosproject.simplefabric.util;
\ No newline at end of file
diff --git a/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/web/SimpleFabricWebApplication.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/web/SimpleFabricWebApplication.java
new file mode 100644
index 0000000..960b4df
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/web/SimpleFabricWebApplication.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.simplefabric.web;
+
+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/web/SimpleFabricWebResource.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/web/SimpleFabricWebResource.java
new file mode 100644
index 0000000..cc68d6b
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/web/SimpleFabricWebResource.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.simplefabric.web;
+
+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/web/package-info.java b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/web/package-info.java
new file mode 100644
index 0000000..596ded0
--- /dev/null
+++ b/apps/simplefabric/app/src/main/java/org/onosproject/simplefabric/web/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Simple fabric web package.
+ */
+package org.onosproject.simplefabric.web;
\ No newline at end of file
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..2aad546
--- /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.cli.SimpleFabricCommand"/>
+ <completers>
+ <ref component-id="SimpleFabricCommandCompleter"/>
+ </completers>
+ </command>
+ </command-bundle>
+
+ <bean id="SimpleFabricCommandCompleter" class="org.onosproject.simplefabric.cli.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..ebcac0e
--- /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.web.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>
diff --git a/apps/simplefabric/app/src/test/java/org/onosproject/simplefabric/impl/DefaultFabricNetworkTest.java b/apps/simplefabric/app/src/test/java/org/onosproject/simplefabric/impl/DefaultFabricNetworkTest.java
new file mode 100644
index 0000000..57152e7
--- /dev/null
+++ b/apps/simplefabric/app/src/test/java/org/onosproject/simplefabric/impl/DefaultFabricNetworkTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.simplefabric.impl;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.testing.EqualsTester;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.EncapsulationType;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.simplefabric.api.FabricNetwork;
+
+import java.util.List;
+import java.util.Set;
+
+import static junit.framework.TestCase.assertEquals;
+
+/**
+ * Unit tests for the default fabric network class.
+ */
+public final class DefaultFabricNetworkTest {
+
+ private static final String NAME_1 = "network1";
+ private static final String NAME_2 = "network2";
+
+ private static final String INTF_NAMES_1_1 = "h11";
+ private static final String INTF_NAMES_1_2 = "h12";
+ private static final String INTF_NAMES_2_1 = "h21";
+ private static final String INTF_NAMES_2_2 = "h22";
+
+ private static final Set<String> INTF_NAME_SET_1 =
+ ImmutableSet.of(INTF_NAMES_1_1, INTF_NAMES_1_2);
+ private static final Set<String> INTF_NAME_SET_2 =
+ ImmutableSet.of(INTF_NAMES_2_1, INTF_NAMES_2_2);
+
+ private static final EncapsulationType ENCAP_TYPE_1 = EncapsulationType.NONE;
+ private static final EncapsulationType ENCAP_TYPE_2 = EncapsulationType.NONE;
+
+ private static final boolean IS_FORWARD_1 = false;
+ private static final boolean IS_FORWARD_2 = true;
+ private static final boolean IS_BROADCAST_1 = false;
+ private static final boolean IS_BROADCAST_2 = true;
+
+ private FabricNetwork fabricNetwork1;
+ private FabricNetwork sameAsFabricNetwork1;
+ private FabricNetwork fabricNetwork2;
+
+ private static Interface createInterface(int index) {
+
+ String name = "INTF_NAME_" + index;
+ ConnectPoint cp = ConnectPoint.fromString("of:0011223344556677/" + index);
+ InterfaceIpAddress intfIp1 = InterfaceIpAddress.valueOf("10.10.10." + index + "/32");
+ InterfaceIpAddress intfIp2 = InterfaceIpAddress.valueOf("20.20.20." + index + "/32");
+ List<InterfaceIpAddress> intfIps = ImmutableList.of(intfIp1, intfIp2);
+ MacAddress mac = MacAddress.valueOf("00:00:00:00:00:00");
+ VlanId vlanId = VlanId.NONE;
+
+ return new Interface(name, cp, intfIps, mac, vlanId);
+ }
+
+ /**
+ * Initial setup for this unit test.
+ */
+ @Before
+ public void setUp() {
+
+ fabricNetwork1 = DefaultFabricNetwork.builder()
+ .name(NAME_1)
+ .interfaceNames(INTF_NAME_SET_1)
+ .encapsulation(ENCAP_TYPE_1)
+ .forward(IS_FORWARD_1)
+ .broadcast(IS_BROADCAST_1)
+ .build();
+
+ sameAsFabricNetwork1 = DefaultFabricNetwork.builder()
+ .name(NAME_1)
+ .interfaceNames(INTF_NAME_SET_1)
+ .encapsulation(ENCAP_TYPE_1)
+ .forward(IS_FORWARD_1)
+ .broadcast(IS_BROADCAST_1)
+ .build();
+
+ fabricNetwork2 = DefaultFabricNetwork.builder()
+ .name(NAME_2)
+ .interfaceNames(INTF_NAME_SET_2)
+ .encapsulation(ENCAP_TYPE_2)
+ .forward(IS_FORWARD_2)
+ .broadcast(IS_BROADCAST_2)
+ .build();
+ }
+
+ /**
+ * Tests object equality.
+ */
+ @Test
+ public void testEquality() {
+ new EqualsTester().addEqualityGroup(fabricNetwork1, sameAsFabricNetwork1)
+ .addEqualityGroup(fabricNetwork2)
+ .testEquals();
+ }
+
+ /**
+ * Test object construction.
+ */
+ @Test
+ public void testConstruction() {
+ FabricNetwork network = fabricNetwork1;
+
+ assertEquals(network.name(), NAME_1);
+ assertEquals(network.interfaceNames(), INTF_NAME_SET_1);
+ assertEquals(network.encapsulation(), ENCAP_TYPE_1);
+ assertEquals(network.isForward(), IS_FORWARD_1);
+ assertEquals(network.isBroadcast(), IS_BROADCAST_1);
+ }
+}
diff --git a/apps/simplefabric/app/src/test/java/org/onosproject/simplefabric/impl/DefaultFabricRouteTest.java b/apps/simplefabric/app/src/test/java/org/onosproject/simplefabric/impl/DefaultFabricRouteTest.java
new file mode 100644
index 0000000..8a39179
--- /dev/null
+++ b/apps/simplefabric/app/src/test/java/org/onosproject/simplefabric/impl/DefaultFabricRouteTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.simplefabric.impl;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.simplefabric.api.FabricRoute;
+import org.onosproject.simplefabric.api.FabricRoute.Source;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+
+/**
+ * Unit tests for the default fabric router.
+ */
+public final class DefaultFabricRouteTest {
+
+ private static final Source SOURCE_1 = Source.STATIC;
+ private static final Source SOURCE_2 = Source.BGP;
+
+ private static final IpPrefix IP_PREFIX_1 = IpPrefix.valueOf("10.10.10.1/32");
+ private static final IpPrefix IP_PREFIX_2 = IpPrefix.valueOf("20.20.20.2/32");
+
+ private static final IpAddress NEXT_HOP_1 = IpAddress.valueOf("10.10.10.1");
+ private static final IpAddress NEXT_HOP_2 = IpAddress.valueOf("20.20.20.2");
+
+ private static final NodeId SOURCE_NODE_1 = NodeId.nodeId("1");
+ private static final NodeId SOURCE_NODE_2 = NodeId.nodeId("2");
+
+ private FabricRoute fabricRoute1;
+ private FabricRoute sameAsFabricRoute1;
+ private FabricRoute fabricRoute2;
+
+ /**
+ * Tests class immutability.
+ */
+ @Test
+ public void testImmutability() {
+ assertThatClassIsImmutable(DefaultFabricRoute.class);
+ }
+
+ /**
+ * Initial setup for this unit test.
+ */
+ @Before
+ public void setUp() {
+ fabricRoute1 = DefaultFabricRoute.builder()
+ .source(SOURCE_1)
+ .prefix(IP_PREFIX_1)
+ .nextHop(NEXT_HOP_1)
+ .sourceNode(SOURCE_NODE_1)
+ .build();
+
+ sameAsFabricRoute1 = DefaultFabricRoute.builder()
+ .source(SOURCE_1)
+ .prefix(IP_PREFIX_1)
+ .nextHop(NEXT_HOP_1)
+ .sourceNode(SOURCE_NODE_1)
+ .build();
+
+ fabricRoute2 = DefaultFabricRoute.builder()
+ .source(SOURCE_2)
+ .prefix(IP_PREFIX_2)
+ .nextHop(NEXT_HOP_2)
+ .sourceNode(SOURCE_NODE_2)
+ .build();
+ }
+
+ /**
+ * Tests object equality.
+ */
+ @Test
+ public void testEquality() {
+ new EqualsTester().addEqualityGroup(fabricRoute1, sameAsFabricRoute1)
+ .addEqualityGroup(fabricRoute2)
+ .testEquals();
+ }
+
+ /**
+ * Test object construction.
+ */
+ @Test
+ public void testConstruction() {
+ FabricRoute route = fabricRoute1;
+
+ assertEquals(route.source(), SOURCE_1);
+ assertEquals(route.prefix(), IP_PREFIX_1);
+ assertEquals(route.nextHop(), NEXT_HOP_1);
+ assertEquals(route.sourceNode(), SOURCE_NODE_1);
+ }
+}
diff --git a/apps/simplefabric/app/src/test/java/org/onosproject/simplefabric/impl/DefaultFabricSubnetTest.java b/apps/simplefabric/app/src/test/java/org/onosproject/simplefabric/impl/DefaultFabricSubnetTest.java
new file mode 100644
index 0000000..82efd14
--- /dev/null
+++ b/apps/simplefabric/app/src/test/java/org/onosproject/simplefabric/impl/DefaultFabricSubnetTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.simplefabric.impl;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.EncapsulationType;
+import org.onosproject.simplefabric.api.FabricSubnet;
+
+import static junit.framework.TestCase.assertEquals;
+
+/**
+ * Unit tests for the default fabric IP subnet class.
+ */
+public final class DefaultFabricSubnetTest {
+
+ private static final IpPrefix IP_PREFIX_1 = IpPrefix.valueOf("10.10.10.11/32");
+ private static final IpPrefix IP_PREFIX_2 = IpPrefix.valueOf("20.20.20.11/32");
+
+ private static final IpAddress GATEWAY_IP_1 = IpAddress.valueOf("10.10.10.1");
+ private static final IpAddress GATEWAY_IP_2 = IpAddress.valueOf("20.20.20.1");
+
+ private static final MacAddress GATEWAY_MAC_1 = MacAddress.valueOf("00:11:22:33:44:55");
+ private static final MacAddress GATEWAY_MAC_2 = MacAddress.valueOf("11:22:33:44:55:66");
+
+ private static final EncapsulationType ENCAP_TYPE_1 = EncapsulationType.NONE;
+ private static final EncapsulationType ENCAP_TYPE_2 = EncapsulationType.NONE;
+
+ private static final String NETWORK_NAME_1 = "sonaFabric1";
+ private static final String NETWORK_NAME_2 = "sonaFabric2";
+
+ private FabricSubnet subnet1;
+ private FabricSubnet sameAsSubnet1;
+ private FabricSubnet subnet2;
+
+ /**
+ * Initial setup for this unit test.
+ */
+ @Before
+ public void setUp() {
+
+ subnet1 = DefaultFabricSubnet.builder()
+ .prefix(IP_PREFIX_1)
+ .gatewayIp(GATEWAY_IP_1)
+ .gatewayMac(GATEWAY_MAC_1)
+ .encapsulation(ENCAP_TYPE_1)
+ .networkName(NETWORK_NAME_1)
+ .build();
+
+ sameAsSubnet1 = DefaultFabricSubnet.builder()
+ .prefix(IP_PREFIX_1)
+ .gatewayIp(GATEWAY_IP_1)
+ .gatewayMac(GATEWAY_MAC_1)
+ .encapsulation(ENCAP_TYPE_1)
+ .networkName(NETWORK_NAME_1)
+ .build();
+
+ subnet2 = DefaultFabricSubnet.builder()
+ .prefix(IP_PREFIX_2)
+ .gatewayIp(GATEWAY_IP_2)
+ .gatewayMac(GATEWAY_MAC_2)
+ .encapsulation(ENCAP_TYPE_2)
+ .networkName(NETWORK_NAME_2)
+ .build();
+ }
+
+ /**
+ * Tests object equality.
+ */
+ @Test
+ public void testEquality() {
+ new EqualsTester().addEqualityGroup(subnet1, sameAsSubnet1)
+ .addEqualityGroup(subnet2)
+ .testEquals();
+ }
+
+ /**
+ * Test object construction.
+ */
+ @Test
+ public void testConstruction() {
+ FabricSubnet subnet = subnet1;
+
+ assertEquals(subnet.prefix(), IP_PREFIX_1);
+ assertEquals(subnet.gatewayIp(), GATEWAY_IP_1);
+ assertEquals(subnet.gatewayMac(), GATEWAY_MAC_1);
+ assertEquals(subnet.encapsulation(), ENCAP_TYPE_1);
+ assertEquals(subnet.networkName(), NETWORK_NAME_1);
+ }
+}