Refactor: split api from single bundle for openstacknetworking
Change-Id: I8d94476d04bea1c0440e9735f519fdca3b1bd77d
diff --git a/apps/openstacknetworking/app/BUCK b/apps/openstacknetworking/app/BUCK
new file mode 100644
index 0000000..b4c5f92
--- /dev/null
+++ b/apps/openstacknetworking/app/BUCK
@@ -0,0 +1,44 @@
+include_defs('//apps/openstacknetworking/openstack4j.bucklet')
+
+COMPILE_DEPS = [
+ '//lib:CORE_DEPS',
+ '//lib:JACKSON',
+ '//lib:KRYO',
+ '//core/store/serializers:onos-core-serializers',
+ '//lib:org.apache.karaf.shell.console',
+ '//lib:javax.ws.rs-api',
+ '//utils/rest:onlab-rest',
+ '//cli:onos-cli',
+ '//apps/openstacknode/api:onos-apps-openstacknode-api',
+ '//apps/openstacknetworking/api:onos-apps-openstacknetworking-api',
+ '//lib:openstack4j-core',
+ '//lib:openstack4j-http-connector',
+ '//lib:openstack4j-httpclient',
+ '//lib:json-patch',
+ '//lib:jackson-coreutils',
+ '//lib:btf',
+ '//lib:msg-simple',
+ '//lib:snakeyaml',
+]
+
+TEST_DEPS = [
+ '//lib:TEST_ADAPTERS',
+ '//core/api:onos-api-tests',
+ '//core/common:onos-core-common-tests',
+ '//web/api:onos-rest-tests',
+ '//lib:TEST_REST',
+]
+
+osgi_jar_with_tests (
+ deps = COMPILE_DEPS,
+ test_deps = TEST_DEPS,
+ web_context = '/onos/openstacknetworking',
+ api_title = 'OpenStack Networking API',
+ api_version = '1.0',
+ api_description = 'REST API for OpenStack Networking',
+ api_package = 'org.onosproject.openstacknetworking.web',
+
+ # dependency embedding
+ import_packages = INCLUDE_PACKAGES + ',' + EXCLUDE_PACKAGES + ',' + ALL_PACKAGES,
+ bundle_classpath = get_openstack4j_deps_path() + get_jackson_deps_path()
+)
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/app.xml b/apps/openstacknetworking/app/app.xml
new file mode 100644
index 0000000..cb178ed
--- /dev/null
+++ b/apps/openstacknetworking/app/app.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<app name="org.onosproject.openstacknetworking" origin="ON.Lab" version="${project.version}"
+ category="Utility" url="https://wiki.onosproject.org/display/ONOS/SONA%3A+DC+Network+Virtualization"
+ title="OpenStack Networking App" features="${project.artifactId}" apps="org.onosproject.openstacknode"
+ featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features">
+ <description>${project.description}</description>
+ <artifact>mvn:${project.groupId}/onos-apps-openstacknetworking-api/${project.version}</artifact>
+ <artifact>mvn:${project.groupId}/onos-apps-openstacknetworking-app/${project.version}</artifact>
+</app>
diff --git a/apps/openstacknetworking/app/features.xml b/apps/openstacknetworking/app/features.xml
new file mode 100644
index 0000000..51cb23f
--- /dev/null
+++ b/apps/openstacknetworking/app/features.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+ ~ 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.
+ -->
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}">
+ <feature name="${project.artifactId}" version="${project.version}"
+ description="${project.description}">
+ <feature>onos-api</feature>
+ <bundle>mvn:${project.groupId}/onos-apps-openstacknetworking-api/${project.version}</bundle>
+ <bundle>mvn:${project.groupId}/onos-apps-openstacknetworking-app/${project.version}</bundle>
+ </feature>
+</features>
diff --git a/apps/openstacknetworking/app/pom.xml b/apps/openstacknetworking/app/pom.xml
new file mode 100644
index 0000000..0658518
--- /dev/null
+++ b/apps/openstacknetworking/app/pom.xml
@@ -0,0 +1,279 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-apps-openstacknetworking</artifactId>
+ <version>1.13.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>onos-apps-openstacknetworking-app</artifactId>
+ <packaging>bundle</packaging>
+
+ <description>SONA Openstack Networking Application</description>
+
+ <properties>
+ <web.context>/onos/openstacknetworking</web.context>
+ <api.version>1.0.0</api.version>
+ <api.title>ONOS OpenStack Networking REST API</api.title>
+ <api.description>
+ APIs for interacting with OpenStack Neutron ONOS driver.
+ </api.description>
+ <api.package>org.onosproject.openstacknetworking.web</api.package>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-apps-openstacknetworking-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core-serializers</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-cli</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.karaf.shell</groupId>
+ <artifactId>org.apache.karaf.shell.console</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.scr.annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-rest</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-rest</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-osgi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-misc</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core-common</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-junit</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-api</artifactId>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core-common</artifactId>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-apps-openstacknode-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-common</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.pacesys</groupId>
+ <artifactId>openstack4j-core</artifactId>
+ <version>${openstack4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.pacesys.openstack4j.connectors</groupId>
+ <artifactId>openstack4j-http-connector</artifactId>
+ <version>${openstack4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.pacesys.openstack4j.connectors</groupId>
+ <artifactId>openstack4j-httpclient</artifactId>
+ <version>${openstack4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.github.fge</groupId>
+ <artifactId>json-patch</artifactId>
+ <version>${json-patch.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.github.fge</groupId>
+ <artifactId>jackson-coreutils</artifactId>
+ <version>${jackson-coreutils.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.github.fge</groupId>
+ <artifactId>btf</artifactId>
+ <version>${btf.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.github.fge</groupId>
+ <artifactId>msg-simple</artifactId>
+ <version>${msg-simple.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.yaml</groupId>
+ <artifactId>snakeyaml</artifactId>
+ <version>${snakeyaml.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.core</groupId>
+ <artifactId>jersey-client</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework</groupId>
+ <artifactId>jersey-test-framework-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+ <artifactId>jersey-test-framework-provider-jetty</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onlab-osgi</artifactId>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-rest</artifactId>
+ <version>${project.version}</version>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-scr-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>generate-scr-srcdescriptor</id>
+ <goals>
+ <goal>scr</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <supportedProjectTypes>
+ <supportedProjectType>bundle</supportedProjectType>
+ <supportedProjectType>war</supportedProjectType>
+ </supportedProjectTypes>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <_wab>src/main/webapp/</_wab>
+ <Include-Resource>
+ WEB-INF/classes/apidoc/swagger.json=target/swagger.json,
+ {maven-resources}
+ </Include-Resource>
+ <Bundle-SymbolicName>
+ ${project.groupId}.${project.artifactId}
+ </Bundle-SymbolicName>
+ <Web-ContextPath>${web.context}</Web-ContextPath>
+ <Import-Package>
+ !org.apache.http.*,
+ !com.fasterxml.jackson.dataformat.*,
+ !javax.annotation,
+ *,org.glassfish.jersey.servlet
+ </Import-Package>
+ <Embed-Dependency>
+ openstack4j-core,
+ openstack4j-http-connector,
+ openstack4j-httpclient,
+ json-patch,
+ jackson-coreutils,
+ btf,
+ msg-simple,
+ snakeyaml
+ </Embed-Dependency>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/DeleteExternalPeerRouterCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/DeleteExternalPeerRouterCommand.java
new file mode 100644
index 0000000..efda0ae
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/DeleteExternalPeerRouterCommand.java
@@ -0,0 +1,65 @@
+/*
+ * 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.openstacknetworking.cli;
+
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+
+import java.util.List;
+
+/**
+ * Deletes external peer router.
+ */
+@Command(scope = "onos", name = "openstack-delete-peer-router",
+ description = "Delete external peer router")
+public class DeleteExternalPeerRouterCommand extends AbstractShellCommand {
+ @Argument(index = 0, name = "ip address", description = "ip address",
+ required = true, multiValued = false)
+ private String ipAddress = null;
+
+ private static final String FORMAT = "%-20s%-20s%-20s";
+ private static final String NO_ELEMENT = "There's no external peer router information with given ip address";
+
+ @Override
+ protected void execute() {
+ OpenstackNetworkService service = AbstractShellCommand.get(OpenstackNetworkService.class);
+
+ if (service.externalPeerRouters().stream()
+ .noneMatch(router -> router.externalPeerRouterIp().toString().equals(ipAddress))) {
+ print(NO_ELEMENT);
+ return;
+ }
+
+ try {
+ service.deleteExternalPeerRouter(ipAddress);
+ } catch (IllegalArgumentException e) {
+ log.error("Exception occurred because of {}", e.toString());
+ }
+ print(FORMAT, "Router IP", "Mac Address", "VLAN ID");
+ List<ExternalPeerRouter> routers = Lists.newArrayList(service.externalPeerRouters());
+
+ for (ExternalPeerRouter router: routers) {
+ print(FORMAT, router.externalPeerRouterIp(),
+ router.externalPeerRouterMac().toString(),
+ router.externalPeerRouterVlanId());
+ }
+
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/ExternalPeerRouterListCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/ExternalPeerRouterListCommand.java
new file mode 100644
index 0000000..ebd949c
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/ExternalPeerRouterListCommand.java
@@ -0,0 +1,48 @@
+/*
+ * 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.openstacknetworking.cli;
+
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+
+import java.util.List;
+
+/**
+ * Lists external peer router lists.
+ */
+@Command(scope = "onos", name = "openstack-peer-routers",
+ description = "Lists external peer router lists")
+public class ExternalPeerRouterListCommand extends AbstractShellCommand {
+
+ private static final String FORMAT = "%-20s%-20s%-20s";
+
+ @Override
+ protected void execute() {
+ OpenstackNetworkService service = AbstractShellCommand.get(OpenstackNetworkService.class);
+
+ print(FORMAT, "Router IP", "Mac Address", "VLAN ID");
+ List<ExternalPeerRouter> routers = Lists.newArrayList(service.externalPeerRouters());
+
+ for (ExternalPeerRouter router: routers) {
+ print(FORMAT, router.externalPeerRouterIp(),
+ router.externalPeerRouterMac().toString(),
+ router.externalPeerRouterVlanId());
+ }
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/IpAddressCompleter.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/IpAddressCompleter.java
new file mode 100644
index 0000000..4ae1dd4
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/IpAddressCompleter.java
@@ -0,0 +1,54 @@
+/*
+ * 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.openstacknetworking.cli;
+
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.stream.Collectors;
+
+/**
+ * IP Address Completer.
+ */
+public class IpAddressCompleter implements Completer {
+
+ @Override
+ public int complete(String buffer, int cursor, List<String> candidates) {
+ StringsCompleter delegate = new StringsCompleter();
+ OpenstackNetworkService osNetService = AbstractShellCommand.get(OpenstackNetworkService.class);
+ Set<IpAddress> set = osNetService.externalPeerRouters().stream()
+ .map(ExternalPeerRouter::externalPeerRouterIp)
+ .collect(Collectors.toSet());
+ SortedSet<String> strings = delegate.getStrings();
+
+ Iterator<IpAddress> it = set.iterator();
+
+ while (it.hasNext()) {
+ strings.add(it.next().toString());
+ }
+
+ return delegate.complete(buffer, cursor, candidates);
+
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/MacAddressCompleter.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/MacAddressCompleter.java
new file mode 100644
index 0000000..6e21de6
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/MacAddressCompleter.java
@@ -0,0 +1,54 @@
+/*
+ * 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.openstacknetworking.cli;
+
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.onlab.packet.MacAddress;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.stream.Collectors;
+
+/**
+ * Mac Address Completer.
+ */
+public class MacAddressCompleter implements Completer {
+
+ @Override
+ public int complete(String buffer, int cursor, List<String> candidates) {
+ StringsCompleter delegate = new StringsCompleter();
+ OpenstackNetworkService osNetService = AbstractShellCommand.get(OpenstackNetworkService.class);
+ Set<MacAddress> set = osNetService.externalPeerRouters().stream()
+ .map(ExternalPeerRouter::externalPeerRouterMac)
+ .collect(Collectors.toSet());
+ SortedSet<String> strings = delegate.getStrings();
+
+ Iterator<MacAddress> it = set.iterator();
+
+ while (it.hasNext()) {
+ strings.add(it.next().toString());
+ }
+
+ return delegate.complete(buffer, cursor, candidates);
+
+ }
+ }
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackFloatingIpListCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackFloatingIpListCommand.java
new file mode 100644
index 0000000..a2ef491
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackFloatingIpListCommand.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.cli;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.OpenstackRouterService;
+import org.openstack4j.model.network.NetFloatingIP;
+import org.openstack4j.openstack.networking.domain.NeutronFloatingIP;
+
+import java.util.Comparator;
+import java.util.List;
+
+import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.modelEntityToJson;
+
+/**
+ * Lists OpenStack floating IP addresses.
+ */
+@Command(scope = "onos", name = "openstack-floatingips",
+ description = "Lists all OpenStack floating IP addresses")
+public class OpenstackFloatingIpListCommand extends AbstractShellCommand {
+
+ private static final String FORMAT = "%-40s%-20s%-20s";
+
+ @Override
+ protected void execute() {
+ OpenstackRouterService service = AbstractShellCommand.get(OpenstackRouterService.class);
+ List<NetFloatingIP> floatingIps = Lists.newArrayList(service.floatingIps());
+ floatingIps.sort(Comparator.comparing(NetFloatingIP::getFloatingIpAddress));
+
+ if (outputJson()) {
+ try {
+ print("%s", mapper().writeValueAsString(json(floatingIps)));
+ } catch (JsonProcessingException e) {
+ print("Failed to list networks in JSON format");
+ }
+ return;
+ }
+
+ print(FORMAT, "ID", "Floating IP", "Fixed IP");
+ for (NetFloatingIP floatingIp: floatingIps) {
+ print(FORMAT, floatingIp.getId(),
+ floatingIp.getFloatingIpAddress(),
+ Strings.isNullOrEmpty(floatingIp.getFixedIpAddress()) ?
+ "" : floatingIp.getFixedIpAddress());
+ }
+ }
+
+ private JsonNode json(List<NetFloatingIP> floatingIps) {
+ ArrayNode result = mapper().enable(INDENT_OUTPUT).createArrayNode();
+ for (NetFloatingIP floatingIp: floatingIps) {
+ result.add(modelEntityToJson(floatingIp, NeutronFloatingIP.class));
+ }
+ return result;
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackNetworkListCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackNetworkListCommand.java
new file mode 100644
index 0000000..401dddc
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackNetworkListCommand.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.cli;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.Subnet;
+import org.openstack4j.openstack.networking.domain.NeutronNetwork;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.modelEntityToJson;
+
+/**
+ * Lists OpenStack networks.
+ */
+@Command(scope = "onos", name = "openstack-networks",
+ description = "Lists all OpenStack networks")
+public class OpenstackNetworkListCommand extends AbstractShellCommand {
+
+ private static final String FORMAT = "%-40s%-20s%-20s%-20s%-8s";
+
+ @Override
+ protected void execute() {
+ OpenstackNetworkService service = AbstractShellCommand.get(OpenstackNetworkService.class);
+ List<Network> networks = Lists.newArrayList(service.networks());
+ networks.sort(Comparator.comparing(Network::getName));
+
+ if (outputJson()) {
+ try {
+ print("%s", mapper().writeValueAsString(json(networks)));
+ } catch (JsonProcessingException e) {
+ print("Failed to list networks in JSON format");
+ }
+ return;
+ }
+
+ print(FORMAT, "ID", "Name", "Network Mode", "VNI", "Subnets");
+ for (Network net: networks) {
+ List<String> subnets = service.subnets().stream()
+ .filter(subnet -> subnet.getNetworkId().equals(net.getId()))
+ .map(Subnet::getCidr)
+ .collect(Collectors.toList());
+ print(FORMAT, net.getId(),
+ net.getName(),
+ net.getNetworkType().toString(),
+ net.getProviderSegID(),
+ subnets.isEmpty() ? "" : subnets);
+ }
+ }
+
+ private JsonNode json(List<Network> networks) {
+ ArrayNode result = mapper().enable(INDENT_OUTPUT).createArrayNode();
+ for (Network net: networks) {
+ result.add(modelEntityToJson(net, NeutronNetwork.class));
+ }
+ return result;
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackPortListCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackPortListCommand.java
new file mode 100644
index 0000000..aa38ffe
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackPortListCommand.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.cli;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.openstack4j.model.network.IP;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.openstack.networking.domain.NeutronPort;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.modelEntityToJson;
+
+/**
+ * Lists OpenStack ports.
+ */
+@Command(scope = "onos", name = "openstack-ports",
+ description = "Lists all OpenStack ports")
+public class OpenstackPortListCommand extends AbstractShellCommand {
+
+ private static final String FORMAT = "%-40s%-20s%-20s%-8s";
+
+ @Argument(name = "networkId", description = "Network ID")
+ private String networkId = null;
+
+ @Override
+ protected void execute() {
+ OpenstackNetworkService service = AbstractShellCommand.get(OpenstackNetworkService.class);
+
+ List<Port> ports = Lists.newArrayList(service.ports());
+ ports.sort(Comparator.comparing(Port::getNetworkId));
+
+ if (!Strings.isNullOrEmpty(networkId)) {
+ ports.removeIf(port -> !port.getNetworkId().equals(networkId));
+ }
+
+ if (outputJson()) {
+ try {
+ print("%s", mapper().writeValueAsString(json(ports)));
+ } catch (JsonProcessingException e) {
+ print("Failed to list ports in JSON format");
+ }
+ return;
+ }
+
+ print(FORMAT, "ID", "Network", "MAC", "Fixed IPs");
+ for (Port port: ports) {
+ List<String> fixedIps = port.getFixedIps().stream()
+ .map(IP::getIpAddress)
+ .collect(Collectors.toList());
+ Network osNet = service.network(port.getNetworkId());
+ print(FORMAT, port.getId(),
+ osNet.getName(),
+ port.getMacAddress(),
+ fixedIps.isEmpty() ? "" : fixedIps);
+ }
+ }
+
+ private JsonNode json(List<Port> ports) {
+ ArrayNode result = mapper().enable(INDENT_OUTPUT).createArrayNode();
+ for (Port port: ports) {
+ result.add(modelEntityToJson(port, NeutronPort.class));
+ }
+ return result;
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackPurgeRulesCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackPurgeRulesCommand.java
new file mode 100644
index 0000000..be72076
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackPurgeRulesCommand.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.cli;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.openstacknetworking.api.Constants;
+
+/**
+ * Purges all existing network states.
+ */
+@Command(scope = "onos", name = "openstack-purge-rules",
+ description = "Purges all flow rules installed by OpenStack networking app")
+public class OpenstackPurgeRulesCommand extends AbstractShellCommand {
+
+ @Override
+ protected void execute() {
+ FlowRuleService flowRuleService = AbstractShellCommand.get(FlowRuleService.class);
+ CoreService coreService = AbstractShellCommand.get(CoreService.class);
+ ApplicationId appId = coreService.getAppId(Constants.OPENSTACK_NETWORKING_APP_ID);
+ if (appId == null) {
+ error("Failed to purge OpenStack networking flow rules.");
+ return;
+ }
+ flowRuleService.removeFlowRulesById(appId);
+ print("Successfully purged flow rules installed by OpenStack networking application.");
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackPurgeStateCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackPurgeStateCommand.java
new file mode 100644
index 0000000..8601505
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackPurgeStateCommand.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.cli;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterAdminService;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupAdminService;
+
+/**
+ * Purges all existing network states.
+ */
+@Command(scope = "onos", name = "openstack-purge-states",
+ description = "Purges all OpenStack network states")
+public class OpenstackPurgeStateCommand extends AbstractShellCommand {
+
+ @Override
+ protected void execute() {
+ get(OpenstackRouterAdminService.class).clear();
+ get(OpenstackNetworkAdminService.class).clear();
+ get(OpenstackSecurityGroupAdminService.class).clear();
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackRouterListCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackRouterListCommand.java
new file mode 100644
index 0000000..772fa9b
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackRouterListCommand.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.cli;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterService;
+import org.openstack4j.model.network.IP;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.Router;
+import org.openstack4j.model.network.RouterInterface;
+import org.openstack4j.openstack.networking.domain.NeutronRouter;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.modelEntityToJson;
+
+/**
+ * Lists OpenStack routers.
+ */
+@Command(scope = "onos", name = "openstack-routers",
+ description = "Lists all OpenStack routers")
+public class OpenstackRouterListCommand extends AbstractShellCommand {
+
+ private static final String FORMAT = "%-40s%-20s%-20s%-8s";
+
+ private final OpenstackRouterService routerService =
+ AbstractShellCommand.get(OpenstackRouterService.class);
+ private final OpenstackNetworkService netService =
+ AbstractShellCommand.get(OpenstackNetworkService.class);
+
+ @Override
+ protected void execute() {
+ List<Router> routers = Lists.newArrayList(routerService.routers());
+ routers.sort(Comparator.comparing(Router::getName));
+
+ if (outputJson()) {
+ try {
+ print("%s", mapper().writeValueAsString(json(routers)));
+ } catch (JsonProcessingException e) {
+ print("Failed to list routers in JSON format");
+ }
+ return;
+ }
+ print(FORMAT, "ID", "Name", "External", "Internal");
+ for (Router router: routers) {
+ String exNetId = router.getExternalGatewayInfo() != null ?
+ router.getExternalGatewayInfo().getNetworkId() : null;
+
+ List<String> externals = Lists.newArrayList();
+ if (exNetId != null) {
+ // FIXME fix openstack4j to provide external gateway ip info
+ externals = netService.ports(exNetId).stream()
+ .filter(port -> Objects.equals(port.getDeviceId(),
+ router.getId()))
+ .flatMap(port -> port.getFixedIps().stream())
+ .map(IP::getIpAddress)
+ .collect(Collectors.toList());
+ }
+
+ List<String> internals = Lists.newArrayList();
+ routerService.routerInterfaces(router.getId()).forEach(iface -> {
+ internals.add(getRouterIfaceIp(iface));
+ });
+ print(FORMAT, router.getId(), router.getName(), externals, internals);
+ }
+ }
+
+ private String getRouterIfaceIp(RouterInterface iface) {
+ Port osPort = netService.port(iface.getPortId());
+ IP ipAddr = osPort.getFixedIps().stream()
+ .filter(ip -> ip.getSubnetId().equals(iface.getSubnetId()))
+ .findAny().orElse(null);
+ return ipAddr == null ? "" : ipAddr.getIpAddress();
+ }
+
+ private JsonNode json(List<Router> routers) {
+ ArrayNode result = mapper().enable(INDENT_OUTPUT).createArrayNode();
+ for (Router router: routers) {
+ result.add(modelEntityToJson(router, NeutronRouter.class));
+ }
+ return result;
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackSecurityGroupListCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackSecurityGroupListCommand.java
new file mode 100644
index 0000000..9342bca
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackSecurityGroupListCommand.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.cli;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupService;
+import org.openstack4j.model.network.SecurityGroup;
+import org.openstack4j.openstack.networking.domain.NeutronSecurityGroup;
+
+import java.util.Comparator;
+import java.util.List;
+
+import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.modelEntityToJson;
+
+/**
+ * Lists OpenStack security groups.
+ */
+@Command(scope = "onos", name = "openstack-security-groups",
+ description = "Lists all OpenStack security groups")
+public class OpenstackSecurityGroupListCommand extends AbstractShellCommand {
+
+ private static final String FORMAT = "%-40s%-20s";
+
+ @Argument(name = "networkId", description = "Network ID")
+ private String networkId = null;
+
+ @Override
+ protected void execute() {
+ OpenstackSecurityGroupService service =
+ AbstractShellCommand.get(OpenstackSecurityGroupService.class);
+
+ List<SecurityGroup> sgs = Lists.newArrayList(service.securityGroups());
+ sgs.sort(Comparator.comparing(SecurityGroup::getId));
+
+ if (outputJson()) {
+ try {
+ print("%s", mapper().writeValueAsString(json(sgs)));
+ } catch (JsonProcessingException e) {
+ error("Failed to list security groups in JSON format");
+ }
+ return;
+ }
+
+ print("Hint: use --json option to see security group rules as well\n");
+ print(FORMAT, "ID", "Name");
+ for (SecurityGroup sg: service.securityGroups()) {
+ print(FORMAT, sg.getId(), sg.getName());
+ }
+ }
+
+ private JsonNode json(List<SecurityGroup> securityGroups) {
+ ArrayNode result = mapper().enable(INDENT_OUTPUT).createArrayNode();
+ for (SecurityGroup sg: securityGroups) {
+ result.add(modelEntityToJson(sg, NeutronSecurityGroup.class));
+ }
+ return result;
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackSyncRulesCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackSyncRulesCommand.java
new file mode 100644
index 0000000..88b9a71
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackSyncRulesCommand.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.cli;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknode.api.NodeState;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeAdminService;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+
+/**
+ * Re-installs flow rules for OpenStack networking.
+ */
+@Command(scope = "onos", name = "openstack-sync-rules",
+ description = "Re-installs flow rules for OpenStack networking")
+public class OpenstackSyncRulesCommand extends AbstractShellCommand {
+
+ @Override
+ protected void execute() {
+ // All handlers in this application reacts the node complete event and
+ // tries to re-configure flow rules for the complete node.
+ OpenstackNodeService osNodeService = AbstractShellCommand.get(OpenstackNodeService.class);
+ OpenstackNodeAdminService osNodeAdminService = AbstractShellCommand.get(OpenstackNodeAdminService.class);
+ if (osNodeService == null) {
+ error("Failed to re-install flow rules for OpenStack networking.");
+ return;
+ }
+ osNodeService.completeNodes().forEach(osNode -> {
+ OpenstackNode updated = osNode.updateState(NodeState.INIT);
+ osNodeAdminService.updateNode(updated);
+ });
+ print("Successfully requested re-installing flow rules.");
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackSyncStateCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackSyncStateCommand.java
new file mode 100644
index 0000000..a297ca0
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackSyncStateCommand.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.cli;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.base.Strings;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterAdminService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterService;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupAdminService;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupService;
+import org.openstack4j.api.OSClient;
+import org.openstack4j.api.exceptions.AuthenticationException;
+import org.openstack4j.model.identity.v2.Access;
+import org.openstack4j.model.network.IP;
+import org.openstack4j.model.network.NetFloatingIP;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.Router;
+import org.openstack4j.model.network.RouterInterface;
+import org.openstack4j.model.network.Subnet;
+import org.openstack4j.openstack.OSFactory;
+import org.openstack4j.openstack.networking.domain.NeutronRouterInterface;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static org.openstack4j.core.transport.ObjectMapperSingleton.getContext;
+
+/**
+ * Synchronizes OpenStack network states.
+ */
+@Command(scope = "onos", name = "openstack-sync-states",
+ description = "Synchronizes all OpenStack network states")
+public class OpenstackSyncStateCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "endpoint", description = "OpenStack service endpoint",
+ required = true, multiValued = false)
+ private String endpoint = null;
+
+ @Argument(index = 1, name = "tenant", description = "OpenStack admin tenant name",
+ required = true, multiValued = false)
+ private String tenant = null;
+
+ @Argument(index = 2, name = "user", description = "OpenStack admin user name",
+ required = true, multiValued = false)
+ private String user = null;
+
+ @Argument(index = 3, name = "password", description = "OpenStack admin user password",
+ required = true, multiValued = false)
+ private String password = null;
+
+ private static final String SECURITY_GROUP_FORMAT = "%-40s%-20s";
+ private static final String NETWORK_FORMAT = "%-40s%-20s%-20s%-8s";
+ private static final String SUBNET_FORMAT = "%-40s%-20s%-20s";
+ private static final String PORT_FORMAT = "%-40s%-20s%-20s%-8s";
+ private static final String ROUTER_FORMAT = "%-40s%-20s%-20s%-8s";
+ private static final String FLOATING_IP_FORMAT = "%-40s%-20s%-20s";
+
+ private static final String DEVICE_OWNER_GW = "network:router_gateway";
+ private static final String DEVICE_OWNER_IFACE = "network:router_interface";
+
+ @Override
+ protected void execute() {
+ OpenstackSecurityGroupAdminService osSgAdminService = get(OpenstackSecurityGroupAdminService.class);
+ OpenstackSecurityGroupService osSgService = get(OpenstackSecurityGroupService.class);
+ OpenstackNetworkAdminService osNetAdminService = get(OpenstackNetworkAdminService.class);
+ OpenstackNetworkService osNetService = get(OpenstackNetworkService.class);
+ OpenstackRouterAdminService osRouterAdminService = get(OpenstackRouterAdminService.class);
+ OpenstackRouterService osRouterService = get(OpenstackRouterService.class);
+
+ Access osAccess;
+ try {
+ osAccess = OSFactory.builderV2()
+ .endpoint(this.endpoint)
+ .tenantName(this.tenant)
+ .credentials(this.user, this.password)
+ .authenticate()
+ .getAccess();
+ } catch (AuthenticationException e) {
+ print("Authentication failed");
+ return;
+ } catch (Exception e) {
+ print("Failed to access OpenStack service");
+ return;
+ }
+
+ OSClient osClient = OSFactory.clientFromAccess(osAccess);
+
+ print("Synchronizing OpenStack security groups");
+ print(SECURITY_GROUP_FORMAT, "ID", "Name");
+ osClient.networking().securitygroup().list().forEach(osSg -> {
+ if (osSgService.securityGroup(osSg.getId()) != null) {
+ osSgAdminService.updateSecurityGroup(osSg);
+ } else {
+ osSgAdminService.createSecurityGroup(osSg);
+ }
+ print(SECURITY_GROUP_FORMAT, osSg.getId(), osSg.getName());
+ });
+
+ print("\nSynchronizing OpenStack networks");
+ print(NETWORK_FORMAT, "ID", "Name", "VNI", "Subnets");
+ osClient.networking().network().list().forEach(osNet -> {
+ if (osNetService.network(osNet.getId()) != null) {
+ osNetAdminService.updateNetwork(osNet);
+ } else {
+ osNetAdminService.createNetwork(osNet);
+ }
+ printNetwork(osNet);
+ });
+
+ print("\nSynchronizing OpenStack subnets");
+ print(SUBNET_FORMAT, "ID", "Network", "CIDR");
+ osClient.networking().subnet().list().forEach(osSubnet -> {
+ if (osNetService.subnet(osSubnet.getId()) != null) {
+ osNetAdminService.updateSubnet(osSubnet);
+ } else {
+ osNetAdminService.createSubnet(osSubnet);
+ }
+ printSubnet(osSubnet, osNetService);
+ });
+
+ print("\nSynchronizing OpenStack ports");
+ print(PORT_FORMAT, "ID", "Network", "MAC", "Fixed IPs");
+ osClient.networking().port().list().forEach(osPort -> {
+ if (osNetService.port(osPort.getId()) != null) {
+ osNetAdminService.updatePort(osPort);
+ } else {
+ osNetAdminService.createPort(osPort);
+ }
+ printPort(osPort, osNetService);
+ });
+
+ print("\nSynchronizing OpenStack routers");
+ print(ROUTER_FORMAT, "ID", "Name", "External", "Internal");
+ osClient.networking().router().list().forEach(osRouter -> {
+ if (osRouterService.router(osRouter.getId()) != null) {
+ osRouterAdminService.updateRouter(osRouter);
+ } else {
+ osRouterAdminService.createRouter(osRouter);
+ }
+
+ // FIXME do we need to manage router interfaces separately?
+ osNetService.ports().stream()
+ .filter(osPort -> Objects.equals(osPort.getDeviceId(), osRouter.getId()) &&
+ Objects.equals(osPort.getDeviceOwner(), DEVICE_OWNER_IFACE))
+ .forEach(osPort -> addRouterIface(osPort, osRouterService,
+ osRouterAdminService));
+
+ printRouter(osRouter, osNetService);
+ });
+
+ print("\nSynchronizing OpenStack floating IPs");
+ print(FLOATING_IP_FORMAT, "ID", "Floating IP", "Fixed IP");
+ osClient.networking().floatingip().list().forEach(osFloating -> {
+ if (osRouterService.floatingIp(osFloating.getId()) != null) {
+ osRouterAdminService.updateFloatingIp(osFloating);
+ } else {
+ osRouterAdminService.createFloatingIp(osFloating);
+ }
+ printFloatingIp(osFloating);
+ });
+ }
+
+ // TODO fix the logic to add router interface to router
+ private void addRouterIface(Port osPort, OpenstackRouterService service,
+ OpenstackRouterAdminService adminService) {
+ osPort.getFixedIps().forEach(p -> {
+ JsonNode jsonTree = mapper().createObjectNode()
+ .put("id", osPort.getDeviceId())
+ .put("tenant_id", osPort.getTenantId())
+ .put("subnet_id", p.getSubnetId())
+ .put("port_id", osPort.getId());
+ try {
+ RouterInterface rIface = getContext(NeutronRouterInterface.class)
+ .readerFor(NeutronRouterInterface.class)
+ .readValue(jsonTree);
+ if (service.routerInterface(rIface.getPortId()) != null) {
+ adminService.updateRouterInterface(rIface);
+ } else {
+ adminService.addRouterInterface(rIface);
+ }
+ } catch (IOException ignore) {
+ }
+ });
+ }
+
+ private void printNetwork(Network osNet) {
+ final String strNet = String.format(NETWORK_FORMAT,
+ osNet.getId(),
+ osNet.getName(),
+ osNet.getProviderSegID(),
+ osNet.getSubnets());
+ print(strNet);
+ }
+
+ private void printSubnet(Subnet osSubnet, OpenstackNetworkService osNetService) {
+ final String strSubnet = String.format(SUBNET_FORMAT,
+ osSubnet.getId(),
+ osNetService.network(osSubnet.getNetworkId()).getName(),
+ osSubnet.getCidr());
+ print(strSubnet);
+ }
+
+ private void printPort(Port osPort, OpenstackNetworkService osNetService) {
+ List<String> fixedIps = osPort.getFixedIps().stream()
+ .map(IP::getIpAddress)
+ .collect(Collectors.toList());
+ final String strPort = String.format(PORT_FORMAT,
+ osPort.getId(),
+ osNetService.network(osPort.getNetworkId()).getName(),
+ osPort.getMacAddress(),
+ fixedIps.isEmpty() ? "" : fixedIps);
+ print(strPort);
+ }
+
+ private void printRouter(Router osRouter, OpenstackNetworkService osNetService) {
+ List<String> externals = osNetService.ports().stream()
+ .filter(osPort -> Objects.equals(osPort.getDeviceId(), osRouter.getId()) &&
+ Objects.equals(osPort.getDeviceOwner(), DEVICE_OWNER_GW))
+ .flatMap(osPort -> osPort.getFixedIps().stream())
+ .map(IP::getIpAddress)
+ .collect(Collectors.toList());
+
+ List<String> internals = osNetService.ports().stream()
+ .filter(osPort -> Objects.equals(osPort.getDeviceId(), osRouter.getId()) &&
+ Objects.equals(osPort.getDeviceOwner(), DEVICE_OWNER_IFACE))
+ .flatMap(osPort -> osPort.getFixedIps().stream())
+ .map(IP::getIpAddress)
+ .collect(Collectors.toList());
+
+ final String strRouter = String.format(ROUTER_FORMAT,
+ osRouter.getId(),
+ osRouter.getName(),
+ externals.isEmpty() ? "" : externals,
+ internals.isEmpty() ? "" : internals);
+ print(strRouter);
+ }
+
+ private void printFloatingIp(NetFloatingIP floatingIp) {
+ final String strFloating = String.format(FLOATING_IP_FORMAT,
+ floatingIp.getId(),
+ floatingIp.getFloatingIpAddress(),
+ Strings.isNullOrEmpty(floatingIp.getFixedIpAddress()) ?
+ "" : floatingIp.getFixedIpAddress());
+ print(strFloating);
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/UpdateExternalPeerRouterCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/UpdateExternalPeerRouterCommand.java
new file mode 100644
index 0000000..15a7555
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/UpdateExternalPeerRouterCommand.java
@@ -0,0 +1,95 @@
+/*
+ * 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.openstacknetworking.cli;
+
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+
+import java.util.List;
+
+/**
+ * Updates external peer router.
+ */
+@Command(scope = "onos", name = "openstack-update-peer-router",
+ description = "Update external peer router")
+public class UpdateExternalPeerRouterCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "ip address", description = "ip address",
+ required = true, multiValued = false)
+ private String ipAddress = null;
+
+ @Argument(index = 1, name = "mac address", description = "mac address",
+ required = true, multiValued = false)
+ private String macAddress = null;
+
+ @Argument(index = 2, name = "vlan id", description = "vlan id",
+ required = true, multiValued = false)
+ private String vlanId = null;
+
+ private static final String FORMAT = "%-20s%-20s%-20s";
+ private static final String NO_ELEMENT = "There's no external peer router information with given ip address";
+ private static final String NONE = "None";
+
+ @Override
+ protected void execute() {
+ OpenstackNetworkService service = AbstractShellCommand.get(OpenstackNetworkService.class);
+
+ IpAddress externalPeerIpAddress = IpAddress.valueOf(
+ IpAddress.Version.INET, Ip4Address.valueOf(ipAddress).toOctets());
+
+ if (service.externalPeerRouters().isEmpty()) {
+ print(NO_ELEMENT);
+ return;
+ } else if (service.externalPeerRouters().stream()
+ .noneMatch(router -> router.externalPeerRouterIp().toString().equals(ipAddress))) {
+ print(NO_ELEMENT);
+ return;
+ }
+ try {
+ if (vlanId.equals(NONE)) {
+ service.updateExternalPeerRouter(externalPeerIpAddress,
+ MacAddress.valueOf(macAddress),
+ VlanId.NONE);
+
+ } else {
+ service.updateExternalPeerRouter(externalPeerIpAddress,
+ MacAddress.valueOf(macAddress),
+ VlanId.vlanId(vlanId));
+ }
+ } catch (IllegalArgumentException e) {
+ log.error("Exception occurred because of {}", e.toString());
+ }
+
+
+ print(FORMAT, "Router IP", "Mac Address", "VLAN ID");
+ List<ExternalPeerRouter> routers = Lists.newArrayList(service.externalPeerRouters());
+
+ for (ExternalPeerRouter router: routers) {
+ print(FORMAT, router.externalPeerRouterIp(),
+ router.externalPeerRouterMac().toString(),
+ router.externalPeerRouterVlanId());
+ }
+ }
+}
+
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/UpdateExternalPeerRouterVlanCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/UpdateExternalPeerRouterVlanCommand.java
new file mode 100644
index 0000000..eed62e7
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/UpdateExternalPeerRouterVlanCommand.java
@@ -0,0 +1,83 @@
+/*
+ * 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.openstacknetworking.cli;
+
+import com.google.common.collect.Lists;
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+
+import java.util.List;
+
+/**
+ * Updates external peer router macc address.
+ */
+@Command(scope = "onos", name = "openstack-update-peer-router-vlan",
+ description = "Updates external peer router vlan")
+public class UpdateExternalPeerRouterVlanCommand extends AbstractShellCommand {
+ @Argument(index = 0, name = "ip address", description = "ip address",
+ required = true, multiValued = false)
+ private String ipAddress = null;
+
+ @Argument(index = 1, name = "vlan id", description = "vlan id",
+ required = true, multiValued = false)
+ private String vlanId = null;
+
+ private static final String FORMAT = "%-20s%-20s%-20s";
+ private static final String NO_ELEMENT = "There's no external peer router information with given ip address";
+ private static final String NONE = "None";
+
+ @Override
+ protected void execute() {
+ OpenstackNetworkService service = AbstractShellCommand.get(OpenstackNetworkService.class);
+
+ IpAddress externalPeerIpAddress = IpAddress.valueOf(
+ IpAddress.Version.INET, Ip4Address.valueOf(ipAddress).toOctets());
+
+ if (service.externalPeerRouters().isEmpty()) {
+ print(NO_ELEMENT);
+ return;
+ } else if (service.externalPeerRouters().stream()
+ .noneMatch(router -> router.externalPeerRouterIp().toString().equals(ipAddress))) {
+ print(NO_ELEMENT);
+ return;
+ }
+
+ try {
+ if (vlanId.equals(NONE)) {
+ service.updateExternalPeerRouterVlan(externalPeerIpAddress, VlanId.NONE);
+ } else {
+ service.updateExternalPeerRouterVlan(externalPeerIpAddress, VlanId.vlanId(vlanId));
+ }
+ } catch (IllegalArgumentException e) {
+ log.error("Exception occurred because of {}", e.toString());
+ }
+
+ print(FORMAT, "Router IP", "Mac Address", "VLAN ID");
+ List<ExternalPeerRouter> routers = Lists.newArrayList(service.externalPeerRouters());
+
+ for (ExternalPeerRouter router: routers) {
+ print(FORMAT, router.externalPeerRouterIp(),
+ router.externalPeerRouterMac().toString(),
+ router.externalPeerRouterVlanId());
+ }
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/VlanIdCompleter.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/VlanIdCompleter.java
new file mode 100644
index 0000000..0768859
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/VlanIdCompleter.java
@@ -0,0 +1,54 @@
+/*
+ * 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.openstacknetworking.cli;
+
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.onlab.packet.VlanId;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.stream.Collectors;
+
+/**
+ * Vlan Id Completer.
+ */
+public class VlanIdCompleter implements Completer {
+
+ @Override
+ public int complete(String buffer, int cursor, List<String> candidates) {
+ StringsCompleter delegate = new StringsCompleter();
+ OpenstackNetworkService osNetService = AbstractShellCommand.get(OpenstackNetworkService.class);
+ Set<VlanId> set = osNetService.externalPeerRouters().stream()
+ .map(ExternalPeerRouter::externalPeerRouterVlanId)
+ .collect(Collectors.toSet());
+ SortedSet<String> strings = delegate.getStrings();
+
+ Iterator<VlanId> it = set.iterator();
+
+ while (it.hasNext()) {
+ strings.add(it.next().toString());
+ }
+
+ return delegate.complete(buffer, cursor, candidates);
+
+ }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/package-info.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/package-info.java
new file mode 100644
index 0000000..ad7bca1
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015-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.
+ */
+
+/**
+ * CLI implementation for refresh/reprogram the data plane for the existing VM(OpenStackInstance).
+ */
+package org.onosproject.openstacknetworking.cli;
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DefaultExternalPeerRouter.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DefaultExternalPeerRouter.java
new file mode 100644
index 0000000..fb18502
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DefaultExternalPeerRouter.java
@@ -0,0 +1,86 @@
+/*
+ * 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.openstacknetworking.impl;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+
+import java.util.Objects;
+
+/**
+ * Implementation of external peer router.
+ */
+public class DefaultExternalPeerRouter implements ExternalPeerRouter {
+
+ private IpAddress externalPeerRouterIp;
+ private MacAddress externalPeerRouterMac;
+ private VlanId externalPeerRouterVlanId;
+
+ public DefaultExternalPeerRouter(IpAddress externalPeerRouterIp,
+ MacAddress externalPeerRouterMac,
+ VlanId externalPeerRouterVlanId) {
+ this.externalPeerRouterIp = externalPeerRouterIp;
+ this.externalPeerRouterMac = externalPeerRouterMac;
+ this.externalPeerRouterVlanId = externalPeerRouterVlanId;
+ }
+
+ @Override
+ public IpAddress externalPeerRouterIp() {
+ return this.externalPeerRouterIp;
+ }
+ @Override
+ public MacAddress externalPeerRouterMac() {
+ return this.externalPeerRouterMac;
+ }
+ @Override
+ public VlanId externalPeerRouterVlanId() {
+ return this.externalPeerRouterVlanId;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj instanceof DefaultExternalPeerRouter) {
+ DefaultExternalPeerRouter that = (DefaultExternalPeerRouter) obj;
+ return Objects.equals(externalPeerRouterIp, that.externalPeerRouterIp) &&
+ Objects.equals(externalPeerRouterMac, that.externalPeerRouterMac) &&
+ Objects.equals(externalPeerRouterVlanId, that.externalPeerRouterVlanId);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(externalPeerRouterIp,
+ externalPeerRouterMac,
+ externalPeerRouterVlanId);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("externalPeerRouterIp", externalPeerRouterIp)
+ .add("externalPeerRouterMac", externalPeerRouterMac)
+ .add("externalPeerRouterVlanId", externalPeerRouterVlanId)
+ .toString();
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DistributedOpenstackNetworkStore.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DistributedOpenstackNetworkStore.java
new file mode 100644
index 0000000..19650e3
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DistributedOpenstackNetworkStore.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkEvent;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkStore;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkStoreDelegate;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.openstack4j.model.network.IPVersionType;
+import org.openstack4j.model.network.Ipv6AddressMode;
+import org.openstack4j.model.network.Ipv6RaMode;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.NetworkType;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.State;
+import org.openstack4j.model.network.Subnet;
+import org.openstack4j.openstack.networking.domain.NeutronAllowedAddressPair;
+import org.openstack4j.openstack.networking.domain.NeutronExtraDhcpOptCreate;
+import org.openstack4j.openstack.networking.domain.NeutronHostRoute;
+import org.openstack4j.openstack.networking.domain.NeutronIP;
+import org.openstack4j.openstack.networking.domain.NeutronNetwork;
+import org.openstack4j.openstack.networking.domain.NeutronPool;
+import org.openstack4j.openstack.networking.domain.NeutronPort;
+import org.openstack4j.openstack.networking.domain.NeutronSubnet;
+import org.slf4j.Logger;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages the inventory of OpenStack network, subnet, and port using a {@code ConsistentMap}.
+ */
+@Service
+@Component(immediate = true)
+public class DistributedOpenstackNetworkStore
+ extends AbstractStore<OpenstackNetworkEvent, OpenstackNetworkStoreDelegate>
+ implements OpenstackNetworkStore {
+
+ protected final Logger log = getLogger(getClass());
+
+ private static final String ERR_NOT_FOUND = " does not exist";
+ private static final String ERR_DUPLICATE = " already exists";
+
+ private static final KryoNamespace SERIALIZER_NEUTRON_L2 = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(Network.class)
+ .register(NeutronNetwork.class)
+ .register(State.class)
+ .register(NetworkType.class)
+ .register(Port.class)
+ .register(NeutronPort.class)
+ .register(NeutronIP.class)
+ .register(NeutronAllowedAddressPair.class)
+ .register(NeutronExtraDhcpOptCreate.class)
+ .register(Subnet.class)
+ .register(NeutronSubnet.class)
+ .register(NeutronPool.class)
+ .register(NeutronHostRoute.class)
+ .register(IPVersionType.class)
+ .register(Ipv6AddressMode.class)
+ .register(Ipv6RaMode.class)
+ .register(LinkedHashMap.class)
+ .build();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ private final ExecutorService eventExecutor = newSingleThreadExecutor(
+ groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+
+ private final MapEventListener<String, Network> networkMapListener = new OpenstackNetworkMapListener();
+ private final MapEventListener<String, Subnet> subnetMapListener = new OpenstackSubnetMapListener();
+ private final MapEventListener<String, Port> portMapListener = new OpenstackPortMapListener();
+
+ private ConsistentMap<String, Network> osNetworkStore;
+ private ConsistentMap<String, Subnet> osSubnetStore;
+ private ConsistentMap<String, Port> osPortStore;
+
+ @Activate
+ protected void activate() {
+ ApplicationId appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+
+ osNetworkStore = storageService.<String, Network>consistentMapBuilder()
+ .withSerializer(Serializer.using(SERIALIZER_NEUTRON_L2))
+ .withName("openstack-networkstore")
+ .withApplicationId(appId)
+ .build();
+ osNetworkStore.addListener(networkMapListener);
+
+ osSubnetStore = storageService.<String, Subnet>consistentMapBuilder()
+ .withSerializer(Serializer.using(SERIALIZER_NEUTRON_L2))
+ .withName("openstack-subnetstore")
+ .withApplicationId(appId)
+ .build();
+ osSubnetStore.addListener(subnetMapListener);
+
+ osPortStore = storageService.<String, Port>consistentMapBuilder()
+ .withSerializer(Serializer.using(SERIALIZER_NEUTRON_L2))
+ .withName("openstack-portstore")
+ .withApplicationId(appId)
+ .build();
+ osPortStore.addListener(portMapListener);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ osNetworkStore.removeListener(networkMapListener);
+ osSubnetStore.removeListener(subnetMapListener);
+ osPortStore.removeListener(portMapListener);
+ eventExecutor.shutdown();
+
+ log.info("Stopped");
+ }
+
+ @Override
+ public void createNetwork(Network osNet) {
+ osNetworkStore.compute(osNet.getId(), (id, existing) -> {
+ final String error = osNet.getName() + ERR_DUPLICATE;
+ checkArgument(existing == null, error);
+ return osNet;
+ });
+ }
+
+ @Override
+ public void updateNetwork(Network osNet) {
+ osNetworkStore.compute(osNet.getId(), (id, existing) -> {
+ final String error = osNet.getName() + ERR_NOT_FOUND;
+ checkArgument(existing != null, error);
+ return osNet;
+ });
+ }
+
+ @Override
+ public Network removeNetwork(String netId) {
+ Versioned<Network> osNet = osNetworkStore.remove(netId);
+ return osNet == null ? null : osNet.value();
+ }
+
+ @Override
+ public Network network(String netId) {
+ Versioned<Network> versioned = osNetworkStore.get(netId);
+ return versioned == null ? null : versioned.value();
+ }
+
+ @Override
+ public Set<Network> networks() {
+ Set<Network> osNets = osNetworkStore.values().stream()
+ .map(Versioned::value)
+ .collect(Collectors.toSet());
+ return ImmutableSet.copyOf(osNets);
+ }
+
+ @Override
+ public void createSubnet(Subnet osSubnet) {
+ osSubnetStore.compute(osSubnet.getId(), (id, existing) -> {
+ final String error = osSubnet.getId() + ERR_DUPLICATE;
+ checkArgument(existing == null, error);
+ return osSubnet;
+ });
+ }
+
+ @Override
+ public void updateSubnet(Subnet osSubnet) {
+ osSubnetStore.compute(osSubnet.getId(), (id, existing) -> {
+ final String error = osSubnet.getId() + ERR_NOT_FOUND;
+ checkArgument(existing != null, error);
+ return osSubnet;
+ });
+ }
+
+ @Override
+ public Subnet removeSubnet(String subnetId) {
+ Versioned<Subnet> osSubnet = osSubnetStore.remove(subnetId);
+ return osSubnet == null ? null : osSubnet.value();
+ }
+
+ @Override
+ public Subnet subnet(String subnetId) {
+ Versioned<Subnet> osSubnet = osSubnetStore.get(subnetId);
+ return osSubnet == null ? null : osSubnet.value();
+ }
+
+ @Override
+ public Set<Subnet> subnets() {
+ Set<Subnet> osSubnets = osSubnetStore.values().stream()
+ .map(Versioned::value)
+ .collect(Collectors.toSet());
+ return ImmutableSet.copyOf(osSubnets);
+ }
+
+ @Override
+ public void createPort(Port osPort) {
+ osPortStore.compute(osPort.getId(), (id, existing) -> {
+ final String error = osPort.getId() + ERR_DUPLICATE;
+ checkArgument(existing == null, error);
+ return osPort;
+ });
+ }
+
+ @Override
+ public void updatePort(Port osPort) {
+ osPortStore.compute(osPort.getId(), (id, existing) -> {
+ final String error = osPort.getId() + ERR_NOT_FOUND;
+ checkArgument(existing != null, error);
+ return osPort;
+ });
+ }
+
+ @Override
+ public Port removePort(String portId) {
+ Versioned<Port> osPort = osPortStore.remove(portId);
+ return osPort == null ? null : osPort.value();
+ }
+
+ @Override
+ public Port port(String portId) {
+ Versioned<Port> osPort = osPortStore.get(portId);
+ return osPort == null ? null : osPort.value();
+ }
+
+ @Override
+ public Set<Port> ports() {
+ Set<Port> osPorts = osPortStore.values().stream()
+ .map(Versioned::value)
+ .collect(Collectors.toSet());
+ return ImmutableSet.copyOf(osPorts);
+ }
+
+ @Override
+ public void clear() {
+ osPortStore.clear();
+ osSubnetStore.clear();
+ osNetworkStore.clear();
+ }
+
+ private class OpenstackNetworkMapListener implements MapEventListener<String, Network> {
+
+ @Override
+ public void event(MapEvent<String, Network> event) {
+ switch (event.type()) {
+ case UPDATE:
+ log.debug("OpenStack network updated");
+ eventExecutor.execute(() -> {
+ notifyDelegate(new OpenstackNetworkEvent(
+ OPENSTACK_NETWORK_UPDATED,
+ event.newValue().value()));
+ });
+ break;
+ case INSERT:
+ log.debug("OpenStack network created");
+ eventExecutor.execute(() -> {
+ notifyDelegate(new OpenstackNetworkEvent(
+ OPENSTACK_NETWORK_CREATED,
+ event.newValue().value()));
+ });
+ break;
+ case REMOVE:
+ log.debug("OpenStack network removed");
+ eventExecutor.execute(() -> {
+ notifyDelegate(new OpenstackNetworkEvent(
+ OPENSTACK_NETWORK_REMOVED,
+ event.oldValue().value()));
+ });
+ break;
+ default:
+ log.error("Unsupported event type");
+ break;
+ }
+ }
+ }
+
+ private class OpenstackSubnetMapListener implements MapEventListener<String, Subnet> {
+
+ @Override
+ public void event(MapEvent<String, Subnet> event) {
+ switch (event.type()) {
+ case UPDATE:
+ log.debug("OpenStack subnet updated");
+ eventExecutor.execute(() -> {
+ notifyDelegate(new OpenstackNetworkEvent(
+ OPENSTACK_SUBNET_UPDATED,
+ network(event.newValue().value().getNetworkId()),
+ event.newValue().value()));
+ });
+ break;
+ case INSERT:
+ log.debug("OpenStack subnet created");
+ eventExecutor.execute(() -> {
+ notifyDelegate(new OpenstackNetworkEvent(
+ OPENSTACK_SUBNET_CREATED,
+ network(event.newValue().value().getNetworkId()),
+ event.newValue().value()));
+ });
+ break;
+ case REMOVE:
+ log.debug("OpenStack subnet removed");
+ eventExecutor.execute(() -> {
+ notifyDelegate(new OpenstackNetworkEvent(
+ OPENSTACK_SUBNET_REMOVED,
+ network(event.oldValue().value().getNetworkId()),
+ event.oldValue().value()));
+ });
+ break;
+ default:
+ log.error("Unsupported event type");
+ break;
+ }
+ }
+ }
+
+ private class OpenstackPortMapListener implements MapEventListener<String, Port> {
+
+ @Override
+ public void event(MapEvent<String, Port> event) {
+ switch (event.type()) {
+ case UPDATE:
+ log.debug("OpenStack port updated");
+ eventExecutor.execute(() -> {
+ Port oldPort = event.oldValue().value();
+ Port newPort = event.newValue().value();
+ notifyDelegate(new OpenstackNetworkEvent(
+ OPENSTACK_PORT_UPDATED,
+ network(event.newValue().value().getNetworkId()), newPort));
+ processSecurityGroupUpdate(oldPort, newPort);
+ });
+ break;
+ case INSERT:
+ log.debug("OpenStack port created");
+ eventExecutor.execute(() -> {
+ notifyDelegate(new OpenstackNetworkEvent(
+ OPENSTACK_PORT_CREATED,
+ network(event.newValue().value().getNetworkId()),
+ event.newValue().value()));
+ });
+ break;
+ case REMOVE:
+ log.debug("OpenStack port removed");
+ eventExecutor.execute(() -> {
+ notifyDelegate(new OpenstackNetworkEvent(
+ OPENSTACK_PORT_REMOVED,
+ network(event.oldValue().value().getNetworkId()),
+ event.oldValue().value()));
+ });
+ break;
+ default:
+ log.error("Unsupported event type");
+ break;
+ }
+ }
+
+ private void processSecurityGroupUpdate(Port oldPort, Port newPort) {
+ List<String> oldSecurityGroups = oldPort.getSecurityGroups() == null ?
+ ImmutableList.of() : oldPort.getSecurityGroups();
+ List<String> newSecurityGroups = newPort.getSecurityGroups() == null ?
+ ImmutableList.of() : newPort.getSecurityGroups();
+
+ oldSecurityGroups.stream()
+ .filter(sgId -> !newPort.getSecurityGroups().contains(sgId))
+ .forEach(sgId -> notifyDelegate(new OpenstackNetworkEvent(
+ OPENSTACK_PORT_SECURITY_GROUP_REMOVED, newPort, sgId
+ )));
+
+ newSecurityGroups.stream()
+ .filter(sgId -> !oldPort.getSecurityGroups().contains(sgId))
+ .forEach(sgId -> notifyDelegate(new OpenstackNetworkEvent(
+ OPENSTACK_PORT_SECURITY_GROUP_ADDED, newPort, sgId
+ )));
+ }
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DistributedOpenstackRouterStore.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DistributedOpenstackRouterStore.java
new file mode 100644
index 0000000..62fc7ac
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DistributedOpenstackRouterStore.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterEvent;
+import org.onosproject.openstacknetworking.api.OpenstackRouterStore;
+import org.onosproject.openstacknetworking.api.OpenstackRouterStoreDelegate;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.openstack4j.model.network.ExternalGateway;
+import org.openstack4j.model.network.NetFloatingIP;
+import org.openstack4j.model.network.Router;
+import org.openstack4j.model.network.RouterInterface;
+import org.openstack4j.model.network.State;
+import org.openstack4j.openstack.networking.domain.NeutronExternalGateway;
+import org.openstack4j.openstack.networking.domain.NeutronFloatingIP;
+import org.openstack4j.openstack.networking.domain.NeutronHostRoute;
+import org.openstack4j.openstack.networking.domain.NeutronRouter;
+import org.openstack4j.openstack.networking.domain.NeutronRouterInterface;
+import org.osgi.service.component.annotations.Activate;
+import org.slf4j.Logger;
+
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.onosproject.openstacknetworking.api.OpenstackRouterEvent.Type.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages the inventory of OpenStack router and floating IP using a {@code ConsistentMap}.
+ */
+@Service
+@Component(immediate = true)
+public class DistributedOpenstackRouterStore
+ extends AbstractStore<OpenstackRouterEvent, OpenstackRouterStoreDelegate>
+ implements OpenstackRouterStore {
+
+ protected final Logger log = getLogger(getClass());
+
+ private static final String ERR_NOT_FOUND = " does not exist";
+ private static final String ERR_DUPLICATE = " already exists";
+
+ private static final KryoNamespace SERIALIZER_NEUTRON_L3 = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(Router.class)
+ .register(NeutronRouter.class)
+ .register(State.class)
+ .register(NeutronHostRoute.class)
+ .register(ExternalGateway.class)
+ .register(NeutronExternalGateway.class)
+ .register(RouterInterface.class)
+ .register(NeutronRouterInterface.class)
+ .register(NetFloatingIP.class)
+ .register(NeutronFloatingIP.class)
+ .build();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackNetworkService osNetworkService;
+
+ private final ExecutorService eventExecutor = newSingleThreadExecutor(
+ groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+ private final MapEventListener<String, Router> routerMapListener = new OpenstackRouterMapListener();
+ private final MapEventListener<String, RouterInterface> routerInterfaceMapListener =
+ new OpenstackRouterInterfaceMapListener();
+ private final MapEventListener<String, NetFloatingIP> floatingIpMapListener =
+ new OpenstackFloatingIpMapListener();
+
+ private ConsistentMap<String, Router> osRouterStore;
+ private ConsistentMap<String, RouterInterface> osRouterInterfaceStore;
+ private ConsistentMap<String, NetFloatingIP> osFloatingIpStore;
+
+ @Activate
+ protected void activate() {
+ ApplicationId appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+
+ osRouterStore = storageService.<String, Router>consistentMapBuilder()
+ .withSerializer(Serializer.using(SERIALIZER_NEUTRON_L3))
+ .withName("openstack-routerstore")
+ .withApplicationId(appId)
+ .build();
+ osRouterStore.addListener(routerMapListener);
+
+ osRouterInterfaceStore = storageService.<String, RouterInterface>consistentMapBuilder()
+ .withSerializer(Serializer.using(SERIALIZER_NEUTRON_L3))
+ .withName("openstack-routerifacestore")
+ .withApplicationId(appId)
+ .build();
+ osRouterInterfaceStore.addListener(routerInterfaceMapListener);
+
+ osFloatingIpStore = storageService.<String, NetFloatingIP>consistentMapBuilder()
+ .withSerializer(Serializer.using(SERIALIZER_NEUTRON_L3))
+ .withName("openstack-floatingipstore")
+ .withApplicationId(appId)
+ .build();
+ osFloatingIpStore.addListener(floatingIpMapListener);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ osRouterStore.removeListener(routerMapListener);
+ osRouterInterfaceStore.removeListener(routerInterfaceMapListener);
+ osFloatingIpStore.removeListener(floatingIpMapListener);
+ eventExecutor.shutdown();
+
+ log.info("Stopped");
+ }
+
+ @Override
+ public void createRouter(Router osRouter) {
+ osRouterStore.compute(osRouter.getId(), (id, existing) -> {
+ final String error = osRouter.getName() + ERR_DUPLICATE;
+ checkArgument(existing == null, error);
+ return osRouter;
+ });
+ }
+
+ @Override
+ public void updateRouter(Router osRouter) {
+ osRouterStore.compute(osRouter.getId(), (id, existing) -> {
+ final String error = osRouter.getName() + ERR_NOT_FOUND;
+ checkArgument(existing != null, error);
+ return osRouter;
+ });
+ }
+
+ @Override
+ public Router removeRouter(String routerId) {
+ Versioned<Router> osRouter = osRouterStore.remove(routerId);
+ return osRouter == null ? null : osRouter.value();
+ }
+
+ @Override
+ public Router router(String routerId) {
+ Versioned<Router> versioned = osRouterStore.get(routerId);
+ return versioned == null ? null : versioned.value();
+ }
+
+ @Override
+ public Set<Router> routers() {
+ Set<Router> osRouters = osRouterStore.values().stream()
+ .map(Versioned::value)
+ .collect(Collectors.toSet());
+ return ImmutableSet.copyOf(osRouters);
+ }
+
+ @Override
+ public void addRouterInterface(RouterInterface osRouterIface) {
+ osRouterInterfaceStore.compute(osRouterIface.getPortId(), (id, existing) -> {
+ final String error = osRouterIface.getPortId() + ERR_DUPLICATE;
+ checkArgument(existing == null, error);
+ return osRouterIface;
+ });
+ }
+
+ @Override
+ public void updateRouterInterface(RouterInterface osRouterIface) {
+ osRouterInterfaceStore.compute(osRouterIface.getPortId(), (id, existing) -> {
+ final String error = osRouterIface.getPortId() + ERR_NOT_FOUND;
+ checkArgument(existing != null, error);
+ return osRouterIface;
+ });
+ }
+
+ @Override
+ public RouterInterface removeRouterInterface(String routerIfaceId) {
+ Versioned<RouterInterface> osRouterIface = osRouterInterfaceStore.remove(routerIfaceId);
+ return osRouterIface == null ? null : osRouterIface.value();
+ }
+
+ @Override
+ public RouterInterface routerInterface(String routerIfaceId) {
+ Versioned<RouterInterface> osRouterIface = osRouterInterfaceStore.get(routerIfaceId);
+ return osRouterIface == null ? null : osRouterIface.value();
+ }
+
+ @Override
+ public Set<RouterInterface> routerInterfaces() {
+ Set<RouterInterface> osRouterIfaces = osRouterInterfaceStore.values().stream()
+ .map(Versioned::value)
+ .collect(Collectors.toSet());
+ return ImmutableSet.copyOf(osRouterIfaces);
+ }
+
+ @Override
+ public void createFloatingIp(NetFloatingIP osFloatingIp) {
+ osFloatingIpStore.compute(osFloatingIp.getId(), (id, existing) -> {
+ final String error = osFloatingIp.getId() + ERR_DUPLICATE;
+ checkArgument(existing == null, error);
+ return osFloatingIp;
+ });
+ }
+
+ @Override
+ public void updateFloatingIp(NetFloatingIP osFloatingIp) {
+ osFloatingIpStore.compute(osFloatingIp.getId(), (id, existing) -> {
+ final String error = osFloatingIp.getId() + ERR_NOT_FOUND;
+ checkArgument(existing != null, error);
+ return osFloatingIp;
+ });
+ }
+
+ @Override
+ public NetFloatingIP removeFloatingIp(String floatingIpId) {
+ Versioned<NetFloatingIP> osFloatingIp = osFloatingIpStore.remove(floatingIpId);
+ return osFloatingIp == null ? null : osFloatingIp.value();
+ }
+
+ @Override
+ public NetFloatingIP floatingIp(String floatingIpId) {
+ Versioned<NetFloatingIP> osFloatingIp = osFloatingIpStore.get(floatingIpId);
+ return osFloatingIp == null ? null : osFloatingIp.value();
+ }
+
+ @Override
+ public Set<NetFloatingIP> floatingIps() {
+ Set<NetFloatingIP> osFloatingIps = osFloatingIpStore.values().stream()
+ .map(Versioned::value)
+ .collect(Collectors.toSet());
+ return ImmutableSet.copyOf(osFloatingIps);
+ }
+
+ @Override
+ public void clear() {
+ osFloatingIpStore.clear();
+ osRouterInterfaceStore.clear();
+ osRouterStore.clear();
+ }
+
+ private class OpenstackRouterMapListener implements MapEventListener<String, Router> {
+
+ @Override
+ public void event(MapEvent<String, Router> event) {
+ switch (event.type()) {
+ case UPDATE:
+ log.debug("OpenStack router updated");
+ eventExecutor.execute(() -> {
+ notifyDelegate(new OpenstackRouterEvent(
+ OPENSTACK_ROUTER_UPDATED,
+ event.newValue().value()));
+ processGatewayUpdate(event);
+ });
+ break;
+ case INSERT:
+ log.debug("OpenStack router created");
+ eventExecutor.execute(() -> {
+ notifyDelegate(new OpenstackRouterEvent(
+ OPENSTACK_ROUTER_CREATED,
+ event.newValue().value()));
+ });
+ break;
+ case REMOVE:
+ log.debug("OpenStack router removed");
+ eventExecutor.execute(() -> {
+ notifyDelegate(new OpenstackRouterEvent(
+ OPENSTACK_ROUTER_REMOVED,
+ event.oldValue().value()));
+ });
+ break;
+ default:
+ log.error("Unsupported event type");
+ break;
+ }
+ }
+
+ private void processGatewayUpdate(MapEvent<String, Router> event) {
+ ExternalGateway oldGateway = event.oldValue().value().getExternalGatewayInfo();
+ ExternalGateway newGateway = event.newValue().value().getExternalGatewayInfo();
+
+ if (oldGateway == null && newGateway != null) {
+ notifyDelegate(new OpenstackRouterEvent(
+ OPENSTACK_ROUTER_GATEWAY_ADDED,
+ event.newValue().value(), newGateway));
+ }
+ if (oldGateway != null && newGateway == null) {
+ notifyDelegate(new OpenstackRouterEvent(
+ OPENSTACK_ROUTER_GATEWAY_REMOVED,
+ event.newValue().value(), oldGateway));
+ }
+ }
+ }
+
+ private class OpenstackRouterInterfaceMapListener implements MapEventListener<String, RouterInterface> {
+
+ @Override
+ public void event(MapEvent<String, RouterInterface> event) {
+ switch (event.type()) {
+ case UPDATE:
+ log.debug("OpenStack router interface updated");
+ eventExecutor.execute(() -> {
+ notifyDelegate(new OpenstackRouterEvent(
+ OPENSTACK_ROUTER_INTERFACE_UPDATED,
+ router(event.newValue().value().getId()),
+ event.newValue().value()));
+ });
+ break;
+ case INSERT:
+ log.debug("OpenStack router interface created");
+ eventExecutor.execute(() -> {
+ notifyDelegate(new OpenstackRouterEvent(
+ OPENSTACK_ROUTER_INTERFACE_ADDED,
+ router(event.newValue().value().getId()),
+ event.newValue().value()));
+ });
+ break;
+ case REMOVE:
+ log.debug("OpenStack router interface removed");
+ eventExecutor.execute(() -> {
+ notifyDelegate(new OpenstackRouterEvent(
+ OPENSTACK_ROUTER_INTERFACE_REMOVED,
+ router(event.oldValue().value().getId()),
+ event.oldValue().value()));
+ });
+ break;
+ default:
+ log.error("Unsupported event type");
+ break;
+ }
+ }
+ }
+
+ private class OpenstackFloatingIpMapListener implements MapEventListener<String, NetFloatingIP> {
+
+ @Override
+ public void event(MapEvent<String, NetFloatingIP> event) {
+ switch (event.type()) {
+ case UPDATE:
+ log.debug("OpenStack floating IP updated");
+ eventExecutor.execute(() -> {
+ Router osRouter = Strings.isNullOrEmpty(
+ event.newValue().value().getRouterId()) ?
+ null :
+ router(event.newValue().value().getRouterId());
+ notifyDelegate(new OpenstackRouterEvent(
+ OPENSTACK_FLOATING_IP_UPDATED,
+ osRouter,
+ event.newValue().value()));
+ processFloatingIpUpdate(event, osRouter);
+ });
+ break;
+ case INSERT:
+ log.debug("OpenStack floating IP created");
+ eventExecutor.execute(() -> {
+ Router osRouter = Strings.isNullOrEmpty(
+ event.newValue().value().getRouterId()) ?
+ null :
+ router(event.newValue().value().getRouterId());
+ notifyDelegate(new OpenstackRouterEvent(
+ OPENSTACK_FLOATING_IP_CREATED,
+ osRouter,
+ event.newValue().value()));
+ });
+ break;
+ case REMOVE:
+ log.debug("OpenStack floating IP removed");
+ eventExecutor.execute(() -> {
+ Router osRouter = Strings.isNullOrEmpty(
+ event.oldValue().value().getRouterId()) ?
+ null :
+ router(event.oldValue().value().getRouterId());
+ notifyDelegate(new OpenstackRouterEvent(
+ OPENSTACK_FLOATING_IP_REMOVED,
+ osRouter,
+ event.oldValue().value()));
+ });
+ break;
+ default:
+ log.error("Unsupported event type");
+ break;
+ }
+ }
+
+ private void processFloatingIpUpdate(MapEvent<String, NetFloatingIP> event,
+ Router osRouter) {
+ String oldPortId = event.oldValue().value().getPortId();
+ String newPortId = event.newValue().value().getPortId();
+
+ if (Strings.isNullOrEmpty(oldPortId) && !Strings.isNullOrEmpty(newPortId)) {
+ notifyDelegate(new OpenstackRouterEvent(
+ OPENSTACK_FLOATING_IP_ASSOCIATED,
+ osRouter,
+ event.newValue().value(), newPortId));
+ }
+ if (!Strings.isNullOrEmpty(oldPortId) && Strings.isNullOrEmpty(newPortId)) {
+ notifyDelegate(new OpenstackRouterEvent(
+ OPENSTACK_FLOATING_IP_DISASSOCIATED,
+ osRouter,
+ event.newValue().value(), oldPortId));
+ }
+ }
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DistributedSecurityGroupStore.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DistributedSecurityGroupStore.java
new file mode 100644
index 0000000..c82c703
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/DistributedSecurityGroupStore.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupEvent;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupStore;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupStoreDelegate;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.MapEvent;
+import org.onosproject.store.service.MapEventListener;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.openstack4j.model.network.SecurityGroup;
+import org.openstack4j.model.network.SecurityGroupRule;
+import org.openstack4j.openstack.networking.domain.NeutronSecurityGroup;
+import org.openstack4j.openstack.networking.domain.NeutronSecurityGroupRule;
+import org.slf4j.Logger;
+
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.onosproject.openstacknetworking.api.OpenstackSecurityGroupEvent.Type.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Manages the inventory of OpenStack security group using a {@code ConsistentMap}.
+ *
+ */
+@Service
+@Component(immediate = true)
+public class DistributedSecurityGroupStore
+ extends AbstractStore<OpenstackSecurityGroupEvent, OpenstackSecurityGroupStoreDelegate>
+ implements OpenstackSecurityGroupStore {
+
+ protected final Logger log = getLogger(getClass());
+
+ private static final String ERR_NOT_FOUND = " does not exist";
+ private static final String ERR_DUPLICATE = " already exists";
+
+ private static final KryoNamespace SERIALIZER_SECURITY_GROUP = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(SecurityGroup.class)
+ .register(SecurityGroupRule.class)
+ .register(NeutronSecurityGroupRule.class)
+ .register(NeutronSecurityGroup.class)
+ .build();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ private final ExecutorService eventExecutor = newSingleThreadExecutor(
+ groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+
+ private final MapEventListener<String, SecurityGroup> securityGroupMapListener =
+ new OpenstackSecurityGroupMapListener();
+
+ private ConsistentMap<String, SecurityGroup> osSecurityGroupStore;
+
+ @Activate
+ protected void activate() {
+ ApplicationId appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+
+ osSecurityGroupStore = storageService.<String, SecurityGroup>consistentMapBuilder()
+ .withSerializer(Serializer.using(SERIALIZER_SECURITY_GROUP))
+ .withName("openstack-securitygroupstore")
+ .withApplicationId(appId)
+ .build();
+ osSecurityGroupStore.addListener(securityGroupMapListener);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ osSecurityGroupStore.removeListener(securityGroupMapListener);
+ eventExecutor.shutdown();
+
+ log.info("Stopped");
+ }
+
+ @Override
+ public void createSecurityGroup(SecurityGroup sg) {
+ osSecurityGroupStore.compute(sg.getId(), (id, existing) -> {
+ final String error = sg.getName() + ERR_DUPLICATE;
+ checkArgument(existing == null, error);
+ return sg;
+ });
+ }
+
+ @Override
+ public void updateSecurityGroup(SecurityGroup sg) {
+ osSecurityGroupStore.compute(sg.getId(), (id, existing) -> {
+ final String error = sg.getName() + ERR_NOT_FOUND;
+ checkArgument(existing != null, error);
+ return sg;
+ });
+ }
+
+ @Override
+ public SecurityGroup removeSecurityGroup(String sgId) {
+ Versioned<SecurityGroup> sg = osSecurityGroupStore.remove(sgId);
+ return sg == null ? null : sg.value();
+ }
+
+ @Override
+ public SecurityGroup securityGroup(String sgId) {
+ Versioned<SecurityGroup> osSg = osSecurityGroupStore.get(sgId);
+ return osSg == null ? null : osSg.value();
+ }
+
+ @Override
+ public Set<SecurityGroup> securityGroups() {
+ Set<SecurityGroup> osSgs = osSecurityGroupStore.values().stream()
+ .map(Versioned::value)
+ .collect(Collectors.toSet());
+ return ImmutableSet.copyOf(osSgs);
+ }
+
+ @Override
+ public void clear() {
+ osSecurityGroupStore.clear();
+ }
+
+ private class OpenstackSecurityGroupMapListener implements MapEventListener<String, SecurityGroup> {
+
+ @Override
+ public void event(MapEvent<String, SecurityGroup> event) {
+ switch (event.type()) {
+ case INSERT:
+ log.debug("OpenStack security group created {}", event.newValue());
+ eventExecutor.execute(() ->
+ notifyDelegate(new OpenstackSecurityGroupEvent(
+ OPENSTACK_SECURITY_GROUP_CREATED,
+ event.newValue().value())));
+ break;
+ case UPDATE:
+ log.debug("OpenStack security group updated {}", event.newValue());
+ eventExecutor.execute(() -> processUpdate(
+ event.oldValue().value(),
+ event.newValue().value()));
+ break;
+ case REMOVE:
+ log.debug("OpenStack security group removed {}", event.newValue());
+ eventExecutor.execute(() ->
+ notifyDelegate(new OpenstackSecurityGroupEvent(
+ OPENSTACK_SECURITY_GROUP_REMOVED,
+ event.oldValue().value())));
+ break;
+ default:
+ }
+ }
+
+ private void processUpdate(SecurityGroup oldSg, SecurityGroup newSg) {
+ Set<String> oldSgRuleIds = oldSg.getRules().stream()
+ .map(SecurityGroupRule::getId).collect(Collectors.toSet());
+ Set<String> newSgRuleIds = newSg.getRules().stream()
+ .map(SecurityGroupRule::getId).collect(Collectors.toSet());
+
+ oldSg.getRules().stream().filter(sgRule -> !newSgRuleIds.contains(sgRule.getId()))
+ .forEach(sgRule -> notifyDelegate(new OpenstackSecurityGroupEvent(
+ OPENSTACK_SECURITY_GROUP_RULE_REMOVED, newSg, sgRule)
+ ));
+ newSg.getRules().stream().filter(sgRule -> !oldSgRuleIds.contains(sgRule.getId()))
+ .forEach(sgRule -> notifyDelegate(new OpenstackSecurityGroupEvent(
+ OPENSTACK_SECURITY_GROUP_RULE_CREATED, newSg, sgRule)
+ ));
+ }
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/HostBasedInstancePort.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/HostBasedInstancePort.java
new file mode 100644
index 0000000..6e20290
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/HostBasedInstancePort.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.PortNumber;
+import org.onosproject.openstacknetworking.api.InstancePort;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Implementation of instance port based on host subsystem.
+ * Basically, HostBasedInstancePort is just a wrapper of a host, which helps
+ * mapping between OpenStack port and the OVS port and retrieving information
+ * such as IP address, location, and so on.
+ */
+public final class HostBasedInstancePort implements InstancePort {
+
+ static final String ANNOTATION_NETWORK_ID = "networkId";
+ static final String ANNOTATION_PORT_ID = "portId";
+ static final String ANNOTATION_CREATE_TIME = "createTime";
+
+ private final Host host;
+
+ /**
+ * Default constructor.
+ *
+ * @param instance host object of this instance
+ */
+ private HostBasedInstancePort(Host instance) {
+ this.host = instance;
+ }
+
+ /**
+ * Returns new instance.
+ *
+ * @param host host object of this instance
+ * @return instance
+ */
+ public static HostBasedInstancePort of(Host host) {
+ checkNotNull(host);
+ checkArgument(!Strings.isNullOrEmpty(host.annotations().value(ANNOTATION_NETWORK_ID)));
+ checkArgument(!Strings.isNullOrEmpty(host.annotations().value(ANNOTATION_PORT_ID)));
+ checkArgument(!Strings.isNullOrEmpty(host.annotations().value(ANNOTATION_CREATE_TIME)));
+
+ return new HostBasedInstancePort(host);
+ }
+
+ @Override
+ public String networkId() {
+ return host.annotations().value(ANNOTATION_NETWORK_ID);
+ }
+
+ @Override
+ public String portId() {
+ return host.annotations().value(ANNOTATION_PORT_ID);
+ }
+
+ @Override
+ public MacAddress macAddress() {
+ return host.mac();
+ }
+
+ @Override
+ public IpAddress ipAddress() {
+ Optional<IpAddress> ipAddr = host.ipAddresses().stream().findFirst();
+ return ipAddr.orElse(null);
+ }
+
+ @Override
+ public DeviceId deviceId() {
+ return host.location().deviceId();
+ }
+
+ @Override
+ public PortNumber portNumber() {
+ return host.location().port();
+ }
+
+ @Override
+ public String toString() {
+ return host.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj instanceof HostBasedInstancePort) {
+ HostBasedInstancePort that = (HostBasedInstancePort) obj;
+ if (Objects.equals(this.portId(), that.portId())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(portId());
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/HostBasedInstancePortManager.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/HostBasedInstancePortManager.java
new file mode 100644
index 0000000..207cd2b
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/HostBasedInstancePortManager.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.util.Tools;
+import org.onosproject.event.ListenerRegistry;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortEvent;
+import org.onosproject.openstacknetworking.api.InstancePortListener;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.slf4j.Logger;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.onosproject.openstacknetworking.api.InstancePortEvent.Type.OPENSTACK_INSTANCE_PORT_DETECTED;
+import static org.onosproject.openstacknetworking.api.InstancePortEvent.Type.OPENSTACK_INSTANCE_PORT_UPDATED;
+import static org.onosproject.openstacknetworking.api.InstancePortEvent.Type.OPENSTACK_INSTANCE_PORT_VANISHED;
+import static org.onosproject.openstacknetworking.impl.HostBasedInstancePort.ANNOTATION_NETWORK_ID;
+import static org.onosproject.openstacknetworking.impl.HostBasedInstancePort.ANNOTATION_PORT_ID;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provides implementation of administering and interfacing host based instance ports.
+ * It also provides instance port events for the hosts mapped to OpenStack VM interface.
+ */
+@Service
+@Component(immediate = true)
+public class HostBasedInstancePortManager
+ extends ListenerRegistry<InstancePortEvent, InstancePortListener>
+ implements InstancePortService {
+
+ protected final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostService hostService;
+
+ private final HostListener hostListener = new InternalHostListener();
+
+ @Activate
+ protected void activate() {
+ hostService.addListener(hostListener);
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ hostService.removeListener(hostListener);
+ log.info("Stopped");
+ }
+
+ @Override
+ public InstancePort instancePort(MacAddress macAddress) {
+ Host host = hostService.getHost(HostId.hostId(macAddress));
+ if (host == null || !isValidHost(host)) {
+ return null;
+ }
+ return HostBasedInstancePort.of(host);
+ }
+
+ @Override
+ public InstancePort instancePort(IpAddress ipAddress, String osNetId) {
+ return Tools.stream(hostService.getHosts()).filter(this::isValidHost)
+ .map(HostBasedInstancePort::of)
+ .filter(instPort -> instPort.networkId().equals(osNetId))
+ .filter(instPort -> instPort.ipAddress().equals(ipAddress))
+ .findAny().orElse(null);
+ }
+
+ @Override
+ public InstancePort instancePort(String osPortId) {
+ return Tools.stream(hostService.getHosts()).filter(this::isValidHost)
+ .map(HostBasedInstancePort::of)
+ .filter(instPort -> instPort.portId().equals(osPortId))
+ .findAny().orElse(null);
+ }
+
+ @Override
+ public Set<InstancePort> instancePorts() {
+ Set<InstancePort> instPors = Tools.stream(hostService.getHosts())
+ .filter(this::isValidHost)
+ .map(HostBasedInstancePort::of)
+ .collect(Collectors.toSet());
+ return ImmutableSet.copyOf(instPors);
+ }
+
+ @Override
+ public Set<InstancePort> instancePorts(String osNetId) {
+ Set<InstancePort> instPors = Tools.stream(hostService.getHosts())
+ .filter(this::isValidHost)
+ .map(HostBasedInstancePort::of)
+ .filter(instPort -> instPort.networkId().equals(osNetId))
+ .collect(Collectors.toSet());
+ return ImmutableSet.copyOf(instPors);
+ }
+
+ private boolean isValidHost(Host host) {
+ return !host.ipAddresses().isEmpty() &&
+ host.annotations().value(ANNOTATION_NETWORK_ID) != null &&
+ host.annotations().value(ANNOTATION_PORT_ID) != null;
+ }
+
+ private class InternalHostListener implements HostListener {
+
+ @Override
+ public boolean isRelevant(HostEvent event) {
+ Host host = event.subject();
+ if (!isValidHost(host)) {
+ log.debug("Invalid host detected, ignore it {}", host);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void event(HostEvent event) {
+ InstancePort instPort = HostBasedInstancePort.of(event.subject());
+ InstancePortEvent instPortEvent;
+ switch (event.type()) {
+ case HOST_UPDATED:
+ instPortEvent = new InstancePortEvent(
+ OPENSTACK_INSTANCE_PORT_UPDATED,
+ instPort);
+ log.debug("Instance port is updated: {}", instPort);
+ process(instPortEvent);
+ break;
+ case HOST_ADDED:
+ instPortEvent = new InstancePortEvent(
+ OPENSTACK_INSTANCE_PORT_DETECTED,
+ instPort);
+ log.debug("Instance port is detected: {}", instPort);
+ process(instPortEvent);
+ break;
+ case HOST_REMOVED:
+ instPortEvent = new InstancePortEvent(
+ OPENSTACK_INSTANCE_PORT_VANISHED,
+ instPort);
+ log.debug("Instance port is disabled: {}", instPort);
+ process(instPortEvent);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackFlowRuleManager.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackFlowRuleManager.java
new file mode 100644
index 0000000..d4a8b68
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackFlowRuleManager.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.openstacknetworking.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.DeviceId;
+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.FlowRuleOperations;
+import org.onosproject.net.flow.FlowRuleOperationsContext;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeEvent;
+import org.onosproject.openstacknode.api.OpenstackNodeListener;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.slf4j.Logger;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Sets flow rules directly using FlowRuleService.
+ */
+@Service
+@Component(immediate = true)
+public class OpenstackFlowRuleManager implements OpenstackFlowRuleService {
+
+ private final Logger log = getLogger(getClass());
+
+ private static final int DROP_PRIORITY = 0;
+ private static final int HIGH_PRIORITY = 30000;
+ private static final int TIMEOUT_SNAT_RULE = 60;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected FlowRuleService flowRuleService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackNodeService osNodeService;
+
+ private final ExecutorService deviceEventExecutor =
+ Executors.newSingleThreadExecutor(groupedThreads("openstacknetworking", "device-event"));
+ private final OpenstackNodeListener internalNodeListener = new InternalOpenstackNodeListener();
+
+ private ApplicationId appId;
+
+ @Activate
+ protected void activate() {
+ appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+ coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+ osNodeService.addListener(internalNodeListener);
+
+ osNodeService.completeNodes(OpenstackNode.NodeType.COMPUTE)
+ .forEach(node -> initializePipeline(node.intgBridge()));
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ osNodeService.removeListener(internalNodeListener);
+ deviceEventExecutor.shutdown();
+
+ log.info("Stopped");
+ }
+
+
+ @Override
+ public void setRule(ApplicationId appId,
+ DeviceId deviceId,
+ TrafficSelector selector,
+ TrafficTreatment treatment,
+ int priority,
+ int tableType,
+ boolean install) {
+
+ FlowRule.Builder flowRuleBuilder = DefaultFlowRule.builder()
+ .forDevice(deviceId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(priority)
+ .fromApp(appId)
+ .forTable(tableType);
+
+ if (priority == Constants.PRIORITY_SNAT_RULE) {
+ flowRuleBuilder.makeTemporary(TIMEOUT_SNAT_RULE);
+ } else {
+ flowRuleBuilder.makePermanent();
+ }
+
+ applyRule(flowRuleBuilder.build(), install);
+ }
+
+ private void applyRule(FlowRule flowRule, boolean install) {
+ FlowRuleOperations.Builder flowOpsBuilder = FlowRuleOperations.builder();
+
+ flowOpsBuilder = install ? flowOpsBuilder.add(flowRule) : flowOpsBuilder.remove(flowRule);
+
+ flowRuleService.apply(flowOpsBuilder.build(new FlowRuleOperationsContext() {
+ @Override
+ public void onSuccess(FlowRuleOperations ops) {
+ log.debug("Provisioned vni or forwarding table");
+ }
+
+ @Override
+ public void onError(FlowRuleOperations ops) {
+ log.debug("Failed to privision vni or forwarding table");
+ }
+ }));
+ }
+
+ private void initializePipeline(DeviceId deviceId) {
+ connectTables(deviceId, Constants.SRC_VNI_TABLE, Constants.ACL_TABLE);
+ connectTables(deviceId, Constants.ACL_TABLE, Constants.JUMP_TABLE);
+ setupJumpTable(deviceId);
+ }
+
+ @Override
+ public void connectTables(DeviceId deviceId, int fromTable, int toTable) {
+ TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+ TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+
+ treatment.transition(toTable);
+
+ FlowRule flowRule = DefaultFlowRule.builder()
+ .forDevice(deviceId)
+ .withSelector(selector.build())
+ .withTreatment(treatment.build())
+ .withPriority(DROP_PRIORITY)
+ .fromApp(appId)
+ .makePermanent()
+ .forTable(fromTable)
+ .build();
+
+ applyRule(flowRule, true);
+ }
+
+ @Override
+ public void setUpTableMissEntry(DeviceId deviceId, int table) {
+ TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+ TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+
+ treatment.drop();
+
+ FlowRule flowRule = DefaultFlowRule.builder()
+ .forDevice(deviceId)
+ .withSelector(selector.build())
+ .withTreatment(treatment.build())
+ .withPriority(DROP_PRIORITY)
+ .fromApp(appId)
+ .makePermanent()
+ .forTable(table)
+ .build();
+
+ applyRule(flowRule, true);
+ }
+
+ private void setupJumpTable(DeviceId deviceId) {
+ TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+ TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+
+ selector.matchEthDst(Constants.DEFAULT_GATEWAY_MAC);
+ treatment.transition(Constants.ROUTING_TABLE);
+
+ FlowRule flowRule = DefaultFlowRule.builder()
+ .forDevice(deviceId)
+ .withSelector(selector.build())
+ .withTreatment(treatment.build())
+ .withPriority(HIGH_PRIORITY)
+ .fromApp(appId)
+ .makePermanent()
+ .forTable(Constants.JUMP_TABLE)
+ .build();
+
+ applyRule(flowRule, true);
+
+ selector = DefaultTrafficSelector.builder();
+ treatment = DefaultTrafficTreatment.builder();
+
+ treatment.transition(Constants.FORWARDING_TABLE);
+
+ flowRule = DefaultFlowRule.builder()
+ .forDevice(deviceId)
+ .withSelector(selector.build())
+ .withTreatment(treatment.build())
+ .withPriority(DROP_PRIORITY)
+ .fromApp(appId)
+ .makePermanent()
+ .forTable(Constants.JUMP_TABLE)
+ .build();
+
+ applyRule(flowRule, true);
+ }
+
+ private class InternalOpenstackNodeListener implements OpenstackNodeListener {
+
+ @Override
+ public void event(OpenstackNodeEvent event) {
+ OpenstackNode osNode = event.subject();
+ // TODO check leadership of the node and make only the leader process
+
+ switch (event.type()) {
+ case OPENSTACK_NODE_COMPLETE:
+ deviceEventExecutor.execute(() -> {
+ log.info("COMPLETE node {} is detected", osNode.hostname());
+ processCompleteNode(event.subject());
+ });
+ break;
+ case OPENSTACK_NODE_CREATED:
+ case OPENSTACK_NODE_UPDATED:
+ case OPENSTACK_NODE_REMOVED:
+ case OPENSTACK_NODE_INCOMPLETE:
+ default:
+ // do nothing
+ break;
+ }
+ }
+
+ private void processCompleteNode(OpenstackNode osNode) {
+ if (osNode.type().equals(OpenstackNode.NodeType.COMPUTE)) {
+ initializePipeline(osNode.intgBridge());
+ }
+ }
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackNetworkManager.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackNetworkManager.java
new file mode 100644
index 0000000..230e740
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackNetworkManager.java
@@ -0,0 +1,541 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.event.ListenerRegistry;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.ExternalPeerRouter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkEvent;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkListener;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkStore;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkStoreDelegate;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.Versioned;
+import org.openstack4j.model.network.ExternalGateway;
+import org.openstack4j.model.network.IP;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.Router;
+import org.openstack4j.model.network.Subnet;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.AnnotationKeys.PORT_NAME;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provides implementation of administering and interfacing OpenStack network,
+ * subnet, and port.
+ */
+
+@Service
+@Component(immediate = true)
+public class OpenstackNetworkManager
+ extends ListenerRegistry<OpenstackNetworkEvent, OpenstackNetworkListener>
+ implements OpenstackNetworkAdminService, OpenstackNetworkService {
+
+ protected final Logger log = getLogger(getClass());
+
+ private static final String MSG_NETWORK = "OpenStack network %s %s";
+ private static final String MSG_SUBNET = "OpenStack subnet %s %s";
+ private static final String MSG_PORT = "OpenStack port %s %s";
+ private static final String MSG_CREATED = "created";
+ private static final String MSG_UPDATED = "updated";
+ private static final String MSG_REMOVED = "removed";
+
+ private static final String ERR_NULL_NETWORK = "OpenStack network cannot be null";
+ private static final String ERR_NULL_NETWORK_ID = "OpenStack network ID cannot be null";
+ private static final String ERR_NULL_NETWORK_NAME = "OpenStack network name cannot be null";
+ private static final String ERR_NULL_SUBNET = "OpenStack subnet cannot be null";
+ private static final String ERR_NULL_SUBNET_ID = "OpenStack subnet ID cannot be null";
+ private static final String ERR_NULL_SUBNET_NET_ID = "OpenStack subnet network ID cannot be null";
+ private static final String ERR_NULL_SUBNET_CIDR = "OpenStack subnet CIDR cannot be null";
+ private static final String ERR_NULL_PORT = "OpenStack port cannot be null";
+ private static final String ERR_NULL_PORT_ID = "OpenStack port ID cannot be null";
+ private static final String ERR_NULL_PORT_NET_ID = "OpenStack port network ID cannot be null";
+
+ private static final String ERR_NOT_FOUND = " does not exist";
+ private static final String ERR_IN_USE = " still in use";
+ private static final String ERR_DUPLICATE = " already exists";
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected PacketService packetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackNetworkStore osNetworkStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackNodeService osNodeService;
+
+
+ private final OpenstackNetworkStoreDelegate delegate = new InternalNetworkStoreDelegate();
+
+ private ConsistentMap<String, ExternalPeerRouter> externalPeerRouterMap;
+
+ private static final KryoNamespace SERIALIZER_EXTERNAL_PEER_ROUTER_MAP = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(ExternalPeerRouter.class)
+ .register(DefaultExternalPeerRouter.class)
+ .register(MacAddress.class)
+ .register(IpAddress.class)
+ .register(VlanId.class)
+ .build();
+
+ private ApplicationId appId;
+
+
+ @Activate
+ protected void activate() {
+ appId = coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
+
+ osNetworkStore.setDelegate(delegate);
+ log.info("Started");
+
+ externalPeerRouterMap = storageService.<String, ExternalPeerRouter>consistentMapBuilder()
+ .withSerializer(Serializer.using(SERIALIZER_EXTERNAL_PEER_ROUTER_MAP))
+ .withName("external-routermap")
+ .withApplicationId(appId)
+ .build();
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ osNetworkStore.unsetDelegate(delegate);
+ log.info("Stopped");
+ }
+
+ @Override
+ public void createNetwork(Network osNet) {
+ checkNotNull(osNet, ERR_NULL_NETWORK);
+ checkArgument(!Strings.isNullOrEmpty(osNet.getId()), ERR_NULL_NETWORK_ID);
+
+ osNetworkStore.createNetwork(osNet);
+ log.info(String.format(MSG_NETWORK, osNet.getName(), MSG_CREATED));
+ }
+
+ @Override
+ public void updateNetwork(Network osNet) {
+ checkNotNull(osNet, ERR_NULL_NETWORK);
+ checkArgument(!Strings.isNullOrEmpty(osNet.getId()), ERR_NULL_NETWORK_ID);
+
+ osNetworkStore.updateNetwork(osNet);
+ log.info(String.format(MSG_NETWORK, osNet.getId(), MSG_UPDATED));
+ }
+
+ @Override
+ public void removeNetwork(String netId) {
+ checkArgument(!Strings.isNullOrEmpty(netId), ERR_NULL_NETWORK_ID);
+ synchronized (this) {
+ if (isNetworkInUse(netId)) {
+ final String error = String.format(MSG_NETWORK, netId, ERR_IN_USE);
+ throw new IllegalStateException(error);
+ }
+ Network osNet = osNetworkStore.removeNetwork(netId);
+ if (osNet != null) {
+ log.info(String.format(MSG_NETWORK, osNet.getName(), MSG_REMOVED));
+ }
+ }
+ }
+
+ @Override
+ public void createSubnet(Subnet osSubnet) {
+ checkNotNull(osSubnet, ERR_NULL_SUBNET);
+ checkArgument(!Strings.isNullOrEmpty(osSubnet.getId()), ERR_NULL_SUBNET_ID);
+ checkArgument(!Strings.isNullOrEmpty(osSubnet.getNetworkId()), ERR_NULL_SUBNET_NET_ID);
+ checkArgument(!Strings.isNullOrEmpty(osSubnet.getCidr()), ERR_NULL_SUBNET_CIDR);
+
+ osNetworkStore.createSubnet(osSubnet);
+ log.info(String.format(MSG_SUBNET, osSubnet.getCidr(), MSG_CREATED));
+ }
+
+ @Override
+ public void updateSubnet(Subnet osSubnet) {
+ checkNotNull(osSubnet, ERR_NULL_SUBNET);
+ checkArgument(!Strings.isNullOrEmpty(osSubnet.getId()), ERR_NULL_SUBNET_ID);
+ checkArgument(!Strings.isNullOrEmpty(osSubnet.getNetworkId()), ERR_NULL_SUBNET_NET_ID);
+ checkArgument(!Strings.isNullOrEmpty(osSubnet.getCidr()), ERR_NULL_SUBNET_CIDR);
+
+ osNetworkStore.updateSubnet(osSubnet);
+ log.info(String.format(MSG_SUBNET, osSubnet.getCidr(), MSG_UPDATED));
+ }
+
+ @Override
+ public void removeSubnet(String subnetId) {
+ checkArgument(!Strings.isNullOrEmpty(subnetId), ERR_NULL_SUBNET_ID);
+ synchronized (this) {
+ if (isSubnetInUse(subnetId)) {
+ final String error = String.format(MSG_SUBNET, subnetId, ERR_IN_USE);
+ throw new IllegalStateException(error);
+ }
+ Subnet osSubnet = osNetworkStore.removeSubnet(subnetId);
+ if (osSubnet != null) {
+ log.info(String.format(MSG_SUBNET, osSubnet.getCidr(), MSG_REMOVED));
+ }
+ }
+ }
+
+ @Override
+ public void createPort(Port osPort) {
+ checkNotNull(osPort, ERR_NULL_PORT);
+ checkArgument(!Strings.isNullOrEmpty(osPort.getId()), ERR_NULL_PORT_ID);
+ checkArgument(!Strings.isNullOrEmpty(osPort.getNetworkId()), ERR_NULL_PORT_NET_ID);
+
+ osNetworkStore.createPort(osPort);
+ log.info(String.format(MSG_PORT, osPort.getId(), MSG_CREATED));
+ }
+
+ @Override
+ public void updatePort(Port osPort) {
+ checkNotNull(osPort, ERR_NULL_PORT);
+ checkArgument(!Strings.isNullOrEmpty(osPort.getId()), ERR_NULL_PORT_ID);
+ checkArgument(!Strings.isNullOrEmpty(osPort.getNetworkId()), ERR_NULL_PORT_NET_ID);
+
+ osNetworkStore.updatePort(osPort);
+ log.info(String.format(MSG_PORT, osPort.getId(), MSG_UPDATED));
+ }
+
+ @Override
+ public void removePort(String portId) {
+ checkArgument(!Strings.isNullOrEmpty(portId), ERR_NULL_PORT_ID);
+ synchronized (this) {
+ if (isPortInUse(portId)) {
+ final String error = String.format(MSG_PORT, portId, ERR_IN_USE);
+ throw new IllegalStateException(error);
+ }
+ Port osPort = osNetworkStore.removePort(portId);
+ if (osPort != null) {
+ log.info(String.format(MSG_PORT, osPort.getId(), MSG_REMOVED));
+ }
+ }
+ }
+
+ @Override
+ public void clear() {
+ osNetworkStore.clear();
+ }
+
+ @Override
+ public Network network(String netId) {
+ checkArgument(!Strings.isNullOrEmpty(netId), ERR_NULL_NETWORK_ID);
+ return osNetworkStore.network(netId);
+ }
+
+ @Override
+ public Set<Network> networks() {
+ return osNetworkStore.networks();
+ }
+
+ @Override
+ public Subnet subnet(String subnetId) {
+ checkArgument(!Strings.isNullOrEmpty(subnetId), ERR_NULL_SUBNET_ID);
+ return osNetworkStore.subnet(subnetId);
+ }
+
+ @Override
+ public Set<Subnet> subnets() {
+ return osNetworkStore.subnets();
+ }
+
+ @Override
+ public Set<Subnet> subnets(String netId) {
+ Set<Subnet> osSubnets = osNetworkStore.subnets().stream()
+ .filter(subnet -> Objects.equals(subnet.getNetworkId(), netId))
+ .collect(Collectors.toSet());
+ return ImmutableSet.copyOf(osSubnets);
+ }
+
+ @Override
+ public Port port(String portId) {
+ checkArgument(!Strings.isNullOrEmpty(portId), ERR_NULL_PORT_ID);
+ return osNetworkStore.port(portId);
+ }
+
+ @Override
+ public Port port(org.onosproject.net.Port port) {
+ String portName = port.annotations().value(PORT_NAME);
+ if (Strings.isNullOrEmpty(portName)) {
+ return null;
+ }
+ Optional<Port> osPort = osNetworkStore.ports()
+ .stream()
+ .filter(p -> p.getId().contains(portName.substring(3)))
+ .findFirst();
+ return osPort.orElse(null);
+ }
+
+ @Override
+ public Set<Port> ports() {
+ return ImmutableSet.copyOf(osNetworkStore.ports());
+ }
+
+ @Override
+ public Set<Port> ports(String netId) {
+ Set<Port> osPorts = osNetworkStore.ports().stream()
+ .filter(port -> Objects.equals(port.getNetworkId(), netId))
+ .collect(Collectors.toSet());
+ return ImmutableSet.copyOf(osPorts);
+ }
+
+ @Override
+ public ExternalPeerRouter externalPeerRouter(IpAddress ipAddress) {
+ if (externalPeerRouterMap.containsKey(ipAddress.toString())) {
+ return externalPeerRouterMap.get(ipAddress.toString()).value();
+ }
+ return null;
+ }
+
+ @Override
+ public void deriveExternalPeerRouterMac(ExternalGateway externalGateway, Router router) {
+ log.info("deriveExternalPeerRouterMac called");
+
+ IpAddress sourceIp = getExternalGatewaySourceIp(externalGateway, router);
+ IpAddress targetIp = getExternalPeerRouterIp(externalGateway);
+
+ if (sourceIp == null || targetIp == null) {
+ log.warn("Failed to derive external router mac address because source IP {} or target IP {} is null",
+ sourceIp, targetIp);
+ return;
+ }
+
+ if (externalPeerRouterMap.containsKey(targetIp.toString()) &&
+ !externalPeerRouterMap.get(
+ targetIp.toString()).value().externalPeerRouterMac().equals(MacAddress.NONE)) {
+ return;
+ }
+
+ MacAddress sourceMac = Constants.DEFAULT_GATEWAY_MAC;
+ Ethernet ethRequest = ARP.buildArpRequest(sourceMac.toBytes(),
+ sourceIp.toOctets(),
+ targetIp.toOctets(),
+ VlanId.NO_VID);
+
+ if (osNodeService.completeNodes(OpenstackNode.NodeType.GATEWAY).isEmpty()) {
+ log.warn("There's no complete gateway");
+ return;
+ }
+ OpenstackNode gatewayNode = osNodeService.completeNodes(OpenstackNode.NodeType.GATEWAY)
+ .stream()
+ .findFirst()
+ .orElse(null);
+
+ if (gatewayNode == null) {
+ return;
+ }
+
+ String upLinkPort = gatewayNode.uplinkPort();
+
+ org.onosproject.net.Port port = deviceService.getPorts(gatewayNode.intgBridge()).stream()
+ .filter(p -> Objects.equals(p.annotations().value(PORT_NAME), upLinkPort))
+ .findAny().orElse(null);
+
+ if (port == null) {
+ log.warn("There's no uplink port for gateway node {}", gatewayNode.toString());
+ return;
+ }
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(port.number())
+ .build();
+
+ packetService.emit(new DefaultOutboundPacket(
+ gatewayNode.intgBridge(),
+ treatment,
+ ByteBuffer.wrap(ethRequest.serialize())));
+
+ externalPeerRouterMap.put(
+ targetIp.toString(), new DefaultExternalPeerRouter(targetIp, MacAddress.NONE, VlanId.NONE));
+
+ log.info("Initializes external peer router map with peer router IP {}", targetIp.toString());
+ }
+
+ @Override
+ public void deleteExternalPeerRouter(ExternalGateway externalGateway) {
+ IpAddress targetIp = getExternalPeerRouterIp(externalGateway);
+ if (targetIp == null) {
+ return;
+ }
+
+ if (externalPeerRouterMap.containsKey(targetIp.toString())) {
+ externalPeerRouterMap.remove(targetIp.toString());
+ }
+ }
+
+ @Override
+ public void deleteExternalPeerRouter(String ipAddress) {
+ if (ipAddress == null) {
+ return;
+ }
+
+ if (externalPeerRouterMap.containsKey(ipAddress)) {
+ externalPeerRouterMap.remove(ipAddress);
+ }
+
+ }
+ private IpAddress getExternalGatewaySourceIp(ExternalGateway externalGateway, Router router) {
+ Port exGatewayPort = ports(externalGateway.getNetworkId())
+ .stream()
+ .filter(port -> Objects.equals(port.getDeviceId(), router.getId()))
+ .findAny().orElse(null);
+ if (exGatewayPort == null) {
+ log.warn("no external gateway port for router({})", router.getName());
+ return null;
+ }
+
+ IP ipAddress = exGatewayPort.getFixedIps().stream().findFirst().orElse(null);
+
+ return ipAddress == null ? null : IpAddress.valueOf(ipAddress.getIpAddress());
+ }
+
+ private IpAddress getExternalPeerRouterIp(ExternalGateway externalGateway) {
+ Optional<Subnet> externalSubnet = subnets(externalGateway.getNetworkId())
+ .stream()
+ .findFirst();
+
+ if (externalSubnet.isPresent()) {
+ return IpAddress.valueOf(externalSubnet.get().getGateway());
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void updateExternalPeerRouterMac(IpAddress ipAddress, MacAddress macAddress) {
+ try {
+ externalPeerRouterMap.computeIfPresent(ipAddress.toString(), (id, existing) ->
+ new DefaultExternalPeerRouter(ipAddress, macAddress, existing.externalPeerRouterVlanId()));
+ } catch (Exception e) {
+ log.error("Exception occurred because of {}", e.toString());
+ }
+
+ log.info("Updated external peer router map {}",
+ externalPeerRouterMap.get(ipAddress.toString()).value().toString());
+ }
+
+
+ @Override
+ public void updateExternalPeerRouter(IpAddress ipAddress, MacAddress macAddress, VlanId vlanId) {
+ try {
+ externalPeerRouterMap.computeIfPresent(ipAddress.toString(), (id, existing) ->
+ new DefaultExternalPeerRouter(ipAddress, macAddress, vlanId));
+ } catch (Exception e) {
+ log.error("Exception occurred because of {}", e.toString());
+ }
+ }
+
+ @Override
+ public MacAddress externalPeerRouterMac(ExternalGateway externalGateway) {
+ IpAddress ipAddress = getExternalPeerRouterIp(externalGateway);
+
+ if (ipAddress == null) {
+ return null;
+ }
+ if (externalPeerRouterMap.containsKey(ipAddress.toString())) {
+ return externalPeerRouterMap.get(ipAddress.toString()).value().externalPeerRouterMac();
+ } else {
+ throw new NoSuchElementException();
+ }
+ }
+
+ @Override
+ public void updateExternalPeerRouterVlan(IpAddress ipAddress, VlanId vlanId) {
+
+ try {
+ externalPeerRouterMap.computeIfPresent(ipAddress.toString(), (id, existing) -> {
+ return new DefaultExternalPeerRouter(ipAddress, existing.externalPeerRouterMac(), vlanId);
+ });
+ } catch (Exception e) {
+ log.error("Exception occurred because of {}", e.toString());
+ }
+ }
+
+ @Override
+ public Set<ExternalPeerRouter> externalPeerRouters() {
+ Set<ExternalPeerRouter> externalPeerRouters = externalPeerRouterMap.values().stream()
+ .map(Versioned::value)
+ .collect(Collectors.toSet());
+ return ImmutableSet.copyOf(externalPeerRouters);
+ }
+
+ private boolean isNetworkInUse(String netId) {
+ return !subnets(netId).isEmpty() && !ports(netId).isEmpty();
+ }
+
+ private boolean isSubnetInUse(String subnetId) {
+ // TODO add something if needed
+ return false;
+ }
+
+ private boolean isPortInUse(String portId) {
+ // TODO add something if needed
+ return false;
+ }
+
+ private class InternalNetworkStoreDelegate implements OpenstackNetworkStoreDelegate {
+
+ @Override
+ public void notify(OpenstackNetworkEvent event) {
+ if (event != null) {
+ log.trace("send oepnstack switching event {}", event);
+ process(event);
+ }
+ }
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRouterManager.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRouterManager.java
new file mode 100644
index 0000000..c636a1c
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRouterManager.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.core.CoreService;
+import org.onosproject.event.ListenerRegistry;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.OpenstackRouterAdminService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterEvent;
+import org.onosproject.openstacknetworking.api.OpenstackRouterListener;
+import org.onosproject.openstacknetworking.api.OpenstackRouterService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterStore;
+import org.onosproject.openstacknetworking.api.OpenstackRouterStoreDelegate;
+import org.openstack4j.model.network.NetFloatingIP;
+import org.openstack4j.model.network.Router;
+import org.openstack4j.model.network.RouterInterface;
+import org.slf4j.Logger;
+
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provides implementation of administering and interfacing OpenStack router and
+ * floating IP address.
+ */
+@Service
+@Component(immediate = true)
+public class OpenstackRouterManager
+ extends ListenerRegistry<OpenstackRouterEvent, OpenstackRouterListener>
+ implements OpenstackRouterAdminService, OpenstackRouterService {
+
+ protected final Logger log = getLogger(getClass());
+
+ private static final String MSG_ROUTER = "OpenStack router %s %s";
+ private static final String MSG_ROUTER_IFACE = "OpenStack router interface %s %s";
+ private static final String MSG_FLOATING_IP = "OpenStack floating IP %s %s";
+ private static final String MSG_CREATED = "created";
+ private static final String MSG_UPDATED = "updated";
+ private static final String MSG_REMOVED = "removed";
+
+ private static final String ERR_NULL_ROUTER = "OpenStack router cannot be null";
+ private static final String ERR_NULL_ROUTER_ID = "OpenStack router ID cannot be null";
+ private static final String ERR_NULL_ROUTER_NAME = "OpenStack router name cannot be null";
+ private static final String ERR_NULL_IFACE = "OpenStack router interface cannot be null";
+ private static final String ERR_NULL_IFACE_ROUTER_ID = "OpenStack router interface router ID cannot be null";
+ private static final String ERR_NULL_IFACE_PORT_ID = "OpenStack router interface port ID cannot be null";
+ private static final String ERR_NULL_FLOATING = "OpenStack floating IP cannot be null";
+ private static final String ERR_NULL_FLOATING_ID = "OpenStack floating IP cannot be null";
+
+ private static final String ERR_IN_USE = " still in use";
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackRouterStore osRouterStore;
+
+ private final OpenstackRouterStoreDelegate delegate = new InternalRouterStoreDelegate();
+
+ @Activate
+ protected void activate() {
+ coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
+ osRouterStore.setDelegate(delegate);
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ osRouterStore.unsetDelegate(delegate);
+ log.info("Stopped");
+ }
+
+ @Override
+ public void createRouter(Router osRouter) {
+ checkNotNull(osRouter, ERR_NULL_ROUTER);
+ checkArgument(!Strings.isNullOrEmpty(osRouter.getId()), ERR_NULL_ROUTER_ID);
+ checkArgument(!Strings.isNullOrEmpty(osRouter.getName()), ERR_NULL_ROUTER_NAME);
+
+ osRouterStore.createRouter(osRouter);
+ log.info(String.format(MSG_ROUTER, osRouter.getName(), MSG_CREATED));
+ }
+
+ @Override
+ public void updateRouter(Router osRouter) {
+ checkNotNull(osRouter, ERR_NULL_ROUTER);
+ checkArgument(!Strings.isNullOrEmpty(osRouter.getId()), ERR_NULL_ROUTER_ID);
+ checkArgument(!Strings.isNullOrEmpty(osRouter.getName()), ERR_NULL_ROUTER_NAME);
+
+ osRouterStore.updateRouter(osRouter);
+ log.info(String.format(MSG_ROUTER, osRouter.getId(), MSG_UPDATED));
+ }
+
+ @Override
+ public void removeRouter(String routerId) {
+ checkArgument(!Strings.isNullOrEmpty(routerId), ERR_NULL_ROUTER_ID);
+ synchronized (this) {
+ if (isRouterInUse(routerId)) {
+ final String error = String.format(MSG_ROUTER, routerId, ERR_IN_USE);
+ throw new IllegalStateException(error);
+ }
+ Router osRouter = osRouterStore.removeRouter(routerId);
+ if (osRouter != null) {
+ log.info(String.format(MSG_ROUTER, osRouter.getName(), MSG_REMOVED));
+ }
+ }
+ }
+
+ @Override
+ public void addRouterInterface(RouterInterface osIface) {
+ checkNotNull(osIface, ERR_NULL_IFACE);
+ checkArgument(!Strings.isNullOrEmpty(osIface.getId()), ERR_NULL_IFACE_ROUTER_ID);
+ checkArgument(!Strings.isNullOrEmpty(osIface.getPortId()), ERR_NULL_IFACE_PORT_ID);
+
+ osRouterStore.addRouterInterface(osIface);
+ log.info(String.format(MSG_ROUTER_IFACE, osIface.getPortId(), MSG_CREATED));
+ }
+
+ @Override
+ public void updateRouterInterface(RouterInterface osIface) {
+ checkNotNull(osIface, ERR_NULL_IFACE);
+ checkArgument(!Strings.isNullOrEmpty(osIface.getId()), ERR_NULL_IFACE_ROUTER_ID);
+ checkArgument(!Strings.isNullOrEmpty(osIface.getPortId()), ERR_NULL_IFACE_PORT_ID);
+
+ osRouterStore.updateRouterInterface(osIface);
+ log.info(String.format(MSG_ROUTER_IFACE, osIface.getPortId(), MSG_UPDATED));
+ }
+
+ @Override
+ public void removeRouterInterface(String osIfaceId) {
+ checkArgument(!Strings.isNullOrEmpty(osIfaceId), ERR_NULL_IFACE_PORT_ID);
+ synchronized (this) {
+ if (isRouterIfaceInUse(osIfaceId)) {
+ final String error = String.format(MSG_ROUTER, osIfaceId, ERR_IN_USE);
+ throw new IllegalStateException(error);
+ }
+ RouterInterface osIface = osRouterStore.removeRouterInterface(osIfaceId);
+ if (osIface != null) {
+ log.info(String.format(MSG_ROUTER_IFACE, osIface.getPortId(), MSG_REMOVED));
+ }
+ }
+ }
+
+ @Override
+ public void createFloatingIp(NetFloatingIP osFloatingIp) {
+ checkNotNull(osFloatingIp, ERR_NULL_FLOATING);
+ checkArgument(!Strings.isNullOrEmpty(osFloatingIp.getId()), ERR_NULL_FLOATING_ID);
+
+ osRouterStore.createFloatingIp(osFloatingIp);
+ log.info(String.format(MSG_FLOATING_IP, osFloatingIp.getId(), MSG_CREATED));
+ }
+
+ @Override
+ public void updateFloatingIp(NetFloatingIP osFloatingIp) {
+ checkNotNull(osFloatingIp, ERR_NULL_FLOATING);
+ checkArgument(!Strings.isNullOrEmpty(osFloatingIp.getId()), ERR_NULL_FLOATING_ID);
+
+ osRouterStore.updateFloatingIp(osFloatingIp);
+ log.info(String.format(MSG_FLOATING_IP, osFloatingIp.getId(), MSG_UPDATED));
+ }
+
+ @Override
+ public void removeFloatingIp(String floatingIpId) {
+ checkArgument(!Strings.isNullOrEmpty(floatingIpId), ERR_NULL_FLOATING_ID);
+ synchronized (this) {
+ if (isFloatingIpInUse(floatingIpId)) {
+ final String error = String.format(MSG_FLOATING_IP, floatingIpId, ERR_IN_USE);
+ throw new IllegalStateException(error);
+ }
+ NetFloatingIP osFloatingIp = osRouterStore.removeFloatingIp(floatingIpId);
+ if (osFloatingIp != null) {
+ log.info(String.format(MSG_FLOATING_IP, osFloatingIp.getId(), MSG_REMOVED));
+ }
+ }
+ }
+
+ @Override
+ public void clear() {
+ osRouterStore.clear();
+ }
+
+ @Override
+ public Router router(String routerId) {
+ checkArgument(!Strings.isNullOrEmpty(routerId), ERR_NULL_ROUTER_ID);
+ return osRouterStore.router(routerId);
+ }
+
+ @Override
+ public Set<Router> routers() {
+ return osRouterStore.routers();
+ }
+
+ @Override
+ public RouterInterface routerInterface(String osIfaceId) {
+ checkArgument(!Strings.isNullOrEmpty(osIfaceId), ERR_NULL_IFACE_PORT_ID);
+ return osRouterStore.routerInterface(osIfaceId);
+ }
+
+ @Override
+ public Set<RouterInterface> routerInterfaces() {
+ return osRouterStore.routerInterfaces();
+ }
+
+ @Override
+ public Set<RouterInterface> routerInterfaces(String routerId) {
+ Set<RouterInterface> osIfaces = osRouterStore.routerInterfaces().stream()
+ .filter(iface -> Objects.equals(iface.getId(), routerId))
+ .collect(Collectors.toSet());
+ return ImmutableSet.copyOf(osIfaces);
+ }
+
+ @Override
+ public NetFloatingIP floatingIp(String floatingIpId) {
+ checkArgument(!Strings.isNullOrEmpty(floatingIpId), ERR_NULL_FLOATING_ID);
+ return osRouterStore.floatingIp(floatingIpId);
+ }
+
+ @Override
+ public Set<NetFloatingIP> floatingIps() {
+ return osRouterStore.floatingIps();
+ }
+
+ @Override
+ public Set<NetFloatingIP> floatingIps(String routerId) {
+ Set<NetFloatingIP> osFloatingIps = osRouterStore.floatingIps().stream()
+ .filter(fip -> Objects.equals(fip.getRouterId(), routerId))
+ .collect(Collectors.toSet());
+ return ImmutableSet.copyOf(osFloatingIps);
+ }
+
+ private boolean isRouterInUse(String routerId) {
+ // TODO add checking
+ return false;
+ }
+
+ private boolean isRouterIfaceInUse(String osIfaceId) {
+ // TODO add checking
+ return false;
+ }
+
+ private boolean isFloatingIpInUse(String floatingIpId) {
+ // TODO add checking
+ return false;
+ }
+
+ private class InternalRouterStoreDelegate implements OpenstackRouterStoreDelegate {
+
+ @Override
+ public void notify(OpenstackRouterEvent event) {
+ if (event != null) {
+ log.trace("send oepnstack routing event {}", event);
+ process(event);
+ }
+ }
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingArpHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingArpHandler.java
new file mode 100644
index 0000000..5dee134
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingArpHandler.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.OpenstackRouterService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.openstack4j.model.network.NetFloatingIP;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Handle ARP requests from gateway nodes.
+ */
+@Component(immediate = true)
+public class OpenstackRoutingArpHandler {
+
+ private final Logger log = getLogger(getClass());
+
+ private static final String DEVICE_OWNER_ROUTER_GW = "network:router_gateway";
+ private static final String DEVICE_OWNER_FLOATING_IP = "network:floatingip";
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected PacketService packetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackNetworkService osNetworkService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackRouterService osRouterService;
+
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackNodeService osNodeService;
+
+ private final ExecutorService eventExecutor = newSingleThreadExecutor(
+ groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+
+ private final PacketProcessor packetProcessor = new InternalPacketProcessor();
+
+ @Activate
+ protected void activate() {
+ packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ packetService.removeProcessor(packetProcessor);
+ eventExecutor.shutdown();
+ log.info("Stopped");
+ }
+
+ private void processArpPacket(PacketContext context, Ethernet ethernet) {
+ ARP arp = (ARP) ethernet.getPayload();
+ if (arp.getOpCode() == ARP.OP_REQUEST) {
+ if (log.isTraceEnabled()) {
+ log.trace("ARP request received from {} for {}",
+ Ip4Address.valueOf(arp.getSenderProtocolAddress()).toString(),
+ Ip4Address.valueOf(arp.getTargetProtocolAddress()).toString());
+ }
+
+ IpAddress targetIp = Ip4Address.valueOf(arp.getTargetProtocolAddress());
+
+ MacAddress targetMac = null;
+
+ NetFloatingIP floatingIP = osRouterService.floatingIps().stream()
+ .filter(ip -> ip.getFloatingIpAddress().equals(targetIp.toString()))
+ .findAny().orElse(null);
+
+ if (floatingIP != null && floatingIP.getPortId() != null) {
+ targetMac = MacAddress.valueOf(osNetworkService.port(floatingIP.getPortId()).getMacAddress());
+ }
+
+ if (isExternalGatewaySourceIp(targetIp.getIp4Address())) {
+ targetMac = Constants.DEFAULT_GATEWAY_MAC;
+ }
+
+ if (targetMac == null) {
+ log.trace("Unknown target ARP request for {}, ignore it", targetIp);
+ return;
+ }
+
+ Ethernet ethReply = ARP.buildArpReply(targetIp.getIp4Address(),
+ targetMac, ethernet);
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(context.inPacket().receivedFrom().port())
+ .build();
+
+ packetService.emit(new DefaultOutboundPacket(
+ context.inPacket().receivedFrom().deviceId(),
+ treatment,
+ ByteBuffer.wrap(ethReply.serialize())));
+
+ context.block();
+ } else if (arp.getOpCode() == ARP.OP_REPLY) {
+ PortNumber receivedPortNum = context.inPacket().receivedFrom().port();
+ log.debug("ARP reply ip: {}, mac: {}",
+ Ip4Address.valueOf(arp.getSenderProtocolAddress()),
+ MacAddress.valueOf(arp.getSenderHardwareAddress()));
+ try {
+ if (receivedPortNum.equals(
+ osNodeService.node(context.inPacket().receivedFrom().deviceId()).uplinkPortNum())) {
+ osNetworkService.updateExternalPeerRouterMac(
+ Ip4Address.valueOf(arp.getSenderProtocolAddress()),
+ MacAddress.valueOf(arp.getSenderHardwareAddress()));
+ }
+ } catch (Exception e) {
+ log.error("Exception occurred because of {}", e.toString());
+ }
+ }
+
+ }
+
+ private class InternalPacketProcessor implements PacketProcessor {
+
+ @Override
+ public void process(PacketContext context) {
+ if (context.isHandled()) {
+ return;
+ }
+
+ Set<DeviceId> gateways = osNodeService.completeNodes(GATEWAY)
+ .stream().map(OpenstackNode::intgBridge)
+ .collect(Collectors.toSet());
+
+ if (!gateways.contains(context.inPacket().receivedFrom().deviceId())) {
+ // return if the packet is not from gateway nodes
+ return;
+ }
+
+ InboundPacket pkt = context.inPacket();
+ Ethernet ethernet = pkt.parsed();
+ if (ethernet != null &&
+ ethernet.getEtherType() == Ethernet.TYPE_ARP) {
+ eventExecutor.execute(() -> processArpPacket(context, ethernet));
+ }
+ }
+ }
+
+ private boolean isExternalGatewaySourceIp(IpAddress targetIp) {
+ return osNetworkService.ports().stream()
+ .filter(osPort -> Objects.equals(osPort.getDeviceOwner(),
+ DEVICE_OWNER_ROUTER_GW))
+ .flatMap(osPort -> osPort.getFixedIps().stream())
+ .anyMatch(ip -> IpAddress.valueOf(ip.getIpAddress()).equals(targetIp));
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java
new file mode 100644
index 0000000..b34ae9c
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingFloatingIpHandler.java
@@ -0,0 +1,528 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterEvent;
+import org.onosproject.openstacknetworking.api.OpenstackRouterListener;
+import org.onosproject.openstacknetworking.api.OpenstackRouterService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeEvent;
+import org.onosproject.openstacknode.api.OpenstackNodeListener;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.openstack4j.model.network.ExternalGateway;
+import org.openstack4j.model.network.NetFloatingIP;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.NetworkType;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.Router;
+import org.openstack4j.model.network.RouterInterface;
+import org.openstack4j.model.network.Subnet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.GW_COMMON_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_EXTERNAL_FLOATING_ROUTING_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_FLOATING_EXTERNAL;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_FLOATING_INTERNAL;
+import static org.onosproject.openstacknetworking.api.Constants.ROUTING_TABLE;
+import static org.onosproject.openstacknetworking.impl.RulePopulatorUtil.buildExtension;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
+
+/**
+ * Handles OpenStack floating IP events.
+ */
+@Component(immediate = true)
+public class OpenstackRoutingFloatingIpHandler {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final String ERR_FLOW = "Failed set flows for floating IP %s: ";
+ private static final String ERR_UNSUPPORTED_NET_TYPE = "Unsupported network type %s";
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected LeadershipService leadershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackNodeService osNodeService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected InstancePortService instancePortService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackRouterService osRouterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackNetworkService osNetworkService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackFlowRuleService osFlowRuleService;
+
+ private final ExecutorService eventExecutor = newSingleThreadExecutor(
+ groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+ private final OpenstackRouterListener floatingIpLisener = new InternalFloatingIpListener();
+ private final OpenstackNodeListener osNodeListener = new InternalNodeListener();
+
+ private ApplicationId appId;
+ private NodeId localNodeId;
+
+ @Activate
+ protected void activate() {
+ appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+ localNodeId = clusterService.getLocalNode().id();
+ leadershipService.runForLeadership(appId.name());
+ osRouterService.addListener(floatingIpLisener);
+ osNodeService.addListener(osNodeListener);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ osNodeService.removeListener(osNodeListener);
+ osRouterService.removeListener(floatingIpLisener);
+ leadershipService.withdraw(appId.name());
+ eventExecutor.shutdown();
+
+ log.info("Stopped");
+ }
+
+ private void setFloatingIpRules(NetFloatingIP floatingIp, Port osPort,
+ boolean install) {
+ Network osNet = osNetworkService.network(osPort.getNetworkId());
+ if (osNet == null) {
+ final String error = String.format(ERR_FLOW + "no network(%s) exists",
+ floatingIp.getFloatingIpAddress(),
+ osPort.getNetworkId());
+ throw new IllegalStateException(error);
+ }
+
+ MacAddress srcMac = MacAddress.valueOf(osPort.getMacAddress());
+ InstancePort instPort = instancePortService.instancePort(srcMac);
+ if (instPort == null) {
+ final String error = String.format(ERR_FLOW + "no host(MAC:%s) found",
+ floatingIp.getFloatingIpAddress(), srcMac);
+ throw new IllegalStateException(error);
+ }
+
+ setComputeNodeToGateway(instPort, osNet, install);
+ setDownstreamRules(floatingIp, osNet, instPort, install);
+ setUpstreamRules(floatingIp, osNet, instPort, install);
+ }
+
+ private void setComputeNodeToGateway(InstancePort instPort, Network osNet, boolean install) {
+ TrafficTreatment treatment;
+
+ TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPSrc(instPort.ipAddress().toIpPrefix())
+ .matchEthDst(Constants.DEFAULT_GATEWAY_MAC);
+
+ switch (osNet.getNetworkType()) {
+ case VXLAN:
+ sBuilder.matchTunnelId(Long.parseLong(osNet.getProviderSegID()));
+ break;
+ case VLAN:
+ sBuilder.matchVlanId(VlanId.vlanId(osNet.getProviderSegID()));
+ break;
+ default:
+ final String error = String.format(
+ ERR_UNSUPPORTED_NET_TYPE,
+ osNet.getNetworkType().toString());
+ throw new IllegalStateException(error);
+ }
+
+ OpenstackNode selectedGatewayNode = selectGatewayNode();
+ if (selectedGatewayNode == null) {
+ return;
+ }
+ treatment = DefaultTrafficTreatment.builder()
+ .extension(buildExtension(
+ deviceService,
+ instPort.deviceId(),
+ selectedGatewayNode.dataIp().getIp4Address()),
+ instPort.deviceId())
+ .setOutput(osNodeService.node(instPort.deviceId()).tunnelPortNum())
+ .build();
+
+ osFlowRuleService.setRule(
+ appId,
+ instPort.deviceId(),
+ sBuilder.build(),
+ treatment,
+ PRIORITY_EXTERNAL_FLOATING_ROUTING_RULE,
+ ROUTING_TABLE,
+ install);
+ }
+
+ private OpenstackNode selectGatewayNode() {
+ //TODO support multiple loadbalancing options.
+ return osNodeService.completeNodes(GATEWAY).stream().findAny().orElse(null);
+ }
+
+ private void setDownstreamRules(NetFloatingIP floatingIp, Network osNet,
+ InstancePort instPort, boolean install) {
+ OpenstackNode cNode = osNodeService.node(instPort.deviceId());
+ if (cNode == null) {
+ final String error = String.format("Cannot find openstack node for device %s",
+ instPort.deviceId());
+ throw new IllegalStateException(error);
+ }
+ if (osNet.getNetworkType() == NetworkType.VXLAN && cNode.dataIp() == null) {
+ final String error = String.format(ERR_FLOW +
+ "VXLAN mode is not ready for %s", floatingIp, cNode.hostname());
+ throw new IllegalStateException(error);
+ }
+ if (osNet.getNetworkType() == NetworkType.VLAN && cNode.vlanIntf() == null) {
+ final String error = String.format(ERR_FLOW +
+ "VLAN mode is not ready for %s", floatingIp, cNode.hostname());
+ throw new IllegalStateException(error);
+ }
+
+ IpAddress floating = IpAddress.valueOf(floatingIp.getFloatingIpAddress());
+ TrafficSelector externalSelector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(floating.toIpPrefix())
+ .build();
+
+ osNodeService.completeNodes(GATEWAY).forEach(gNode -> {
+ TrafficTreatment.Builder externalBuilder = DefaultTrafficTreatment.builder()
+ .setEthSrc(Constants.DEFAULT_GATEWAY_MAC)
+ .setEthDst(instPort.macAddress())
+ .setIpDst(instPort.ipAddress().getIp4Address());
+
+ switch (osNet.getNetworkType()) {
+ case VXLAN:
+ externalBuilder.setTunnelId(Long.valueOf(osNet.getProviderSegID()))
+ .extension(buildExtension(
+ deviceService,
+ gNode.intgBridge(),
+ cNode.dataIp().getIp4Address()),
+ gNode.intgBridge())
+ .setOutput(gNode.tunnelPortNum());
+ break;
+ case VLAN:
+ externalBuilder.pushVlan()
+ .setVlanId(VlanId.vlanId(osNet.getProviderSegID()))
+ .setOutput(gNode.vlanPortNum());
+ break;
+ default:
+ final String error = String.format(ERR_UNSUPPORTED_NET_TYPE,
+ osNet.getNetworkType());
+ throw new IllegalStateException(error);
+ }
+
+ osFlowRuleService.setRule(
+ appId,
+ gNode.intgBridge(),
+ externalSelector,
+ externalBuilder.build(),
+ PRIORITY_FLOATING_EXTERNAL,
+ GW_COMMON_TABLE,
+ install);
+
+ // access from one VM to the others via floating IP
+ TrafficSelector internalSelector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(floating.toIpPrefix())
+ .matchInPort(gNode.tunnelPortNum())
+ .build();
+
+ TrafficTreatment.Builder internalBuilder = DefaultTrafficTreatment.builder()
+ .setEthSrc(Constants.DEFAULT_GATEWAY_MAC)
+ .setEthDst(instPort.macAddress())
+ .setIpDst(instPort.ipAddress().getIp4Address());
+
+ switch (osNet.getNetworkType()) {
+ case VXLAN:
+ internalBuilder.setTunnelId(Long.valueOf(osNet.getProviderSegID()))
+ .extension(buildExtension(
+ deviceService,
+ gNode.intgBridge(),
+ cNode.dataIp().getIp4Address()),
+ gNode.intgBridge())
+ .setOutput(PortNumber.IN_PORT);
+ break;
+ case VLAN:
+ internalBuilder.pushVlan()
+ .setVlanId(VlanId.vlanId(osNet.getProviderSegID()))
+ .setOutput(PortNumber.IN_PORT);
+ break;
+ default:
+ final String error = String.format(ERR_UNSUPPORTED_NET_TYPE,
+ osNet.getNetworkType());
+ throw new IllegalStateException(error);
+ }
+
+ osFlowRuleService.setRule(
+ appId,
+ gNode.intgBridge(),
+ internalSelector,
+ internalBuilder.build(),
+ PRIORITY_FLOATING_INTERNAL,
+ GW_COMMON_TABLE,
+ install);
+ });
+ }
+
+ private void setUpstreamRules(NetFloatingIP floatingIp, Network osNet,
+ InstancePort instPort, boolean install) {
+ IpAddress floating = IpAddress.valueOf(floatingIp.getFloatingIpAddress());
+ TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPSrc(instPort.ipAddress().toIpPrefix());
+
+ switch (osNet.getNetworkType()) {
+ case VXLAN:
+ sBuilder.matchTunnelId(Long.valueOf(osNet.getProviderSegID()));
+ break;
+ case VLAN:
+ sBuilder.matchVlanId(VlanId.vlanId(osNet.getProviderSegID()));
+ break;
+ default:
+ final String error = String.format(ERR_UNSUPPORTED_NET_TYPE,
+ osNet.getNetworkType());
+ throw new IllegalStateException(error);
+ }
+
+ MacAddress externalPeerRouterMac = externalPeerRouterMac(osNet);
+ if (externalPeerRouterMac == null) {
+ return;
+ }
+
+
+ osNodeService.completeNodes(GATEWAY).forEach(gNode -> {
+ TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder()
+ .setIpSrc(floating.getIp4Address())
+ .setEthSrc(instPort.macAddress())
+ .setEthDst(externalPeerRouterMac);
+
+ if (osNet.getNetworkType().equals(NetworkType.VLAN)) {
+ tBuilder.popVlan();
+ }
+
+ osFlowRuleService.setRule(
+ appId,
+ gNode.intgBridge(),
+ sBuilder.build(),
+ tBuilder.setOutput(gNode.uplinkPortNum()).build(),
+ PRIORITY_FLOATING_EXTERNAL,
+ GW_COMMON_TABLE,
+ install);
+ });
+ }
+
+ private MacAddress externalPeerRouterMac(Network network) {
+ if (network == null) {
+ return null;
+ }
+
+ Subnet subnet = osNetworkService.subnets(network.getId()).stream().findAny().orElse(null);
+
+ if (subnet == null) {
+ return null;
+ }
+
+ RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
+ .filter(i -> Objects.equals(i.getSubnetId(), subnet.getId()))
+ .findAny().orElse(null);
+ if (osRouterIface == null) {
+ return null;
+ }
+
+ Router osRouter = osRouterService.router(osRouterIface.getId());
+ if (osRouter == null) {
+ return null;
+ }
+ if (osRouter.getExternalGatewayInfo() == null) {
+ return null;
+ }
+
+ ExternalGateway exGatewayInfo = osRouter.getExternalGatewayInfo();
+
+ return osNetworkService.externalPeerRouterMac(exGatewayInfo);
+ }
+ private class InternalFloatingIpListener implements OpenstackRouterListener {
+
+ @Override
+ public boolean isRelevant(OpenstackRouterEvent event) {
+ // do not allow to proceed without leadership
+ NodeId leader = leadershipService.getLeader(appId.name());
+ if (!Objects.equals(localNodeId, leader)) {
+ return false;
+ }
+ return event.floatingIp() != null;
+ }
+
+ @Override
+ public void event(OpenstackRouterEvent event) {
+ switch (event.type()) {
+ case OPENSTACK_FLOATING_IP_ASSOCIATED:
+ eventExecutor.execute(() -> {
+ NetFloatingIP osFip = event.floatingIp();
+ associateFloatingIp(osFip);
+ log.info("Associated floating IP {}:{}",
+ osFip.getFloatingIpAddress(), osFip.getFixedIpAddress());
+ });
+ break;
+ case OPENSTACK_FLOATING_IP_DISASSOCIATED:
+ eventExecutor.execute(() -> {
+ NetFloatingIP osFip = event.floatingIp();
+ disassociateFloatingIp(osFip, event.portId());
+ log.info("Disassociated floating IP {}:{}",
+ osFip.getFloatingIpAddress(), osFip.getFixedIpAddress());
+ });
+ break;
+ case OPENSTACK_FLOATING_IP_REMOVED:
+ eventExecutor.execute(() -> {
+ NetFloatingIP osFip = event.floatingIp();
+ if (!Strings.isNullOrEmpty(osFip.getPortId())) {
+ disassociateFloatingIp(osFip, osFip.getPortId());
+ }
+ log.info("Removed floating IP {}", osFip.getFloatingIpAddress());
+ });
+ break;
+ case OPENSTACK_FLOATING_IP_CREATED:
+ eventExecutor.execute(() -> {
+ NetFloatingIP osFip = event.floatingIp();
+ if (!Strings.isNullOrEmpty(osFip.getPortId())) {
+ associateFloatingIp(event.floatingIp());
+ }
+ log.info("Created floating IP {}", osFip.getFloatingIpAddress());
+ });
+ break;
+ case OPENSTACK_FLOATING_IP_UPDATED:
+ case OPENSTACK_ROUTER_CREATED:
+ case OPENSTACK_ROUTER_UPDATED:
+ case OPENSTACK_ROUTER_REMOVED:
+ case OPENSTACK_ROUTER_INTERFACE_ADDED:
+ case OPENSTACK_ROUTER_INTERFACE_UPDATED:
+ case OPENSTACK_ROUTER_INTERFACE_REMOVED:
+ default:
+ // do nothing for the other events
+ break;
+ }
+ }
+
+ private void associateFloatingIp(NetFloatingIP osFip) {
+ Port osPort = osNetworkService.port(osFip.getPortId());
+ if (osPort == null) {
+ final String error = String.format(ERR_FLOW + "port(%s) not found",
+ osFip.getFloatingIpAddress(), osFip.getPortId());
+ throw new IllegalStateException(error);
+ }
+ // set floating IP rules only if the port is associated to a VM
+ if (!Strings.isNullOrEmpty(osPort.getDeviceId())) {
+ setFloatingIpRules(osFip, osPort, true);
+ }
+ }
+
+ private void disassociateFloatingIp(NetFloatingIP osFip, String portId) {
+ Port osPort = osNetworkService.port(portId);
+ if (osPort == null) {
+ // FIXME when a port with floating IP removed without
+ // disassociation step, it can reach here
+ return;
+ }
+ // set floating IP rules only if the port is associated to a VM
+ if (!Strings.isNullOrEmpty(osPort.getDeviceId())) {
+ setFloatingIpRules(osFip, osPort, false);
+ }
+ }
+ }
+
+ private class InternalNodeListener implements OpenstackNodeListener {
+
+ @Override
+ public boolean isRelevant(OpenstackNodeEvent event) {
+ // do not allow to proceed without leadership
+ NodeId leader = leadershipService.getLeader(appId.name());
+ if (!Objects.equals(localNodeId, leader)) {
+ return false;
+ }
+ return event.subject().type() == GATEWAY;
+ }
+
+ @Override
+ public void event(OpenstackNodeEvent event) {
+
+ switch (event.type()) {
+ case OPENSTACK_NODE_COMPLETE:
+ eventExecutor.execute(() -> {
+ for (NetFloatingIP fip : osRouterService.floatingIps()) {
+ if (Strings.isNullOrEmpty(fip.getPortId())) {
+ continue;
+ }
+ Port osPort = osNetworkService.port(fip.getPortId());
+ if (osPort == null) {
+ log.warn("Failed to set floating IP {}", fip.getId());
+ continue;
+ }
+ setFloatingIpRules(fip, osPort, true);
+ }
+ });
+ break;
+ case OPENSTACK_NODE_CREATED:
+ case OPENSTACK_NODE_UPDATED:
+ case OPENSTACK_NODE_REMOVED:
+ case OPENSTACK_NODE_INCOMPLETE:
+ default:
+ // do nothing
+ break;
+ }
+ }
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingHandler.java
new file mode 100644
index 0000000..11b3247
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingHandler.java
@@ -0,0 +1,1058 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.DriverService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortEvent;
+import org.onosproject.openstacknetworking.api.InstancePortListener;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterEvent;
+import org.onosproject.openstacknetworking.api.OpenstackRouterListener;
+import org.onosproject.openstacknetworking.api.OpenstackRouterService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNode.NetworkMode;
+import org.onosproject.openstacknode.api.OpenstackNodeEvent;
+import org.onosproject.openstacknode.api.OpenstackNodeListener;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.openstack4j.model.network.ExternalGateway;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.NetworkType;
+import org.openstack4j.model.network.Router;
+import org.openstack4j.model.network.RouterInterface;
+import org.openstack4j.model.network.Subnet;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Dictionary;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_EXTERNAL_ROUTER_MAC;
+import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC;
+import static org.onosproject.openstacknetworking.api.Constants.FORWARDING_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.GW_COMMON_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_EXTERNAL_ROUTING_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ICMP_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_INTERNAL_ROUTING_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_STATEFUL_SNAT_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_SWITCHING_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.ROUTING_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ADMIN_RULE;
+import static org.onosproject.openstacknetworking.impl.RulePopulatorUtil.buildExtension;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
+
+/**
+ * Handles OpenStack router events.
+ */
+@Component(immediate = true)
+public class OpenstackRoutingHandler {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final String MSG_ENABLED = "Enabled ";
+ private static final String MSG_DISABLED = "Disabled ";
+ private static final String ERR_SET_FLOWS = "Failed to set flows for router %s:";
+ private static final String ERR_UNSUPPORTED_NET_TYPE = "Unsupported network type";
+ private static final boolean USE_STATEFUL_SNAT = false;
+
+ @Property(name = "useStatefulSnat", boolValue = USE_STATEFUL_SNAT,
+ label = "Use Stateful SNAT for source NATing")
+ private boolean useStatefulSnat = USE_STATEFUL_SNAT;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected LeadershipService leadershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackNodeService osNodeService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackNetworkService osNetworkService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackRouterService osRouterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected InstancePortService instancePortService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackFlowRuleService osFlowRuleService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipService mastershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DriverService driverService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ComponentConfigService configService;
+
+ private final ExecutorService eventExecutor = newSingleThreadScheduledExecutor(
+ groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+ private final OpenstackNodeListener osNodeListener = new InternalNodeEventListener();
+ private final OpenstackRouterListener osRouterListener = new InternalRouterEventListener();
+ private final InstancePortListener instancePortListener = new InternalInstancePortListener();
+
+ private ApplicationId appId;
+ private NodeId localNodeId;
+
+ @Activate
+ protected void activate() {
+ appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+ localNodeId = clusterService.getLocalNode().id();
+ leadershipService.runForLeadership(appId.name());
+ osNodeService.addListener(osNodeListener);
+ osRouterService.addListener(osRouterListener);
+ instancePortService.addListener(instancePortListener);
+ configService.registerProperties(getClass());
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ osRouterService.removeListener(osRouterListener);
+ osNodeService.removeListener(osNodeListener);
+ instancePortService.removeListener(instancePortListener);
+ leadershipService.withdraw(appId.name());
+ configService.unregisterProperties(getClass(), false);
+ eventExecutor.shutdown();
+
+ log.info("Stopped");
+ }
+
+ @Modified
+ protected void modified(ComponentContext context) {
+ Dictionary<?, ?> properties = context.getProperties();
+ Boolean flag;
+
+ flag = Tools.isPropertyEnabled(properties, "useStatefulSnat");
+ if (flag == null) {
+ log.info("useStatefulSnat is not configured, " +
+ "using current value of {}", useStatefulSnat);
+ } else {
+ useStatefulSnat = flag;
+ log.info("Configured. useStatefulSnat is {}",
+ useStatefulSnat ? "enabled" : "disabled");
+ }
+
+ resetSnatRules();
+ }
+
+ private void routerUpdated(Router osRouter) {
+ ExternalGateway exGateway = osRouter.getExternalGatewayInfo();
+ osRouterService.routerInterfaces(osRouter.getId()).forEach(iface -> {
+ Network network = osNetworkService.network(osNetworkService.subnet(iface.getSubnetId())
+ .getNetworkId());
+ setRouterAdminRules(network.getProviderSegID(), network.getNetworkType(), !osRouter.isAdminStateUp());
+ });
+
+ if (exGateway == null) {
+ osNetworkService.deleteExternalPeerRouter(exGateway);
+ osRouterService.routerInterfaces(osRouter.getId()).forEach(iface -> {
+ setSourceNat(osRouter, iface, false);
+ });
+ } else {
+ osNetworkService.deriveExternalPeerRouterMac(exGateway, osRouter);
+ osRouterService.routerInterfaces(osRouter.getId()).forEach(iface -> {
+ setSourceNat(osRouter, iface, exGateway.isEnableSnat());
+ });
+ }
+ }
+
+ private void routerRemove(Router osRouter) {
+ osRouterService.routerInterfaces(osRouter.getId()).forEach(iface -> {
+ Network network = osNetworkService.network(osNetworkService.subnet(iface.getSubnetId())
+ .getNetworkId());
+ setRouterAdminRules(network.getProviderSegID(), network.getNetworkType(), false);
+ });
+ }
+
+ private void routerIfaceAdded(Router osRouter, RouterInterface osRouterIface) {
+ Subnet osSubnet = osNetworkService.subnet(osRouterIface.getSubnetId());
+ if (osSubnet == null) {
+ final String error = String.format(
+ ERR_SET_FLOWS + "subnet %s does not exist",
+ osRouterIface.getId(),
+ osRouterIface.getSubnetId());
+ throw new IllegalStateException(error);
+ }
+
+ if (!osRouter.isAdminStateUp()) {
+ Network network = osNetworkService.network(osSubnet.getNetworkId());
+ setRouterAdminRules(network.getProviderSegID(), network.getNetworkType(), true);
+ }
+
+ setInternalRoutes(osRouter, osSubnet, true);
+ setGatewayIcmp(osSubnet, true);
+ ExternalGateway exGateway = osRouter.getExternalGatewayInfo();
+ if (exGateway != null && exGateway.isEnableSnat()) {
+ setSourceNat(osRouter, osRouterIface, true);
+ }
+ log.info("Connected subnet({}) to {}", osSubnet.getCidr(), osRouter.getName());
+ }
+
+ private void routerIfaceRemoved(Router osRouter, RouterInterface osRouterIface) {
+ Subnet osSubnet = osNetworkService.subnet(osRouterIface.getSubnetId());
+ if (osSubnet == null) {
+ final String error = String.format(
+ ERR_SET_FLOWS + "subnet %s does not exist",
+ osRouterIface.getId(),
+ osRouterIface.getSubnetId());
+ throw new IllegalStateException(error);
+ }
+
+ if (!osRouter.isAdminStateUp()) {
+ Network network = osNetworkService.network(osSubnet.getNetworkId());
+ setRouterAdminRules(network.getProviderSegID(), network.getNetworkType(), false);
+ }
+
+ setInternalRoutes(osRouter, osSubnet, false);
+ setGatewayIcmp(osSubnet, false);
+ ExternalGateway exGateway = osRouter.getExternalGatewayInfo();
+ if (exGateway != null && exGateway.isEnableSnat()) {
+ setSourceNat(osRouter, osRouterIface, false);
+ }
+ log.info("Disconnected subnet({}) from {}", osSubnet.getCidr(), osRouter.getName());
+ }
+
+ private void setSourceNat(Router osRouter, RouterInterface routerIface, boolean install) {
+ Subnet osSubnet = osNetworkService.subnet(routerIface.getSubnetId());
+ Network osNet = osNetworkService.network(osSubnet.getNetworkId());
+
+ osNodeService.completeNodes(COMPUTE).forEach(cNode -> {
+ setRulesToGateway(cNode, osNet.getProviderSegID(),
+ IpPrefix.valueOf(osSubnet.getCidr()), osNet.getNetworkType(),
+ install);
+ });
+
+ if (useStatefulSnat) {
+ setStatefulSnatRules(routerIface, install);
+ } else {
+ setReactiveSnatRules(routerIface, install);
+ }
+
+ final String updateStr = install ? MSG_ENABLED : MSG_DISABLED;
+ log.info(updateStr + "external access for subnet({})", osSubnet.getCidr());
+ }
+
+ private void setStatefulSnatRules(RouterInterface routerIface, boolean install) {
+ Subnet osSubnet = osNetworkService.subnet(routerIface.getSubnetId());
+ Network osNet = osNetworkService.network(osSubnet.getNetworkId());
+
+ Optional<Router> osRouter = osRouterService.routers().stream()
+ .filter(router -> osRouterService.routerInterfaces(routerIface.getId()) != null)
+ .findAny();
+
+ if (!osRouter.isPresent()) {
+ log.error("Cannot find a router for router interface {} ", routerIface);
+ return;
+ }
+ IpAddress natAddress = getGatewayIpAddress(osRouter.get());
+ if (natAddress == null) {
+ return;
+ }
+ String netId = osNetworkService.subnet(routerIface.getSubnetId()).getNetworkId();
+
+ osNodeService.completeNodes(OpenstackNode.NodeType.GATEWAY)
+ .forEach(gwNode -> {
+ instancePortService.instancePorts(netId).stream()
+ .forEach(port -> setRulesForSnatIngressRule(gwNode.intgBridge(),
+ Long.parseLong(osNet.getProviderSegID()),
+ IpPrefix.valueOf(port.ipAddress(), 32),
+ port.deviceId(),
+ install));
+
+ setOvsNatIngressRule(gwNode.intgBridge(),
+ IpPrefix.valueOf(natAddress, 32),
+ Constants.DEFAULT_EXTERNAL_ROUTER_MAC, install);
+ setOvsNatEgressRule(gwNode.intgBridge(),
+ natAddress, Long.parseLong(osNet.getProviderSegID()),
+ gwNode.patchPortNum(), install);
+ });
+ }
+
+ private void setReactiveSnatRules(RouterInterface routerIface, boolean install) {
+ Subnet osSubnet = osNetworkService.subnet(routerIface.getSubnetId());
+ Network osNet = osNetworkService.network(osSubnet.getNetworkId());
+
+ osNodeService.completeNodes(GATEWAY)
+ .forEach(gwNode -> setRulesToController(
+ gwNode.intgBridge(),
+ osNet.getProviderSegID(),
+ IpPrefix.valueOf(osSubnet.getCidr()),
+ osNet.getNetworkType(),
+ install));
+ }
+
+ private IpAddress getGatewayIpAddress(Router osRouter) {
+
+ String extNetId = osNetworkService.network(osRouter.getExternalGatewayInfo().getNetworkId()).getId();
+ Optional<Subnet> extSubnet = osNetworkService.subnets().stream()
+ .filter(subnet -> subnet.getNetworkId().equals(extNetId))
+ .findAny();
+
+ if (!extSubnet.isPresent()) {
+ log.error("Cannot find externel subnet for the router");
+ return null;
+ }
+
+ return IpAddress.valueOf(extSubnet.get().getGateway());
+ }
+
+ private void resetSnatRules() {
+ if (useStatefulSnat) {
+ osRouterService.routerInterfaces().forEach(
+ routerIface -> {
+ setReactiveSnatRules(routerIface, false);
+ setStatefulSnatRules(routerIface, true);
+ }
+ );
+ } else {
+ osRouterService.routerInterfaces().forEach(
+ routerIface -> {
+ setStatefulSnatRules(routerIface, false);
+ setReactiveSnatRules(routerIface, true);
+ }
+ );
+ }
+ }
+
+ private void setGatewayIcmp(Subnet osSubnet, boolean install) {
+ OpenstackNode sourceNatGateway = osNodeService.completeNodes(GATEWAY).stream().findFirst().orElse(null);
+
+ if (sourceNatGateway == null) {
+ return;
+ }
+
+ if (Strings.isNullOrEmpty(osSubnet.getGateway())) {
+ // do nothing if no gateway is set
+ return;
+ }
+
+ // take ICMP request to a subnet gateway through gateway node group
+ Network network = osNetworkService.network(osSubnet.getNetworkId());
+ switch (network.getNetworkType()) {
+ case VXLAN:
+ osNodeService.completeNodes(COMPUTE).stream()
+ .filter(cNode -> cNode.dataIp() != null)
+ .forEach(cNode -> setRulesToGatewayWithDstIp(
+ cNode,
+ sourceNatGateway,
+ network.getProviderSegID(),
+ IpAddress.valueOf(osSubnet.getGateway()),
+ NetworkMode.VXLAN,
+ install));
+ break;
+ case VLAN:
+ osNodeService.completeNodes(COMPUTE).stream()
+ .filter(cNode -> cNode.vlanPortNum() != null)
+ .forEach(cNode -> setRulesToGatewayWithDstIp(
+ cNode,
+ sourceNatGateway,
+ network.getProviderSegID(),
+ IpAddress.valueOf(osSubnet.getGateway()),
+ NetworkMode.VLAN,
+ install));
+ break;
+ default:
+ final String error = String.format(
+ ERR_UNSUPPORTED_NET_TYPE + "%s",
+ network.getNetworkType().toString());
+ throw new IllegalStateException(error);
+ }
+
+ IpAddress gatewayIp = IpAddress.valueOf(osSubnet.getGateway());
+ osNodeService.completeNodes(GATEWAY).forEach(gNode -> {
+ setGatewayIcmpRule(
+ gatewayIp,
+ gNode.intgBridge(),
+ install
+ );
+ });
+
+ final String updateStr = install ? MSG_ENABLED : MSG_DISABLED;
+ log.debug(updateStr + "ICMP to {}", osSubnet.getGateway());
+ }
+
+ private void setInternalRoutes(Router osRouter, Subnet updatedSubnet, boolean install) {
+ Network updatedNetwork = osNetworkService.network(updatedSubnet.getNetworkId());
+ Set<Subnet> routableSubnets = routableSubnets(osRouter, updatedSubnet.getId());
+ String updatedSegmendId = getSegmentId(updatedSubnet);
+
+ // installs rule from/to my subnet intentionally to fix ICMP failure
+ // to my subnet gateway if no external gateway added to the router
+ osNodeService.completeNodes(COMPUTE).forEach(cNode -> {
+ setInternalRouterRules(
+ cNode.intgBridge(),
+ updatedSegmendId,
+ updatedSegmendId,
+ IpPrefix.valueOf(updatedSubnet.getCidr()),
+ IpPrefix.valueOf(updatedSubnet.getCidr()),
+ updatedNetwork.getNetworkType(),
+ install
+ );
+
+ routableSubnets.forEach(subnet -> {
+ setInternalRouterRules(
+ cNode.intgBridge(),
+ updatedSegmendId,
+ getSegmentId(subnet),
+ IpPrefix.valueOf(updatedSubnet.getCidr()),
+ IpPrefix.valueOf(subnet.getCidr()),
+ updatedNetwork.getNetworkType(),
+ install
+ );
+ setInternalRouterRules(
+ cNode.intgBridge(),
+ getSegmentId(subnet),
+ updatedSegmendId,
+ IpPrefix.valueOf(subnet.getCidr()),
+ IpPrefix.valueOf(updatedSubnet.getCidr()),
+ updatedNetwork.getNetworkType(),
+ install
+ );
+ });
+ });
+
+
+ final String updateStr = install ? MSG_ENABLED : MSG_DISABLED;
+ routableSubnets.forEach(subnet -> log.debug(
+ updateStr + "route between subnet:{} and subnet:{}",
+ subnet.getCidr(),
+ updatedSubnet.getCidr()));
+ }
+
+ private Set<Subnet> routableSubnets(Router osRouter, String osSubnetId) {
+ Set<Subnet> osSubnets = osRouterService.routerInterfaces(osRouter.getId())
+ .stream()
+ .filter(iface -> !Objects.equals(iface.getSubnetId(), osSubnetId))
+ .map(iface -> osNetworkService.subnet(iface.getSubnetId()))
+ .collect(Collectors.toSet());
+ return ImmutableSet.copyOf(osSubnets);
+ }
+
+ private String getSegmentId(Subnet osSubnet) {
+ return osNetworkService.network(osSubnet.getNetworkId()).getProviderSegID();
+ }
+
+ private void setGatewayIcmpRule(IpAddress gatewayIp, DeviceId deviceId, boolean install) {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPProtocol(IPv4.PROTOCOL_ICMP)
+ .matchIPDst(gatewayIp.getIp4Address().toIpPrefix())
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.CONTROLLER)
+ .build();
+
+ osFlowRuleService.setRule(
+ appId,
+ deviceId,
+ selector,
+ treatment,
+ PRIORITY_ICMP_RULE,
+ Constants.GW_COMMON_TABLE,
+ install);
+ }
+
+ private void setInternalRouterRules(DeviceId deviceId, String srcSegmentId, String dstSegmentId,
+ IpPrefix srcSubnet, IpPrefix dstSubnet,
+ NetworkType networkType, boolean install) {
+ TrafficSelector selector;
+ TrafficTreatment treatment;
+ switch (networkType) {
+ case VXLAN:
+ selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchTunnelId(Long.parseLong(srcSegmentId))
+ .matchIPSrc(srcSubnet.getIp4Prefix())
+ .matchIPDst(dstSubnet.getIp4Prefix())
+ .build();
+
+ treatment = DefaultTrafficTreatment.builder()
+ .setTunnelId(Long.parseLong(dstSegmentId))
+ .transition(FORWARDING_TABLE)
+ .build();
+
+ osFlowRuleService.setRule(
+ appId,
+ deviceId,
+ selector,
+ treatment,
+ PRIORITY_INTERNAL_ROUTING_RULE,
+ ROUTING_TABLE,
+ install);
+
+ selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchTunnelId(Long.parseLong(dstSegmentId))
+ .matchIPSrc(srcSubnet.getIp4Prefix())
+ .matchIPDst(dstSubnet.getIp4Prefix())
+ .build();
+
+ treatment = DefaultTrafficTreatment.builder()
+ .setTunnelId(Long.parseLong(dstSegmentId))
+ .transition(FORWARDING_TABLE)
+ .build();
+
+ osFlowRuleService.setRule(
+ appId,
+ deviceId,
+ selector,
+ treatment,
+ PRIORITY_INTERNAL_ROUTING_RULE,
+ ROUTING_TABLE,
+ install);
+ break;
+ case VLAN:
+ selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchVlanId(VlanId.vlanId(srcSegmentId))
+ .matchIPSrc(srcSubnet.getIp4Prefix())
+ .matchIPDst(dstSubnet.getIp4Prefix())
+ .build();
+
+ treatment = DefaultTrafficTreatment.builder()
+ .setVlanId(VlanId.vlanId(dstSegmentId))
+ .transition(FORWARDING_TABLE)
+ .build();
+
+ osFlowRuleService.setRule(
+ appId,
+ deviceId,
+ selector,
+ treatment,
+ PRIORITY_INTERNAL_ROUTING_RULE,
+ ROUTING_TABLE,
+ install);
+
+ selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchVlanId(VlanId.vlanId(dstSegmentId))
+ .matchIPSrc(srcSubnet.getIp4Prefix())
+ .matchIPDst(dstSubnet.getIp4Prefix())
+ .build();
+
+ treatment = DefaultTrafficTreatment.builder()
+ .setVlanId(VlanId.vlanId(dstSegmentId))
+ .transition(FORWARDING_TABLE)
+ .build();
+
+ osFlowRuleService.setRule(
+ appId,
+ deviceId,
+ selector,
+ treatment,
+ PRIORITY_INTERNAL_ROUTING_RULE,
+ ROUTING_TABLE,
+ install);
+ break;
+ default:
+ final String error = String.format(
+ ERR_UNSUPPORTED_NET_TYPE + "%s",
+ networkType.toString());
+ throw new IllegalStateException(error);
+ }
+
+ }
+
+ private void setRulesToGateway(OpenstackNode osNode, String segmentId, IpPrefix srcSubnet,
+ NetworkType networkType, boolean install) {
+ TrafficTreatment treatment;
+ OpenstackNode sourceNatGateway = osNodeService.completeNodes(GATEWAY).stream().findFirst().orElse(null);
+
+ if (sourceNatGateway == null) {
+ return;
+ }
+
+ TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPSrc(srcSubnet.getIp4Prefix())
+ .matchEthDst(Constants.DEFAULT_GATEWAY_MAC);
+
+ switch (networkType) {
+ case VXLAN:
+ sBuilder.matchTunnelId(Long.parseLong(segmentId));
+ break;
+ case VLAN:
+ sBuilder.matchVlanId(VlanId.vlanId(segmentId));
+ break;
+ default:
+ final String error = String.format(
+ ERR_UNSUPPORTED_NET_TYPE + "%s",
+ networkType.toString());
+ throw new IllegalStateException(error);
+ }
+
+ treatment = DefaultTrafficTreatment.builder()
+ .extension(buildExtension(
+ deviceService,
+ osNode.intgBridge(),
+ sourceNatGateway.dataIp().getIp4Address()),
+ osNode.intgBridge())
+ .setOutput(osNode.tunnelPortNum())
+ .build();
+
+ osFlowRuleService.setRule(
+ appId,
+ osNode.intgBridge(),
+ sBuilder.build(),
+ treatment,
+ PRIORITY_EXTERNAL_ROUTING_RULE,
+ ROUTING_TABLE,
+ install);
+ }
+
+ private void setRulesForSnatIngressRule(DeviceId deviceId, Long vni, IpPrefix destVmIp,
+ DeviceId dstDeviceId, boolean install) {
+
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(destVmIp)
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setTunnelId(vni)
+ .extension(buildExtension(
+ deviceService,
+ deviceId,
+ osNodeService.node(dstDeviceId).dataIp().getIp4Address()),
+ deviceId)
+ .setOutput(osNodeService.node(deviceId).tunnelPortNum())
+ .build();
+
+ osFlowRuleService.setRule(
+ appId,
+ deviceId,
+ selector,
+ treatment,
+ PRIORITY_EXTERNAL_ROUTING_RULE,
+ Constants.GW_COMMON_TABLE,
+ install);
+ }
+
+ private void setRulesToGatewayWithDstIp(OpenstackNode osNode, OpenstackNode sourceNatGateway,
+ String segmentId, IpAddress dstIp,
+ NetworkMode networkMode, boolean install) {
+ TrafficSelector selector;
+ if (networkMode.equals(NetworkMode.VXLAN)) {
+ selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchTunnelId(Long.valueOf(segmentId))
+ .matchIPDst(dstIp.getIp4Address().toIpPrefix())
+ .build();
+ } else {
+ selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchVlanId(VlanId.vlanId(segmentId))
+ .matchIPDst(dstIp.getIp4Address().toIpPrefix())
+ .build();
+ }
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .extension(buildExtension(
+ deviceService,
+ osNode.intgBridge(),
+ sourceNatGateway.dataIp().getIp4Address()),
+ osNode.intgBridge())
+ .setOutput(osNode.tunnelPortNum())
+ .build();
+
+ osFlowRuleService.setRule(
+ appId,
+ osNode.intgBridge(),
+ selector,
+ treatment,
+ PRIORITY_SWITCHING_RULE,
+ ROUTING_TABLE,
+ install);
+ }
+
+ private void setOvsNatIngressRule(DeviceId deviceId, IpPrefix cidr, MacAddress dstMac, boolean install) {
+
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(cidr)
+ .build();
+
+ ExtensionTreatment natTreatment = RulePopulatorUtil.niciraConnTrackTreatmentBuilder(driverService, deviceId)
+ .commit(false)
+ .natAction(true)
+ .table((short) 0)
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setEthDst(dstMac)
+ .extension(natTreatment, deviceId)
+ .build();
+
+ osFlowRuleService.setRule(
+ appId,
+ deviceId,
+ selector,
+ treatment,
+ PRIORITY_STATEFUL_SNAT_RULE,
+ GW_COMMON_TABLE,
+ install);
+ }
+
+ private void setOvsNatEgressRule(DeviceId deviceId, IpAddress natAddress, long vni, PortNumber output,
+ boolean install) {
+
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchEthDst(DEFAULT_GATEWAY_MAC)
+ .matchTunnelId(vni)
+ .build();
+
+ ExtensionTreatment natTreatment = RulePopulatorUtil.niciraConnTrackTreatmentBuilder(driverService, deviceId)
+ .commit(true)
+ .natAction(true)
+ .natIp(natAddress)
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .extension(natTreatment, deviceId)
+ .setEthDst(DEFAULT_EXTERNAL_ROUTER_MAC)
+ .setEthSrc(DEFAULT_GATEWAY_MAC)
+ .setOutput(output)
+ .build();
+
+ osFlowRuleService.setRule(
+ appId,
+ deviceId,
+ selector,
+ treatment,
+ PRIORITY_STATEFUL_SNAT_RULE,
+ GW_COMMON_TABLE,
+ install);
+ }
+
+ private void setRulesToController(DeviceId deviceId, String segmentId, IpPrefix srcSubnet,
+ NetworkType networkType, boolean install) {
+ TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPSrc(srcSubnet);
+
+ switch (networkType) {
+ case VXLAN:
+ sBuilder.matchTunnelId(Long.parseLong(segmentId))
+ .matchEthDst(Constants.DEFAULT_GATEWAY_MAC);
+ break;
+ case VLAN:
+ sBuilder.matchVlanId(VlanId.vlanId(segmentId))
+ .matchEthDst(osNodeService.node(deviceId).vlanPortMac());
+ break;
+ default:
+ final String error = String.format(
+ ERR_UNSUPPORTED_NET_TYPE + "%s",
+ networkType.toString());
+ throw new IllegalStateException(error);
+ }
+
+ TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder()
+ .setEthDst(Constants.DEFAULT_GATEWAY_MAC);
+
+ if (networkType.equals(NetworkType.VLAN)) {
+ tBuilder.popVlan();
+ }
+
+ tBuilder.setOutput(PortNumber.CONTROLLER);
+
+ osFlowRuleService.setRule(
+ appId,
+ deviceId,
+ sBuilder.build(),
+ tBuilder.build(),
+ PRIORITY_EXTERNAL_ROUTING_RULE,
+ GW_COMMON_TABLE,
+ install);
+
+
+ // Sends ICMP response to controller for SNATing ingress traffic
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPProtocol(IPv4.PROTOCOL_ICMP)
+ .matchIcmpType(ICMP.TYPE_ECHO_REPLY)
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.CONTROLLER)
+ .build();
+
+ osFlowRuleService.setRule(
+ appId,
+ deviceId,
+ selector,
+ treatment,
+ PRIORITY_INTERNAL_ROUTING_RULE,
+ GW_COMMON_TABLE,
+ install);
+ }
+
+ private void setRouterAdminRules(String segmentId, NetworkType networkType, boolean install) {
+ TrafficTreatment treatment;
+ TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4);
+
+ switch (networkType) {
+ case VXLAN:
+ sBuilder.matchTunnelId(Long.parseLong(segmentId));
+ break;
+ case VLAN:
+ sBuilder.matchVlanId(VlanId.vlanId(segmentId));
+ break;
+ default:
+ final String error = String.format(
+ ERR_UNSUPPORTED_NET_TYPE + "%s",
+ networkType.toString());
+ throw new IllegalStateException(error);
+ }
+
+ treatment = DefaultTrafficTreatment.builder()
+ .drop()
+ .build();
+
+ osNodeService.completeNodes().stream()
+ .filter(osNode -> osNode.type() == COMPUTE)
+ .forEach(osNode -> {
+ osFlowRuleService.setRule(
+ appId,
+ osNode.intgBridge(),
+ sBuilder.build(),
+ treatment,
+ PRIORITY_ADMIN_RULE,
+ ROUTING_TABLE,
+ install);
+ });
+ }
+
+ private class InternalRouterEventListener implements OpenstackRouterListener {
+
+ @Override
+ public boolean isRelevant(OpenstackRouterEvent event) {
+ // do not allow to proceed without leadership
+ NodeId leader = leadershipService.getLeader(appId.name());
+ return Objects.equals(localNodeId, leader);
+ }
+
+ // FIXME only one leader in the cluster should process
+ @Override
+ public void event(OpenstackRouterEvent event) {
+ switch (event.type()) {
+ case OPENSTACK_ROUTER_CREATED:
+ log.debug("Router(name:{}, ID:{}) is created",
+ event.subject().getName(),
+ event.subject().getId());
+ eventExecutor.execute(() -> routerUpdated(event.subject()));
+ break;
+ case OPENSTACK_ROUTER_UPDATED:
+ log.debug("Router(name:{}, ID:{}) is updated",
+ event.subject().getName(),
+ event.subject().getId());
+ eventExecutor.execute(() -> routerUpdated(event.subject()));
+ break;
+ case OPENSTACK_ROUTER_REMOVED:
+ log.debug("Router(name:{}, ID:{}) is removed",
+ event.subject().getName(),
+ event.subject().getId());
+ eventExecutor.execute(() -> routerRemove(event.subject()));
+ break;
+ case OPENSTACK_ROUTER_INTERFACE_ADDED:
+ log.debug("Router interface {} added to router {}",
+ event.routerIface().getPortId(),
+ event.routerIface().getId());
+ eventExecutor.execute(() -> routerIfaceAdded(
+ event.subject(),
+ event.routerIface()));
+ break;
+ case OPENSTACK_ROUTER_INTERFACE_UPDATED:
+ log.debug("Router interface {} on {} updated",
+ event.routerIface().getPortId(),
+ event.routerIface().getId());
+ break;
+ case OPENSTACK_ROUTER_INTERFACE_REMOVED:
+ log.debug("Router interface {} removed from router {}",
+ event.routerIface().getPortId(),
+ event.routerIface().getId());
+ eventExecutor.execute(() -> routerIfaceRemoved(
+ event.subject(),
+ event.routerIface()));
+ break;
+ case OPENSTACK_ROUTER_GATEWAY_ADDED:
+ log.debug("Router external gateway {} added", event.externalGateway().getNetworkId());
+ break;
+ case OPENSTACK_ROUTER_GATEWAY_REMOVED:
+ log.debug("Router external gateway {} removed", event.externalGateway().getNetworkId());
+ break;
+ case OPENSTACK_FLOATING_IP_CREATED:
+ case OPENSTACK_FLOATING_IP_UPDATED:
+ case OPENSTACK_FLOATING_IP_REMOVED:
+ case OPENSTACK_FLOATING_IP_ASSOCIATED:
+ case OPENSTACK_FLOATING_IP_DISASSOCIATED:
+ default:
+ // do nothing for the other events
+ break;
+ }
+ }
+ }
+
+ private class InternalNodeEventListener implements OpenstackNodeListener {
+
+ @Override
+ public boolean isRelevant(OpenstackNodeEvent event) {
+ // do not allow to proceed without leadership
+ NodeId leader = leadershipService.getLeader(appId.name());
+ return Objects.equals(localNodeId, leader);
+ }
+
+ @Override
+ public void event(OpenstackNodeEvent event) {
+ OpenstackNode osNode = event.subject();
+
+ switch (event.type()) {
+ case OPENSTACK_NODE_COMPLETE:
+ case OPENSTACK_NODE_INCOMPLETE:
+ eventExecutor.execute(() -> {
+ log.info("Reconfigure routers for {}", osNode.hostname());
+ reconfigureRouters();
+ });
+ break;
+ case OPENSTACK_NODE_CREATED:
+ case OPENSTACK_NODE_UPDATED:
+ case OPENSTACK_NODE_REMOVED:
+ default:
+ break;
+ }
+ }
+
+ private void reconfigureRouters() {
+ osRouterService.routers().forEach(osRouter -> {
+ routerUpdated(osRouter);
+ osRouterService.routerInterfaces(osRouter.getId()).forEach(iface -> {
+ routerIfaceAdded(osRouter, iface);
+ });
+ });
+ }
+ }
+
+ private class InternalInstancePortListener implements InstancePortListener {
+
+ @Override
+ public boolean isRelevant(InstancePortEvent event) {
+ InstancePort instPort = event.subject();
+ return mastershipService.isLocalMaster(instPort.deviceId());
+ }
+
+ @Override
+ public void event(InstancePortEvent event) {
+ InstancePort instPort = event.subject();
+ switch (event.type()) {
+ case OPENSTACK_INSTANCE_PORT_UPDATED:
+ case OPENSTACK_INSTANCE_PORT_DETECTED:
+ eventExecutor.execute(() -> {
+ log.info("RoutingHandler: Instance port detected MAC:{} IP:{}",
+ instPort.macAddress(),
+ instPort.ipAddress());
+ instPortDetected(event.subject());
+ });
+ break;
+ case OPENSTACK_INSTANCE_PORT_VANISHED:
+ eventExecutor.execute(() -> {
+ log.info("RoutingHandler: Instance port vanished MAC:{} IP:{}",
+ instPort.macAddress(),
+ instPort.ipAddress());
+ instPortRemoved(event.subject());
+ });
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void instPortDetected(InstancePort instPort) {
+ osNodeService.completeNodes(GATEWAY)
+ .forEach(gwNode -> setRulesForSnatIngressRule(gwNode.intgBridge(),
+ Long.parseLong(osNetworkService.network(instPort.networkId()).getProviderSegID()),
+ IpPrefix.valueOf(instPort.ipAddress(), 32),
+ instPort.deviceId(),
+ true));
+ }
+
+ private void instPortRemoved(InstancePort instPort) {
+ osNodeService.completeNodes(GATEWAY)
+ .forEach(gwNode -> setRulesForSnatIngressRule(gwNode.intgBridge(),
+ Long.parseLong(osNetworkService.network(instPort.networkId()).getProviderSegID()),
+ IpPrefix.valueOf(instPort.ipAddress(), 32),
+ instPort.deviceId(),
+ false));
+ }
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingIcmpHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingIcmpHandler.java
new file mode 100644
index 0000000..13c9e18
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingIcmpHandler.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.ICMP;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+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.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.openstack4j.model.network.ExternalGateway;
+import org.openstack4j.model.network.IP;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.Router;
+import org.openstack4j.model.network.RouterInterface;
+import org.openstack4j.model.network.Subnet;
+import org.openstack4j.openstack.networking.domain.NeutronIP;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
+import static org.slf4j.LoggerFactory.getLogger;
+
+
+/**
+ * Handles ICMP packet received from a gateway node.
+ * For a request for virtual network subnet gateway, it generates fake ICMP reply.
+ * For a request for the external network, it does source NAT with the public IP and
+ * forward the request to the external only if the requested virtual subnet has
+ * external connectivity.
+ */
+@Component(immediate = true)
+public class OpenstackRoutingIcmpHandler {
+
+ protected final Logger log = getLogger(getClass());
+
+ private static final String ERR_REQ = "Failed to handle ICMP request: ";
+ private static final String ERR_DUPLICATE = " already exists";
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected PacketService packetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackNodeService osNodeService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected InstancePortService instancePortService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackNetworkService osNetworkService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackRouterService osRouterService;
+
+ private final ExecutorService eventExecutor = newSingleThreadExecutor(
+ groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+ private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
+ private ConsistentMap<String, InstancePort> icmpInfoMap;
+
+ private static final KryoNamespace SERIALIZER_ICMP_MAP = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(InstancePort.class)
+ .register(HostBasedInstancePort.class)
+ .build();
+
+ private ApplicationId appId;
+
+ @Activate
+ protected void activate() {
+ appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+ packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
+
+ icmpInfoMap = storageService.<String, InstancePort>consistentMapBuilder()
+ .withSerializer(Serializer.using(SERIALIZER_ICMP_MAP))
+ .withName("openstack-icmpmap")
+ .withApplicationId(appId)
+ .build();
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ packetService.removeProcessor(packetProcessor);
+ eventExecutor.shutdown();
+
+ log.info("Stopped");
+ }
+
+ private void processIcmpPacket(PacketContext context, Ethernet ethernet) {
+ IPv4 ipPacket = (IPv4) ethernet.getPayload();
+ ICMP icmp = (ICMP) ipPacket.getPayload();
+ log.trace("Processing ICMP packet source MAC:{}, source IP:{}," +
+ "dest MAC:{}, dest IP:{}",
+ ethernet.getSourceMAC(),
+ IpAddress.valueOf(ipPacket.getSourceAddress()),
+ ethernet.getDestinationMAC(),
+ IpAddress.valueOf(ipPacket.getDestinationAddress()));
+
+ switch (icmp.getIcmpType()) {
+ case ICMP.TYPE_ECHO_REQUEST:
+ handleEchoRequest(
+ context.inPacket().receivedFrom().deviceId(),
+ ethernet.getSourceMAC(),
+ ipPacket,
+ icmp);
+ context.block();
+ break;
+ case ICMP.TYPE_ECHO_REPLY:
+ handleEchoReply(ipPacket, icmp);
+ context.block();
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void handleEchoRequest(DeviceId srcDevice, MacAddress srcMac, IPv4 ipPacket,
+ ICMP icmp) {
+ InstancePort instPort = instancePortService.instancePort(srcMac);
+ if (instPort == null) {
+ log.trace(ERR_REQ + "unknown source host(MAC:{})", srcMac);
+ return;
+ }
+
+ IpAddress srcIp = IpAddress.valueOf(ipPacket.getSourceAddress());
+ Subnet srcSubnet = getSourceSubnet(instPort, srcIp);
+ if (srcSubnet == null) {
+ log.trace(ERR_REQ + "unknown source subnet(IP:{})", srcIp);
+ return;
+ }
+ if (Strings.isNullOrEmpty(srcSubnet.getGateway())) {
+ log.trace(ERR_REQ + "source subnet(ID:{}, CIDR:{}) has no gateway",
+ srcSubnet.getId(), srcSubnet.getCidr());
+ return;
+ }
+
+ MacAddress externalPeerRouterMac = externalPeerRouterMac(srcSubnet);
+ if (externalPeerRouterMac == null) {
+ log.trace(ERR_REQ + "failed to get external peer router mac");
+ return;
+ }
+
+ if (isForSubnetGateway(IpAddress.valueOf(ipPacket.getDestinationAddress()),
+ srcSubnet)) {
+ // this is a request for the subnet gateway
+ processRequestForGateway(ipPacket, instPort);
+ } else {
+ // this is a request for the external network
+ IpAddress externalIp = getExternalIp(srcSubnet);
+ if (externalIp == null) {
+ return;
+ }
+
+ sendRequestForExternal(ipPacket, srcDevice, externalIp, externalPeerRouterMac(srcSubnet));
+ String icmpInfoKey = String.valueOf(getIcmpId(icmp))
+ .concat(String.valueOf(externalIp.getIp4Address().toInt()))
+ .concat(String.valueOf(ipPacket.getDestinationAddress()));
+ try {
+ icmpInfoMap.compute(icmpInfoKey, (id, existing) -> {
+ checkArgument(existing == null, ERR_DUPLICATE);
+ return instPort;
+ });
+ } catch (IllegalArgumentException e) {
+ log.warn("Exception occurred because of {}", e.toString());
+ }
+
+ }
+ }
+
+ private MacAddress externalPeerRouterMac(Subnet subnet) {
+ RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
+ .filter(i -> Objects.equals(i.getSubnetId(), subnet.getId()))
+ .findAny().orElse(null);
+ if (osRouterIface == null) {
+ return null;
+ }
+
+ Router osRouter = osRouterService.router(osRouterIface.getId());
+ if (osRouter == null) {
+ return null;
+ }
+ if (osRouter.getExternalGatewayInfo() == null) {
+ return null;
+ }
+
+ ExternalGateway exGatewayInfo = osRouter.getExternalGatewayInfo();
+
+ return osNetworkService.externalPeerRouterMac(exGatewayInfo);
+ }
+
+ private void handleEchoReply(IPv4 ipPacket, ICMP icmp) {
+ String icmpInfoKey = String.valueOf(getIcmpId(icmp))
+ .concat(String.valueOf(ipPacket.getDestinationAddress()))
+ .concat(String.valueOf(ipPacket.getSourceAddress()));
+
+ if (icmpInfoMap.get(icmpInfoKey) != null) {
+ processReplyFromExternal(ipPacket, icmpInfoMap.get(icmpInfoKey).value());
+ icmpInfoMap.remove(icmpInfoKey);
+ } else {
+ log.warn("No ICMP Info for ICMP packet");
+ }
+ }
+
+ private Subnet getSourceSubnet(InstancePort instance, IpAddress srcIp) {
+ Port osPort = osNetworkService.port(instance.portId());
+ IP fixedIp = osPort.getFixedIps().stream()
+ .filter(ip -> IpAddress.valueOf(ip.getIpAddress()).equals(srcIp))
+ .findAny().orElse(null);
+ if (fixedIp == null) {
+ return null;
+ }
+ return osNetworkService.subnet(fixedIp.getSubnetId());
+ }
+
+ private boolean isForSubnetGateway(IpAddress dstIp, Subnet srcSubnet) {
+ RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
+ .filter(i -> Objects.equals(i.getSubnetId(), srcSubnet.getId()))
+ .findAny().orElse(null);
+ if (osRouterIface == null) {
+ log.trace(ERR_REQ + "source subnet(ID:{}, CIDR:{}) has no router",
+ srcSubnet.getId(), srcSubnet.getCidr());
+ return false;
+ }
+
+ Router osRouter = osRouterService.router(osRouterIface.getId());
+ Set<IpAddress> routableGateways = osRouterService.routerInterfaces(osRouter.getId())
+ .stream()
+ .map(iface -> osNetworkService.subnet(iface.getSubnetId()).getGateway())
+ .map(IpAddress::valueOf)
+ .collect(Collectors.toSet());
+
+ return routableGateways.contains(dstIp);
+ }
+
+ private IpAddress getExternalIp(Subnet srcSubnet) {
+ RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
+ .filter(i -> Objects.equals(i.getSubnetId(), srcSubnet.getId()))
+ .findAny().orElse(null);
+ if (osRouterIface == null) {
+ final String error = String.format(ERR_REQ +
+ "subnet(ID:%s, CIDR:%s) is not connected to any router",
+ srcSubnet.getId(), srcSubnet.getCidr());
+ throw new IllegalStateException(error);
+ }
+
+ Router osRouter = osRouterService.router(osRouterIface.getId());
+ if (osRouter.getExternalGatewayInfo() == null) {
+ final String error = String.format(ERR_REQ +
+ "router(ID:%s, name:%s) does not have external gateway",
+ osRouter.getId(), osRouter.getName());
+ throw new IllegalStateException(error);
+ }
+
+ // TODO fix openstack4j for ExternalGateway provides external fixed IP list
+ ExternalGateway exGatewayInfo = osRouter.getExternalGatewayInfo();
+ Port exGatewayPort = osNetworkService.ports(exGatewayInfo.getNetworkId())
+ .stream()
+ .filter(port -> Objects.equals(port.getDeviceId(), osRouter.getId()))
+ .findAny().orElse(null);
+ if (exGatewayPort == null) {
+ final String error = String.format(ERR_REQ +
+ "no external gateway port for router (ID:%s, name:%s)",
+ osRouter.getId(), osRouter.getName());
+ throw new IllegalStateException(error);
+ }
+ Optional<NeutronIP> externalIpAddress = (Optional<NeutronIP>) exGatewayPort.getFixedIps().stream().findFirst();
+ if (!externalIpAddress.isPresent() || externalIpAddress.get().getIpAddress() == null) {
+ final String error = String.format(ERR_REQ +
+ "no external gateway IP address for router (ID:%s, name:%s)",
+ osRouter.getId(), osRouter.getName());
+ throw new IllegalStateException(error);
+ }
+
+ return IpAddress.valueOf(externalIpAddress.get().getIpAddress());
+ }
+
+ private void processRequestForGateway(IPv4 ipPacket, InstancePort instPort) {
+ ICMP icmpReq = (ICMP) ipPacket.getPayload();
+ icmpReq.setChecksum((short) 0);
+ icmpReq.setIcmpType(ICMP.TYPE_ECHO_REPLY).resetChecksum();
+
+ int destinationAddress = ipPacket.getSourceAddress();
+
+ ipPacket.setSourceAddress(ipPacket.getDestinationAddress())
+ .setDestinationAddress(destinationAddress)
+ .resetChecksum();
+
+ ipPacket.setPayload(icmpReq);
+ Ethernet icmpReply = new Ethernet();
+ icmpReply.setEtherType(Ethernet.TYPE_IPV4)
+ .setSourceMACAddress(Constants.DEFAULT_GATEWAY_MAC)
+ .setDestinationMACAddress(instPort.macAddress())
+ .setPayload(ipPacket);
+
+ sendReply(icmpReply, instPort);
+ }
+
+ private void sendRequestForExternal(IPv4 ipPacket, DeviceId srcDevice,
+ IpAddress srcNatIp, MacAddress externalRouterMac) {
+ ICMP icmpReq = (ICMP) ipPacket.getPayload();
+ icmpReq.resetChecksum();
+ ipPacket.setSourceAddress(srcNatIp.getIp4Address().toInt()).resetChecksum();
+ ipPacket.setPayload(icmpReq);
+
+ Ethernet icmpRequestEth = new Ethernet();
+ icmpRequestEth.setEtherType(Ethernet.TYPE_IPV4)
+ .setSourceMACAddress(DEFAULT_GATEWAY_MAC)
+ .setDestinationMACAddress(externalRouterMac)
+ .setPayload(ipPacket);
+
+ OpenstackNode osNode = osNodeService.node(srcDevice);
+ if (osNode == null) {
+ final String error = String.format("Cannot find openstack node for %s",
+ srcDevice);
+ throw new IllegalStateException(error);
+ }
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(osNode.uplinkPortNum())
+ .build();
+
+ OutboundPacket packet = new DefaultOutboundPacket(
+ srcDevice,
+ treatment,
+ ByteBuffer.wrap(icmpRequestEth.serialize()));
+
+ packetService.emit(packet);
+ }
+
+ private void processReplyFromExternal(IPv4 ipPacket, InstancePort instPort) {
+ ICMP icmpReply = (ICMP) ipPacket.getPayload();
+ icmpReply.resetChecksum();
+
+ ipPacket.setDestinationAddress(instPort.ipAddress().getIp4Address().toInt())
+ .resetChecksum();
+ ipPacket.setPayload(icmpReply);
+
+ Ethernet icmpResponseEth = new Ethernet();
+ icmpResponseEth.setEtherType(Ethernet.TYPE_IPV4)
+ .setSourceMACAddress(Constants.DEFAULT_GATEWAY_MAC)
+ .setDestinationMACAddress(instPort.macAddress())
+ .setPayload(ipPacket);
+
+ sendReply(icmpResponseEth, instPort);
+ }
+
+ private void sendReply(Ethernet icmpReply, InstancePort instPort) {
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(instPort.portNumber())
+ .build();
+
+ OutboundPacket packet = new DefaultOutboundPacket(
+ instPort.deviceId(),
+ treatment,
+ ByteBuffer.wrap(icmpReply.serialize()));
+
+ packetService.emit(packet);
+ }
+
+ private short getIcmpId(ICMP icmp) {
+ return ByteBuffer.wrap(icmp.serialize(), 4, 2).getShort();
+ }
+
+ private class InternalPacketProcessor implements PacketProcessor {
+
+ @Override
+ public void process(PacketContext context) {
+ Set<DeviceId> gateways = osNodeService.completeNodes(GATEWAY)
+ .stream().map(OpenstackNode::intgBridge)
+ .collect(Collectors.toSet());
+
+ if (context.isHandled()) {
+ return;
+ } else if (!gateways.contains(context.inPacket().receivedFrom().deviceId())) {
+ // return if the packet is not from gateway nodes
+ return;
+ }
+
+ InboundPacket pkt = context.inPacket();
+ Ethernet ethernet = pkt.parsed();
+ if (ethernet == null || ethernet.getEtherType() == Ethernet.TYPE_ARP) {
+ return;
+ }
+
+ IPv4 iPacket = (IPv4) ethernet.getPayload();
+ if (iPacket.getProtocol() == IPv4.PROTOCOL_ICMP) {
+ eventExecutor.execute(() -> processIcmpPacket(context, ethernet));
+ }
+ }
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java
new file mode 100644
index 0000000..5ea8442
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingSnatHandler.java
@@ -0,0 +1,564 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.TCP;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.UDP;
+import org.onlab.packet.VlanId;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.OpenstackRouterService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.DistributedSet;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.openstack4j.model.network.ExternalGateway;
+import org.openstack4j.model.network.IP;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.NetworkType;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.Router;
+import org.openstack4j.model.network.RouterInterface;
+import org.openstack4j.model.network.Subnet;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC;
+import static org.onosproject.openstacknetworking.api.Constants.GW_COMMON_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_SNAT_RULE;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Handle packets needs SNAT.
+ */
+@Component(immediate = true)
+public class OpenstackRoutingSnatHandler {
+
+ private final Logger log = getLogger(getClass());
+
+ private static final String ERR_PACKETIN = "Failed to handle packet in: ";
+ private static final String ERR_UNSUPPORTED_NET_TYPE = "Unsupported network type";
+ private static final long TIME_OUT_SNAT_PORT_MS = 120L * 1000L;
+ private static final int TP_PORT_MINIMUM_NUM = 65000;
+ private static final int TP_PORT_MAXIMUM_NUM = 65535;
+
+ private static final KryoNamespace.Builder NUMBER_SERIALIZER = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API);
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected PacketService packetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected InstancePortService instancePortService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackNodeService osNodeService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackNetworkService osNetworkService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackRouterService osRouterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackFlowRuleService osFlowRuleService;
+
+ private final ExecutorService eventExecutor = newSingleThreadExecutor(
+ groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
+ private final PacketProcessor packetProcessor = new InternalPacketProcessor();
+
+ private ConsistentMap<Integer, Long> allocatedPortNumMap;
+ private DistributedSet<Integer> unUsedPortNumSet;
+ private ApplicationId appId;
+
+ @Activate
+ protected void activate() {
+ appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+
+ allocatedPortNumMap = storageService.<Integer, Long>consistentMapBuilder()
+ .withSerializer(Serializer.using(NUMBER_SERIALIZER.build()))
+ .withName("openstackrouting-allocatedportnummap")
+ .withApplicationId(appId)
+ .build();
+
+ unUsedPortNumSet = storageService.<Integer>setBuilder()
+ .withName("openstackrouting-unusedportnumset")
+ .withSerializer(Serializer.using(KryoNamespaces.API))
+ .build()
+ .asDistributedSet();
+
+ initializeUnusedPortNumSet();
+
+ packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
+ log.info("Started");
+ }
+
+ private void initializeUnusedPortNumSet() {
+ for (int i = TP_PORT_MINIMUM_NUM; i < TP_PORT_MAXIMUM_NUM; i++) {
+ if (!allocatedPortNumMap.containsKey(i)) {
+ unUsedPortNumSet.add(i);
+ }
+ }
+
+ clearPortNumMap();
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ packetService.removeProcessor(packetProcessor);
+ eventExecutor.shutdown();
+ log.info("Stopped");
+ }
+
+ private void processSnatPacket(PacketContext context, Ethernet eth) {
+ IPv4 iPacket = (IPv4) eth.getPayload();
+ InboundPacket packetIn = context.inPacket();
+
+ int patPort = getPortNum();
+
+ InstancePort srcInstPort = instancePortService.instancePort(eth.getSourceMAC());
+ if (srcInstPort == null) {
+ log.error(ERR_PACKETIN + "source host(MAC:{}) does not exist",
+ eth.getSourceMAC());
+ return;
+ }
+
+ IpAddress srcIp = IpAddress.valueOf(iPacket.getSourceAddress());
+ Subnet srcSubnet = getSourceSubnet(srcInstPort, srcIp);
+ IpAddress externalGatewayIp = getExternalIp(srcSubnet);
+
+ if (externalGatewayIp == null) {
+ return;
+ }
+
+ MacAddress externalPeerRouterMac = externalPeerRouterMac(srcSubnet);
+ if (externalPeerRouterMac == null) {
+ return;
+ }
+
+ populateSnatFlowRules(context.inPacket(),
+ srcInstPort,
+ TpPort.tpPort(patPort),
+ externalGatewayIp, externalPeerRouterMac);
+
+ packetOut(eth.duplicate(),
+ packetIn.receivedFrom().deviceId(),
+ patPort,
+ externalGatewayIp, externalPeerRouterMac);
+ }
+
+ private MacAddress externalPeerRouterMac(Subnet subnet) {
+ RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
+ .filter(i -> Objects.equals(i.getSubnetId(), subnet.getId()))
+ .findAny().orElse(null);
+ if (osRouterIface == null) {
+ return null;
+ }
+
+ Router osRouter = osRouterService.router(osRouterIface.getId());
+ if (osRouter == null) {
+ return null;
+ }
+ if (osRouter.getExternalGatewayInfo() == null) {
+ return null;
+ }
+
+ ExternalGateway exGatewayInfo = osRouter.getExternalGatewayInfo();
+
+ return osNetworkService.externalPeerRouterMac(exGatewayInfo);
+ }
+
+ private Subnet getSourceSubnet(InstancePort instance, IpAddress srcIp) {
+ Port osPort = osNetworkService.port(instance.portId());
+ IP fixedIp = osPort.getFixedIps().stream()
+ .filter(ip -> IpAddress.valueOf(ip.getIpAddress()).equals(srcIp))
+ .findAny().orElse(null);
+ if (fixedIp == null) {
+ return null;
+ }
+ return osNetworkService.subnet(fixedIp.getSubnetId());
+ }
+
+ private IpAddress getExternalIp(Subnet srcSubnet) {
+ RouterInterface osRouterIface = osRouterService.routerInterfaces().stream()
+ .filter(i -> Objects.equals(i.getSubnetId(), srcSubnet.getId()))
+ .findAny().orElse(null);
+ if (osRouterIface == null) {
+ // this subnet is not connected to the router
+ log.trace(ERR_PACKETIN + "source subnet(ID:{}, CIDR:{}) has no router",
+ srcSubnet.getId(), srcSubnet.getCidr());
+ return null;
+ }
+
+ Router osRouter = osRouterService.router(osRouterIface.getId());
+ if (osRouter.getExternalGatewayInfo() == null) {
+ // this router does not have external connectivity
+ log.trace(ERR_PACKETIN + "router({}) has no external gateway",
+ osRouter.getName());
+ return null;
+ }
+
+ ExternalGateway exGatewayInfo = osRouter.getExternalGatewayInfo();
+ if (!exGatewayInfo.isEnableSnat()) {
+ // SNAT is disabled in this router
+ log.trace(ERR_PACKETIN + "router({}) SNAT is disabled", osRouter.getName());
+ return null;
+ }
+
+ // TODO fix openstack4j for ExternalGateway provides external fixed IP list
+ Port exGatewayPort = osNetworkService.ports(exGatewayInfo.getNetworkId())
+ .stream()
+ .filter(port -> Objects.equals(port.getDeviceId(), osRouter.getId()))
+ .findAny().orElse(null);
+ if (exGatewayPort == null) {
+ log.trace(ERR_PACKETIN + "no external gateway port for router({})",
+ osRouter.getName());
+ return null;
+ }
+
+ return IpAddress.valueOf(exGatewayPort.getFixedIps().stream()
+ .findFirst().get().getIpAddress());
+ }
+
+ private void populateSnatFlowRules(InboundPacket packetIn, InstancePort srcInstPort,
+ TpPort patPort, IpAddress externalIp, MacAddress externalPeerRouterMac) {
+ Network osNet = osNetworkService.network(srcInstPort.networkId());
+ if (osNet == null) {
+ final String error = String.format(ERR_PACKETIN + "network %s not found",
+ srcInstPort.networkId());
+ throw new IllegalStateException(error);
+ }
+
+ setDownstreamRules(srcInstPort,
+ osNet.getProviderSegID(),
+ osNet.getNetworkType(),
+ externalIp,
+ patPort,
+ packetIn);
+
+ setUpstreamRules(osNet.getProviderSegID(),
+ osNet.getNetworkType(),
+ externalIp,
+ externalPeerRouterMac,
+ patPort,
+ packetIn);
+ }
+
+ private void setDownstreamRules(InstancePort srcInstPort, String segmentId,
+ NetworkType networkType,
+ IpAddress externalIp,
+ TpPort patPort,
+ InboundPacket packetIn) {
+ IPv4 iPacket = (IPv4) packetIn.parsed().getPayload();
+ IpAddress internalIp = IpAddress.valueOf(iPacket.getSourceAddress());
+
+ TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPProtocol(iPacket.getProtocol())
+ .matchIPDst(IpPrefix.valueOf(externalIp.getIp4Address(), 32))
+ .matchIPSrc(IpPrefix.valueOf(iPacket.getDestinationAddress(), 32));
+
+ TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder()
+ .setEthDst(packetIn.parsed().getSourceMAC())
+ .setIpDst(internalIp);
+
+ switch (networkType) {
+ case VXLAN:
+ tBuilder.setTunnelId(Long.parseLong(segmentId));
+ break;
+ case VLAN:
+ tBuilder.pushVlan()
+ .setVlanId(VlanId.vlanId(segmentId))
+ .setEthSrc(DEFAULT_GATEWAY_MAC);
+ break;
+ default:
+ final String error = String.format(
+ ERR_UNSUPPORTED_NET_TYPE + "%s",
+ networkType.toString());
+ throw new IllegalStateException(error);
+ }
+
+
+ switch (iPacket.getProtocol()) {
+ case IPv4.PROTOCOL_TCP:
+ TCP tcpPacket = (TCP) iPacket.getPayload();
+ sBuilder.matchTcpSrc(TpPort.tpPort(tcpPacket.getDestinationPort()))
+ .matchTcpDst(patPort);
+ tBuilder.setTcpDst(TpPort.tpPort(tcpPacket.getSourcePort()));
+ break;
+ case IPv4.PROTOCOL_UDP:
+ UDP udpPacket = (UDP) iPacket.getPayload();
+ sBuilder.matchUdpSrc(TpPort.tpPort(udpPacket.getDestinationPort()))
+ .matchUdpDst(patPort);
+ tBuilder.setUdpDst(TpPort.tpPort(udpPacket.getSourcePort()));
+ break;
+ default:
+ break;
+ }
+
+ OpenstackNode srcNode = osNodeService.node(srcInstPort.deviceId());
+ osNodeService.completeNodes(GATEWAY).forEach(gNode -> {
+ TrafficTreatment.Builder tmpBuilder =
+ DefaultTrafficTreatment.builder(tBuilder.build());
+ switch (networkType) {
+ case VXLAN:
+ tmpBuilder.extension(RulePopulatorUtil.buildExtension(
+ deviceService,
+ gNode.intgBridge(),
+ srcNode.dataIp().getIp4Address()), gNode.intgBridge())
+ .setOutput(gNode.tunnelPortNum());
+ break;
+ case VLAN:
+ tmpBuilder.setOutput(gNode.vlanPortNum());
+ break;
+ default:
+ final String error = String.format(ERR_UNSUPPORTED_NET_TYPE + "%s",
+ networkType.toString());
+ throw new IllegalStateException(error);
+ }
+
+ osFlowRuleService.setRule(
+ appId,
+ gNode.intgBridge(),
+ sBuilder.build(),
+ tmpBuilder.build(),
+ PRIORITY_SNAT_RULE,
+ GW_COMMON_TABLE,
+ true);
+ });
+ }
+
+ private void setUpstreamRules(String segmentId, NetworkType networkType,
+ IpAddress externalIp, MacAddress externalPeerRouterMac,
+ TpPort patPort,
+ InboundPacket packetIn) {
+ IPv4 iPacket = (IPv4) packetIn.parsed().getPayload();
+
+ TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPProtocol(iPacket.getProtocol())
+ .matchIPSrc(IpPrefix.valueOf(iPacket.getSourceAddress(), 32))
+ .matchIPDst(IpPrefix.valueOf(iPacket.getDestinationAddress(), 32));
+
+ TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+ switch (networkType) {
+ case VXLAN:
+ sBuilder.matchTunnelId(Long.parseLong(segmentId));
+ break;
+ case VLAN:
+ sBuilder.matchVlanId(VlanId.vlanId(segmentId));
+ tBuilder.popVlan();
+ break;
+ default:
+ final String error = String.format(ERR_UNSUPPORTED_NET_TYPE + "%s",
+ networkType.toString());
+ throw new IllegalStateException(error);
+ }
+
+ switch (iPacket.getProtocol()) {
+ case IPv4.PROTOCOL_TCP:
+ TCP tcpPacket = (TCP) iPacket.getPayload();
+ sBuilder.matchTcpSrc(TpPort.tpPort(tcpPacket.getSourcePort()))
+ .matchTcpDst(TpPort.tpPort(tcpPacket.getDestinationPort()));
+ tBuilder.setTcpSrc(patPort)
+ .setEthDst(externalPeerRouterMac);
+ break;
+ case IPv4.PROTOCOL_UDP:
+ UDP udpPacket = (UDP) iPacket.getPayload();
+ sBuilder.matchUdpSrc(TpPort.tpPort(udpPacket.getSourcePort()))
+ .matchUdpDst(TpPort.tpPort(udpPacket.getDestinationPort()));
+ tBuilder.setUdpSrc(patPort)
+ .setEthDst(externalPeerRouterMac);
+ break;
+ default:
+ log.debug("Unsupported IPv4 protocol {}");
+ break;
+ }
+
+ tBuilder.setIpSrc(externalIp);
+ osNodeService.completeNodes(GATEWAY).forEach(gNode -> {
+ TrafficTreatment.Builder tmpBuilder =
+ DefaultTrafficTreatment.builder(tBuilder.build());
+ tmpBuilder.setOutput(gNode.uplinkPortNum());
+
+ osFlowRuleService.setRule(
+ appId,
+ gNode.intgBridge(),
+ sBuilder.build(),
+ tmpBuilder.build(),
+ PRIORITY_SNAT_RULE,
+ GW_COMMON_TABLE,
+ true);
+ });
+ }
+
+ private void packetOut(Ethernet ethPacketIn, DeviceId srcDevice, int patPort,
+ IpAddress externalIp, MacAddress externalPeerRouterMac) {
+ IPv4 iPacket = (IPv4) ethPacketIn.getPayload();
+ switch (iPacket.getProtocol()) {
+ case IPv4.PROTOCOL_TCP:
+ TCP tcpPacket = (TCP) iPacket.getPayload();
+ tcpPacket.setSourcePort(patPort);
+ tcpPacket.resetChecksum();
+ tcpPacket.setParent(iPacket);
+ iPacket.setPayload(tcpPacket);
+ break;
+ case IPv4.PROTOCOL_UDP:
+ UDP udpPacket = (UDP) iPacket.getPayload();
+ udpPacket.setSourcePort(patPort);
+ udpPacket.resetChecksum();
+ udpPacket.setParent(iPacket);
+ iPacket.setPayload(udpPacket);
+ break;
+ default:
+ log.trace("Temporally, this method can process UDP and TCP protocol.");
+ return;
+ }
+
+ iPacket.setSourceAddress(externalIp.toString());
+ iPacket.resetChecksum();
+ iPacket.setParent(ethPacketIn);
+ ethPacketIn.setDestinationMACAddress(externalPeerRouterMac);
+ ethPacketIn.setPayload(iPacket);
+ ethPacketIn.resetChecksum();
+
+ OpenstackNode srcNode = osNodeService.node(srcDevice);
+ if (srcNode == null) {
+ final String error = String.format("Cannot find openstack node for %s",
+ srcDevice);
+ throw new IllegalStateException(error);
+ }
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(srcNode.uplinkPortNum()).build();
+ packetService.emit(new DefaultOutboundPacket(
+ srcDevice,
+ treatment,
+ ByteBuffer.wrap(ethPacketIn.serialize())));
+ }
+
+ private int getPortNum() {
+ if (unUsedPortNumSet.isEmpty()) {
+ clearPortNumMap();
+ }
+ int portNum = findUnusedPortNum();
+ if (portNum != 0) {
+ unUsedPortNumSet.remove(portNum);
+ allocatedPortNumMap.put(portNum, System.currentTimeMillis());
+ }
+ return portNum;
+ }
+
+ private int findUnusedPortNum() {
+ return unUsedPortNumSet.stream().findAny().orElse(0);
+ }
+
+ private void clearPortNumMap() {
+ allocatedPortNumMap.entrySet().forEach(e -> {
+ if (System.currentTimeMillis() - e.getValue().value() > TIME_OUT_SNAT_PORT_MS) {
+ allocatedPortNumMap.remove(e.getKey());
+ unUsedPortNumSet.add(e.getKey());
+ }
+ });
+ }
+
+ private class InternalPacketProcessor implements PacketProcessor {
+
+ @Override
+ public void process(PacketContext context) {
+ Set<DeviceId> gateways = osNodeService.completeNodes(OpenstackNode.NodeType.GATEWAY)
+ .stream().map(OpenstackNode::intgBridge)
+ .collect(Collectors.toSet());
+ if (context.isHandled()) {
+ return;
+ } else if (!gateways.contains(context.inPacket().receivedFrom().deviceId())) {
+ // return if the packet is not from gateway nodes
+ return;
+ }
+
+ InboundPacket pkt = context.inPacket();
+ Ethernet eth = pkt.parsed();
+ if (eth == null || eth.getEtherType() == Ethernet.TYPE_ARP) {
+ return;
+ }
+
+ IPv4 iPacket = (IPv4) eth.getPayload();
+ switch (iPacket.getProtocol()) {
+ case IPv4.PROTOCOL_ICMP:
+ break;
+ case IPv4.PROTOCOL_UDP:
+ UDP udpPacket = (UDP) iPacket.getPayload();
+ if (udpPacket.getDestinationPort() == UDP.DHCP_SERVER_PORT &&
+ udpPacket.getSourcePort() == UDP.DHCP_CLIENT_PORT) {
+ // don't process DHCP
+ break;
+ }
+ default:
+ eventExecutor.execute(() -> processSnatPacket(context, eth));
+ break;
+ }
+ }
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupHandler.java
new file mode 100644
index 0000000..23beaf9
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupHandler.java
@@ -0,0 +1,794 @@
+/*
+* Copyright 2017-present Open Networking Foundation
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package org.onosproject.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.TpPort;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.driver.DriverService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.ExtensionSelector;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortEvent;
+import org.onosproject.openstacknetworking.api.InstancePortListener;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkEvent;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkListener;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupEvent;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupListener;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeEvent;
+import org.onosproject.openstacknode.api.OpenstackNodeListener;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.SecurityGroup;
+import org.openstack4j.model.network.SecurityGroupRule;
+import org.openstack4j.openstack.networking.domain.NeutronSecurityGroupRule;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.ACL_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.JUMP_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.CT_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.ERROR_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ACL_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_CT_DROP_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_CT_HOOK_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_CT_RULE;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Populates flow rules to handle OpenStack SecurityGroups.
+ */
+@Component(immediate = true)
+public class OpenstackSecurityGroupHandler {
+
+ private final Logger log = getLogger(getClass());
+
+ private static final boolean USE_SECURITY_GROUP = false;
+
+ @Property(name = "useSecurityGroup", boolValue = USE_SECURITY_GROUP,
+ label = "Apply OpenStack security group rule for VM traffic")
+ private boolean useSecurityGroup = USE_SECURITY_GROUP;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected InstancePortService instancePortService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipService mastershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackNetworkService osNetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackSecurityGroupService securityGroupService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackFlowRuleService osFlowRuleService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ComponentConfigService configService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackNodeService osNodeService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DriverService driverService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected LeadershipService leadershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ private final InstancePortListener instancePortListener = new InternalInstancePortListener();
+ private final OpenstackNetworkListener portListener = new InternalOpenstackPortListener();
+ private final OpenstackSecurityGroupListener securityGroupListener = new InternalSecurityGroupListener();
+ private final OpenstackNodeListener osNodeListener = new InternalNodeListener();
+ private ApplicationId appId;
+ private NodeId localNodeId;
+
+ private final ExecutorService eventExecutor = newSingleThreadExecutor(
+ groupedThreads(this.getClass().getSimpleName(), "event-handler"));
+
+ private static final String PROTO_ICMP = "ICMP";
+ private static final String PROTO_TCP = "TCP";
+ private static final String PROTO_UDP = "UDP";
+ private static final String ETHTYPE_IPV4 = "IPV4";
+ private static final String EGRESS = "EGRESS";
+ private static final String INGRESS = "INGRESS";
+ private static final IpPrefix IP_PREFIX_ANY = Ip4Prefix.valueOf("0.0.0.0/0");
+
+ // We expose pipeline structure to SONA application considering removing pipeline soon.
+ private static final int GOTO_CONNTRACK_TABLE = 2;
+ private static final int GOTO_JUMP_TABLE = 3;
+
+ private static final int CT_COMMIT = 0;
+ private static final int CT_NO_COMMIT = 1;
+ private static final short CT_NO_RECIRC = -1;
+
+ private static final int ACTION_NONE = 0;
+ private static final int ACTION_DROP = -1;
+
+ @Activate
+ protected void activate() {
+ appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+ localNodeId = clusterService.getLocalNode().id();
+ instancePortService.addListener(instancePortListener);
+ securityGroupService.addListener(securityGroupListener);
+ osNetService.addListener(portListener);
+ configService.registerProperties(getClass());
+ osNodeService.addListener(osNodeListener);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ instancePortService.removeListener(instancePortListener);
+ securityGroupService.removeListener(securityGroupListener);
+ osNetService.removeListener(portListener);
+ configService.unregisterProperties(getClass(), false);
+ osNodeService.removeListener(osNodeListener);
+ eventExecutor.shutdown();
+
+ log.info("Stopped");
+ }
+
+ @Modified
+ protected void modified(ComponentContext context) {
+ Dictionary<?, ?> properties = context.getProperties();
+ Boolean flag;
+
+ flag = Tools.isPropertyEnabled(properties, "useSecurityGroup");
+ if (flag == null) {
+ log.info("useSecurityGroup is not configured, " +
+ "using current value of {}", useSecurityGroup);
+ } else {
+ useSecurityGroup = flag;
+ log.info("Configured. useSecurityGroup is {}",
+ useSecurityGroup ? "enabled" : "disabled");
+ }
+
+ securityGroupService.setSecurityGroupEnabled(useSecurityGroup);
+ resetSecurityGroupRules();
+ }
+
+ private void initializeConnTrackTable(DeviceId deviceId, boolean install) {
+
+ //table=1,ip,ct_state=-trk, actions=ct(table:2)
+ long ctState = RulePopulatorUtil.computeCtStateFlag(false, false, false);
+ long ctMask = RulePopulatorUtil.computeCtMaskFlag(true, false, false);
+ setConnTrackRule(deviceId, ctState, ctMask, CT_NO_COMMIT, (short) GOTO_CONNTRACK_TABLE,
+ ACTION_NONE, PRIORITY_CT_HOOK_RULE, install);
+
+ //table=2,ip,nw_dst=10.10.0.2,ct_state=+trk+est,action=goto_table:3
+ ctState = RulePopulatorUtil.computeCtStateFlag(true, false, true);
+ ctMask = RulePopulatorUtil.computeCtMaskFlag(true, false, true);
+ setConnTrackRule(deviceId, ctState, ctMask, CT_NO_COMMIT, CT_NO_RECIRC,
+ GOTO_JUMP_TABLE, PRIORITY_CT_RULE, install);
+
+ //table=2,ip,nw_dst=10.10.0.2,ct_state=+trk+new,action=drop
+ ctState = RulePopulatorUtil.computeCtStateFlag(true, true, false);
+ ctMask = RulePopulatorUtil.computeCtMaskFlag(true, true, false);
+ setConnTrackRule(deviceId, ctState, ctMask, CT_NO_COMMIT, CT_NO_RECIRC,
+ ACTION_DROP, PRIORITY_CT_DROP_RULE, install);
+ }
+
+ private void setSecurityGroupRules(InstancePort instPort, Port port, boolean install) {
+ port.getSecurityGroups().forEach(sgId -> {
+ SecurityGroup sg = securityGroupService.securityGroup(sgId);
+ if (sg == null) {
+ log.error("Security Group Not Found : {}", sgId);
+ return;
+ }
+ sg.getRules().forEach(sgRule -> updateSecurityGroupRule(instPort, port, sgRule, install));
+ final String action = install ? "Installed " : "Removed ";
+ log.debug(action + "security group rule ID : " + sgId);
+ });
+ }
+
+ private void updateSecurityGroupRule(InstancePort instPort, Port port, SecurityGroupRule sgRule, boolean install) {
+
+ if (sgRule.getRemoteGroupId() != null && !sgRule.getRemoteGroupId().isEmpty()) {
+ getRemoteInstPorts(port.getTenantId(), sgRule.getRemoteGroupId())
+ .forEach(rInstPort -> {
+ populateSecurityGroupRule(sgRule, instPort, rInstPort.ipAddress().toIpPrefix(), install);
+ populateSecurityGroupRule(sgRule, rInstPort, instPort.ipAddress().toIpPrefix(), install);
+
+ SecurityGroupRule rSgRule = new NeutronSecurityGroupRule.SecurityGroupRuleConcreteBuilder()
+ .from(sgRule)
+ .direction(sgRule.getDirection().toUpperCase().equals(EGRESS) ? INGRESS : EGRESS).build();
+ populateSecurityGroupRule(rSgRule, instPort, rInstPort.ipAddress().toIpPrefix(), install);
+ populateSecurityGroupRule(rSgRule, rInstPort, instPort.ipAddress().toIpPrefix(), install);
+ });
+ } else {
+ populateSecurityGroupRule(sgRule, instPort, sgRule.getRemoteIpPrefix() == null ? IP_PREFIX_ANY :
+ IpPrefix.valueOf(sgRule.getRemoteIpPrefix()), install);
+ }
+ }
+
+ private void populateSecurityGroupRule(SecurityGroupRule sgRule, InstancePort instPort,
+ IpPrefix remoteIp, boolean install) {
+ Set<TrafficSelector> selectors = buildSelectors(sgRule,
+ Ip4Address.valueOf(instPort.ipAddress().toInetAddress()), remoteIp);
+ if (selectors == null || selectors.isEmpty()) {
+ return;
+ }
+
+ selectors.forEach(selector -> {
+ osFlowRuleService.setRule(appId,
+ instPort.deviceId(),
+ selector,
+ DefaultTrafficTreatment.builder().transition(JUMP_TABLE).build(),
+ PRIORITY_ACL_RULE,
+ ACL_TABLE,
+ install);
+ });
+ }
+
+ /**
+ * Sets connection tracking rule using OVS extension commands.
+ * It is not so graceful, but I don't want to make it more general because it is going to be used
+ * only here. The following is the usage of the function.
+ *
+ * @param deviceId Device ID
+ * @param ctState ctState: please use RulePopulatorUtil.computeCtStateFlag() to build the value
+ * @param ctMask crMask: please use RulePopulatorUtil.computeCtMaskFlag() to build the value
+ * @param commit CT_COMMIT for commit action, CT_NO_COMMIT otherwise
+ * @param recircTable table number for recirculation after CT actions. CT_NO_RECIRC with no recirculation
+ * @param action Additional actions. ACTION_DROP, ACTION_NONE, GOTO_XXX_TABLE are supported.
+ * @param priority priority value for the rule
+ * @param install true for insertion, false for removal
+ */
+ private void setConnTrackRule(DeviceId deviceId, long ctState, long ctMask,
+ int commit, short recircTable,
+ int action, int priority, boolean install) {
+
+ ExtensionSelector esCtSate = RulePopulatorUtil.buildCtExtensionSelector(driverService, deviceId,
+ ctState, ctMask);
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .extension(esCtSate, deviceId)
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .build();
+
+ TrafficTreatment.Builder tb = DefaultTrafficTreatment.builder();
+
+ if (commit == CT_COMMIT || recircTable > 0) {
+ RulePopulatorUtil.NiriraConnTrackTreatmentBuilder natTreatmentBuilder =
+ RulePopulatorUtil.niciraConnTrackTreatmentBuilder(driverService, deviceId);
+ natTreatmentBuilder.natAction(false);
+ if (commit == CT_COMMIT) {
+ natTreatmentBuilder.commit(true);
+ } else {
+ natTreatmentBuilder.commit(false);
+ }
+ if (recircTable > 0) {
+ natTreatmentBuilder.table(recircTable);
+ }
+ tb.extension(natTreatmentBuilder.build(), deviceId);
+ } else if (action == ACTION_DROP) {
+ tb.drop();
+ }
+
+ if (action != ACTION_NONE) {
+ tb.transition(action);
+ }
+
+ int tableType = ERROR_TABLE;
+ if (priority == PRIORITY_CT_RULE || priority == PRIORITY_CT_DROP_RULE) {
+ tableType = CT_TABLE;
+ } else if (priority == PRIORITY_CT_HOOK_RULE) {
+ tableType = ACL_TABLE;
+ } else {
+ log.error("Cannot an appropriate table for the conn track rule.");
+ }
+
+ osFlowRuleService.setRule(
+ appId,
+ deviceId,
+ selector,
+ tb.build(),
+ priority,
+ tableType,
+ install);
+ }
+
+ /**
+ * Returns a set of host IP addresses engaged with supplied security group ID.
+ * It only searches a VM in the same tenant boundary.
+ *
+ * @param tenantId tenant id
+ * @param sgId security group id
+ * @return set of ip addresses
+ */
+ private Set<InstancePort> getRemoteInstPorts(String tenantId, String sgId) {
+ Set<InstancePort> remoteInstPorts;
+
+ remoteInstPorts = osNetService.ports().stream()
+ .filter(port -> port.getTenantId().equals(tenantId))
+ .filter(port -> port.getSecurityGroups().contains(sgId))
+ .map(port -> instancePortService.instancePort(port.getId()))
+ .filter(instPort -> instPort != null && instPort.ipAddress() != null)
+ .collect(Collectors.toSet());
+
+ return Collections.unmodifiableSet(remoteInstPorts);
+ }
+
+ private Set<TrafficSelector> buildSelectors(SecurityGroupRule sgRule,
+ Ip4Address vmIp,
+ IpPrefix remoteIp) {
+ if (remoteIp != null && remoteIp.equals(IpPrefix.valueOf(vmIp, 32))) {
+ // do nothing if the remote IP is my IP
+ return null;
+ }
+
+ Set<TrafficSelector> selectorSet = Sets.newHashSet();
+
+ TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder();
+ buildMatchs(sBuilder, sgRule, vmIp, remoteIp);
+
+ if (sgRule.getPortRangeMax() != null && sgRule.getPortRangeMin() != null &&
+ sgRule.getPortRangeMin() < sgRule.getPortRangeMax()) {
+ Map<TpPort, TpPort> portRangeMatchMap = buildPortRangeMatches(sgRule.getPortRangeMin(),
+ sgRule.getPortRangeMax());
+ portRangeMatchMap.entrySet().forEach(entry -> {
+
+ if (sgRule.getProtocol().toUpperCase().equals(PROTO_TCP)) {
+ if (sgRule.getDirection().toUpperCase().equals(EGRESS)) {
+ sBuilder.matchTcpSrcMasked(entry.getKey(), entry.getValue());
+ } else {
+ sBuilder.matchTcpDstMasked(entry.getKey(), entry.getValue());
+ }
+ } else if (sgRule.getProtocol().toUpperCase().equals(PROTO_UDP)) {
+ if (sgRule.getDirection().toUpperCase().equals(EGRESS)) {
+ sBuilder.matchUdpSrcMasked(entry.getKey(), entry.getValue());
+ } else {
+ sBuilder.matchUdpDstMasked(entry.getKey(), entry.getValue());
+ }
+ }
+
+ selectorSet.add(sBuilder.build());
+ }
+ );
+ } else {
+ selectorSet.add(sBuilder.build());
+ }
+
+ return selectorSet;
+ }
+
+ private void buildMatchs(TrafficSelector.Builder sBuilder, SecurityGroupRule sgRule,
+ Ip4Address vmIp, IpPrefix remoteIp) {
+ buildMatchEthType(sBuilder, sgRule.getEtherType());
+ buildMatchDirection(sBuilder, sgRule.getDirection(), vmIp);
+ buildMatchProto(sBuilder, sgRule.getProtocol());
+ buildMatchPort(sBuilder, sgRule.getProtocol(), sgRule.getDirection(),
+ sgRule.getPortRangeMin() == null ? 0 : sgRule.getPortRangeMin(),
+ sgRule.getPortRangeMax() == null ? 0 : sgRule.getPortRangeMax());
+ buildMatchRemoteIp(sBuilder, remoteIp, sgRule.getDirection());
+ if (sgRule.getRemoteGroupId() != null && sgRule.getRemoteGroupId().isEmpty()) {
+ buildMatchRemoteIp(sBuilder, remoteIp, sgRule.getDirection());
+ }
+ }
+
+ private void buildMatchDirection(TrafficSelector.Builder sBuilder,
+ String direction,
+ Ip4Address vmIp) {
+ if (direction.toUpperCase().equals(EGRESS)) {
+ sBuilder.matchIPSrc(IpPrefix.valueOf(vmIp, 32));
+ } else {
+ sBuilder.matchIPDst(IpPrefix.valueOf(vmIp, 32));
+ }
+ }
+
+ private void buildMatchEthType(TrafficSelector.Builder sBuilder, String etherType) {
+ // Either IpSrc or IpDst (or both) is set by default, and we need to set EthType as IPv4.
+ sBuilder.matchEthType(Ethernet.TYPE_IPV4);
+ if (etherType != null && !Objects.equals(etherType, "null") &&
+ !etherType.toUpperCase().equals(ETHTYPE_IPV4)) {
+ log.debug("EthType {} is not supported yet in Security Group", etherType);
+ }
+ }
+
+ private void buildMatchRemoteIp(TrafficSelector.Builder sBuilder, IpPrefix remoteIpPrefix, String direction) {
+ if (remoteIpPrefix != null && !remoteIpPrefix.getIp4Prefix().equals(IP_PREFIX_ANY)) {
+ if (direction.toUpperCase().equals(EGRESS)) {
+ sBuilder.matchIPDst(remoteIpPrefix);
+ } else {
+ sBuilder.matchIPSrc(remoteIpPrefix);
+ }
+ }
+ }
+
+ private void buildMatchProto(TrafficSelector.Builder sBuilder, String protocol) {
+ if (protocol != null) {
+ switch (protocol.toUpperCase()) {
+ case PROTO_ICMP:
+ sBuilder.matchIPProtocol(IPv4.PROTOCOL_ICMP);
+ break;
+ case PROTO_TCP:
+ sBuilder.matchIPProtocol(IPv4.PROTOCOL_TCP);
+ break;
+ case PROTO_UDP:
+ sBuilder.matchIPProtocol(IPv4.PROTOCOL_UDP);
+ break;
+ default:
+ }
+ }
+ }
+
+ private void buildMatchPort(TrafficSelector.Builder sBuilder, String protocol, String direction,
+ int portMin, int portMax) {
+ if (portMin > 0 && portMax > 0 && portMin == portMax) {
+ if (protocol.toUpperCase().equals(PROTO_TCP)) {
+ if (direction.toUpperCase().equals(EGRESS)) {
+ sBuilder.matchTcpSrc(TpPort.tpPort(portMax));
+ } else {
+ sBuilder.matchTcpDst(TpPort.tpPort(portMax));
+ }
+ } else if (protocol.toUpperCase().equals(PROTO_UDP)) {
+ if (direction.toUpperCase().equals(EGRESS)) {
+ sBuilder.matchUdpSrc(TpPort.tpPort(portMax));
+ } else {
+ sBuilder.matchUdpDst(TpPort.tpPort(portMax));
+ }
+ }
+ }
+ }
+
+ private void resetSecurityGroupRules() {
+
+ if (useSecurityGroup) {
+ osNodeService.completeNodes(OpenstackNode.NodeType.COMPUTE)
+ .forEach(node -> osFlowRuleService.setUpTableMissEntry(node.intgBridge(), ACL_TABLE));
+ securityGroupService.securityGroups().forEach(securityGroup ->
+ securityGroup.getRules().forEach(this::securityGroupRuleAdded));
+ osNodeService.nodes().stream()
+ .filter(node -> node.type().equals(OpenstackNode.NodeType.COMPUTE))
+ .forEach(node -> initializeConnTrackTable(node .intgBridge(), true));
+ } else {
+ osNodeService.completeNodes(OpenstackNode.NodeType.COMPUTE)
+ .forEach(node -> osFlowRuleService.connectTables(node.intgBridge(), ACL_TABLE, JUMP_TABLE));
+ securityGroupService.securityGroups().forEach(securityGroup ->
+ securityGroup.getRules().forEach(this::securityGroupRuleRemoved));
+ osNodeService.nodes().stream()
+ .filter(node -> node.type().equals(OpenstackNode.NodeType.COMPUTE))
+ .forEach(node -> initializeConnTrackTable(node.intgBridge(), false));
+ }
+
+ log.info("Reset security group info " + (useSecurityGroup ? " with " : " without") + " Security Group");
+ }
+
+ private void securityGroupRuleAdded(SecurityGroupRule sgRule) {
+ osNetService.ports().stream()
+ .filter(port -> port.getSecurityGroups().contains(sgRule.getSecurityGroupId()))
+ .forEach(port -> {
+ updateSecurityGroupRule(
+ instancePortService.instancePort(port.getId()),
+ port, sgRule, true);
+ log.debug("Applied security group rule {} to port {}",
+ sgRule.getId(), port.getId());
+ });
+ }
+
+ private void securityGroupRuleRemoved(SecurityGroupRule sgRule) {
+ osNetService.ports().stream()
+ .filter(port -> port.getSecurityGroups().contains(sgRule.getSecurityGroupId()))
+ .forEach(port -> {
+ updateSecurityGroupRule(
+ instancePortService.instancePort(port.getId()),
+ port, sgRule, false);
+ log.debug("Removed security group rule {} from port {}",
+ sgRule.getId(), port.getId());
+ });
+ }
+
+ private int binLower(String binStr, int bits) {
+ String outBin = binStr.substring(0, 16 - bits);
+ for (int i = 0; i < bits; i++) {
+ outBin += "0";
+ }
+
+ return Integer.parseInt(outBin, 2);
+ }
+
+ private int binHigher(String binStr, int bits) {
+ String outBin = binStr.substring(0, 16 - bits);
+ for (int i = 0; i < bits; i++) {
+ outBin += "1";
+ }
+
+ return Integer.parseInt(outBin, 2);
+ }
+
+ private int testMasks(String binStr, int start, int end) {
+ int mask = 0;
+ for (; mask <= 16; mask++) {
+ int maskStart = binLower(binStr, mask);
+ int maskEnd = binHigher(binStr, mask);
+ if (maskStart < start || maskEnd > end) {
+ return mask - 1;
+ }
+ }
+
+ return mask;
+ }
+
+ private String getMask(int bits) {
+ switch (bits) {
+ case 0: return "ffff";
+ case 1: return "fffe";
+ case 2: return "fffc";
+ case 3: return "fff8";
+ case 4: return "fff0";
+ case 5: return "ffe0";
+ case 6: return "ffc0";
+ case 7: return "ff80";
+ case 8: return "ff00";
+ case 9: return "fe00";
+ case 10: return "fc00";
+ case 11: return "f800";
+ case 12: return "f000";
+ case 13: return "e000";
+ case 14: return "c000";
+ case 15: return "8000";
+ case 16: return "0000";
+ default: return null;
+ }
+ }
+
+ private Map<TpPort, TpPort> buildPortRangeMatches(int portMin, int portMax) {
+
+ boolean processing = true;
+ int start = portMin;
+ Map<TpPort, TpPort> portMaskMap = Maps.newHashMap();
+ while (processing) {
+ String minStr = Integer.toBinaryString(start);
+ String binStrMinPadded = "0000000000000000".substring(minStr.length()) + minStr;
+
+ int mask = testMasks(binStrMinPadded, start, portMax);
+ int maskStart = binLower(binStrMinPadded, mask);
+ int maskEnd = binHigher(binStrMinPadded, mask);
+
+ log.debug("start : {} port/mask = {} / {} ", start, getMask(mask), maskStart);
+ portMaskMap.put(TpPort.tpPort(maskStart), TpPort.tpPort(Integer.parseInt(getMask(mask), 16)));
+
+ start = maskEnd + 1;
+ if (start > portMax) {
+ processing = false;
+ }
+ }
+
+ return portMaskMap;
+ }
+
+ private class InternalInstancePortListener implements InstancePortListener {
+
+ @Override
+ public boolean isRelevant(InstancePortEvent event) {
+ InstancePort instPort = event.subject();
+ if (!useSecurityGroup) {
+ return false;
+ }
+ return mastershipService.isLocalMaster(instPort.deviceId());
+ }
+
+ @Override
+ public void event(InstancePortEvent event) {
+ InstancePort instPort = event.subject();
+ switch (event.type()) {
+ case OPENSTACK_INSTANCE_PORT_UPDATED:
+ case OPENSTACK_INSTANCE_PORT_DETECTED:
+ log.debug("Instance port detected MAC:{} IP:{}",
+ instPort.macAddress(),
+ instPort.ipAddress());
+ eventExecutor.execute(() -> {
+ setSecurityGroupRules(instPort,
+ osNetService.port(event.subject().portId()),
+ true);
+ });
+ break;
+ case OPENSTACK_INSTANCE_PORT_VANISHED:
+ log.debug("Instance port vanished MAC:{} IP:{}",
+ instPort.macAddress(),
+ instPort.ipAddress());
+ eventExecutor.execute(() -> {
+ setSecurityGroupRules(instPort,
+ osNetService.port(event.subject().portId()),
+ false);
+ });
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private class InternalOpenstackPortListener implements OpenstackNetworkListener {
+
+ @Override
+ public boolean isRelevant(OpenstackNetworkEvent event) {
+ if (event.port() == null || !Strings.isNullOrEmpty(event.port().getId())) {
+ return false;
+ }
+ if (event.securityGroupId() == null ||
+ securityGroupService.securityGroup(event.securityGroupId()) == null) {
+ return false;
+ }
+ if (instancePortService.instancePort(event.port().getId()) == null) {
+ return false;
+ }
+ if (!useSecurityGroup) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void event(OpenstackNetworkEvent event) {
+ Port osPort = event.port();
+ InstancePort instPort = instancePortService.instancePort(osPort.getId());
+ SecurityGroup osSg = securityGroupService.securityGroup(event.securityGroupId());
+
+ switch (event.type()) {
+ case OPENSTACK_PORT_SECURITY_GROUP_ADDED:
+ eventExecutor.execute(() -> {
+ osSg.getRules().forEach(sgRule -> {
+ updateSecurityGroupRule(instPort, osPort, sgRule, true);
+ });
+ log.info("Added security group {} to port {}",
+ event.securityGroupId(), event.port().getId());
+ });
+ break;
+ case OPENSTACK_PORT_SECURITY_GROUP_REMOVED:
+ eventExecutor.execute(() -> {
+ osSg.getRules().forEach(sgRule -> {
+ updateSecurityGroupRule(instPort, osPort, sgRule, false);
+ });
+ log.info("Removed security group {} from port {}",
+ event.securityGroupId(), event.port().getId());
+ });
+ break;
+ default:
+ // do nothing for the other events
+ break;
+ }
+ }
+ }
+
+ private class InternalSecurityGroupListener implements OpenstackSecurityGroupListener {
+
+ @Override
+ public boolean isRelevant(OpenstackSecurityGroupEvent event) {
+ if (!useSecurityGroup) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void event(OpenstackSecurityGroupEvent event) {
+ switch (event.type()) {
+ case OPENSTACK_SECURITY_GROUP_RULE_CREATED:
+ SecurityGroupRule securityGroupRuleToAdd = event.securityGroupRule();
+ eventExecutor.execute(() -> {
+ securityGroupRuleAdded(securityGroupRuleToAdd);
+ log.info("Applied new security group rule {} to ports",
+ securityGroupRuleToAdd.getId());
+ });
+ break;
+
+ case OPENSTACK_SECURITY_GROUP_RULE_REMOVED:
+ SecurityGroupRule securityGroupRuleToRemove = event.securityGroupRule();
+ eventExecutor.execute(() -> {
+ securityGroupRuleRemoved(securityGroupRuleToRemove);
+ log.info("Removed security group rule {} from ports",
+ securityGroupRuleToRemove.getId());
+ });
+ break;
+ case OPENSTACK_SECURITY_GROUP_CREATED:
+ case OPENSTACK_SECURITY_GROUP_REMOVED:
+ default:
+ // do nothing
+ break;
+ }
+ }
+ }
+
+ private class InternalNodeListener implements OpenstackNodeListener {
+
+ @Override
+ public boolean isRelevant(OpenstackNodeEvent event) {
+ // do not allow to proceed without leadership
+ NodeId leader = leadershipService.getLeader(appId.name());
+ if (!Objects.equals(localNodeId, leader)) {
+ return false;
+ }
+ return event.subject().type() == COMPUTE;
+ }
+
+ @Override
+ public void event(OpenstackNodeEvent event) {
+ OpenstackNode osNode = event.subject();
+
+ switch (event.type()) {
+ case OPENSTACK_NODE_COMPLETE:
+ eventExecutor.execute(() -> {
+ try {
+ if (useSecurityGroup) {
+ initializeConnTrackTable(osNode.intgBridge(), true);
+ log.warn("SG table initialization : {} is done", osNode.intgBridge());
+ }
+ } catch (IllegalArgumentException e) {
+ log.error("ACL table initialization error : {}", e.getMessage());
+ }
+ });
+ break;
+ case OPENSTACK_NODE_CREATED:
+ case OPENSTACK_NODE_REMOVED:
+ case OPENSTACK_NODE_UPDATED:
+ case OPENSTACK_NODE_INCOMPLETE:
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupManager.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupManager.java
new file mode 100644
index 0000000..f6a4de7
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSecurityGroupManager.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+
+import com.google.common.base.Strings;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.core.CoreService;
+import org.onosproject.event.ListenerRegistry;
+import org.onosproject.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupAdminService;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupEvent;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupListener;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupService;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupStore;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupStoreDelegate;
+import org.openstack4j.model.network.SecurityGroup;
+import org.openstack4j.model.network.SecurityGroupRule;
+import org.openstack4j.openstack.networking.domain.NeutronSecurityGroup;
+import org.slf4j.Logger;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provides implementation of administering and interfacing OpenStack security
+ * groups.
+ */
+@Service
+@Component(immediate = true)
+public class OpenstackSecurityGroupManager
+ extends ListenerRegistry<OpenstackSecurityGroupEvent, OpenstackSecurityGroupListener>
+ implements OpenstackSecurityGroupAdminService, OpenstackSecurityGroupService {
+
+ protected final Logger log = getLogger(getClass());
+
+ private static final String MSG_SG = "OpenStack security group %s %s";
+ private static final String MSG_SG_RULE = "OpenStack security group rule %s %s";
+
+ private static final String MSG_CREATED = "created";
+ private static final String MSG_REMOVED = "removed";
+
+ private static final String ERR_NULL_SG = "OpenStack security group cannot be null";
+ private static final String ERR_NULL_SG_ID = "OpenStack security group ID cannot be null";
+ private static final String ERR_NULL_SG_RULE = "OpenStack security group rule cannot be null";
+ private static final String ERR_NULL_SG_RULE_ID = "OpenStack security group rule ID cannot be null";
+ private static final String ERR_NOT_FOUND = "not found";
+ private static final String ERR_DUPLICATE = "already exist";
+
+ private boolean useSecurityGroup = false;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackSecurityGroupStore osSecurityGroupStore;
+
+ private final OpenstackSecurityGroupStoreDelegate delegate = new InternalSecurityGroupStoreDelegate();
+
+ @Activate
+ protected void activate() {
+ coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
+ osSecurityGroupStore.setDelegate(delegate);
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ osSecurityGroupStore.unsetDelegate(delegate);
+ log.info("Stopped");
+ }
+
+ @Override
+ public void createSecurityGroup(SecurityGroup sg) {
+ checkNotNull(sg, ERR_NULL_SG);
+ checkArgument(!Strings.isNullOrEmpty(sg.getId()), ERR_NULL_SG_ID);
+
+ osSecurityGroupStore.createSecurityGroup(sg);
+ log.info(String.format(MSG_SG, sg.getId(), MSG_CREATED));
+ }
+
+ @Override
+ public void updateSecurityGroup(SecurityGroup sg) {
+ checkNotNull(sg, ERR_NULL_SG);
+ checkArgument(!Strings.isNullOrEmpty(sg.getId()), ERR_NULL_SG_ID);
+
+ osSecurityGroupStore.updateSecurityGroup(sg);
+ }
+
+ @Override
+ public void removeSecurityGroup(String sgId) {
+ checkNotNull(sgId, ERR_NULL_SG_ID);
+
+ osSecurityGroupStore.removeSecurityGroup(sgId);
+ log.info(String.format(MSG_SG, sgId, MSG_REMOVED));
+ }
+
+ @Override
+ public void createSecurityGroupRule(SecurityGroupRule sgRule) {
+ checkNotNull(sgRule, ERR_NULL_SG_RULE);
+ checkArgument(!Strings.isNullOrEmpty(sgRule.getId()), ERR_NULL_SG_RULE_ID);
+ checkArgument(!Strings.isNullOrEmpty(sgRule.getSecurityGroupId()), ERR_NULL_SG_ID);
+
+ synchronized (this) {
+ SecurityGroup sg = securityGroup(sgRule.getSecurityGroupId());
+ if (sg == null) {
+ final String error = String.format(MSG_SG, sgRule.getSecurityGroupId(), ERR_NOT_FOUND);
+ throw new IllegalStateException(error);
+ }
+ if (sg.getRules().stream().anyMatch(rule -> Objects.equals(rule.getId(), sgRule.getId()))) {
+ final String error = String.format(MSG_SG_RULE,
+ sgRule.getSecurityGroupId(), ERR_DUPLICATE);
+ throw new IllegalStateException(error);
+ }
+
+ // FIXME we cannot add element to extend list
+ List updatedSgRules = sg.getRules();
+ updatedSgRules.add(sgRule);
+ SecurityGroup updatedSg = NeutronSecurityGroup.builder().from(sg).build();
+ osSecurityGroupStore.updateSecurityGroup(updatedSg);
+ }
+
+ log.info(String.format(MSG_SG_RULE, sgRule.getId(), MSG_CREATED));
+ }
+
+ @Override
+ public void removeSecurityGroupRule(String sgRuleId) {
+ checkArgument(!Strings.isNullOrEmpty(sgRuleId), ERR_NULL_SG_RULE_ID);
+
+ synchronized (this) {
+ SecurityGroupRule sgRule = securityGroupRule(sgRuleId);
+ if (sgRule == null) {
+ final String error = String.format(MSG_SG_RULE, sgRuleId, ERR_NOT_FOUND);
+ throw new IllegalStateException(error);
+ }
+
+ SecurityGroup sg = securityGroup(sgRule.getSecurityGroupId());
+ if (sg == null) {
+ final String error = String.format(MSG_SG, sgRule.getSecurityGroupId(), ERR_NOT_FOUND);
+ throw new IllegalStateException(error);
+ }
+
+ if (sg.getRules().stream().noneMatch(rule -> Objects.equals(rule.getId(), sgRule.getId()))) {
+ final String error = String.format(MSG_SG_RULE,
+ sgRule.getSecurityGroupId(), ERR_NOT_FOUND);
+ throw new IllegalStateException(error);
+ }
+
+ // FIXME we cannot handle the element of extend list as a specific class object
+ List updatedSgRules = sg.getRules();
+ updatedSgRules.removeIf(r -> ((SecurityGroupRule) r).getId().equals(sgRuleId));
+ SecurityGroup updatedSg = NeutronSecurityGroup.builder().from(sg).build();
+ osSecurityGroupStore.updateSecurityGroup(updatedSg);
+ }
+
+ log.info(String.format(MSG_SG_RULE, sgRuleId, MSG_REMOVED));
+ }
+
+ @Override
+ public Set<SecurityGroup> securityGroups() {
+ return osSecurityGroupStore.securityGroups();
+ }
+
+ @Override
+ public SecurityGroup securityGroup(String sgId) {
+ checkArgument(!Strings.isNullOrEmpty(sgId), ERR_NULL_SG_ID);
+ return osSecurityGroupStore.securityGroup(sgId);
+ }
+
+ @Override
+ public boolean isSecurityGroupEnabled() {
+ return useSecurityGroup;
+ }
+
+ @Override
+ public void setSecurityGroupEnabled(boolean option) {
+ useSecurityGroup = option;
+ }
+
+ @Override
+ public void clear() {
+ osSecurityGroupStore.clear();
+ }
+
+ private SecurityGroupRule securityGroupRule(String sgRuleId) {
+ return osSecurityGroupStore.securityGroups().stream()
+ .flatMap(sg -> sg.getRules().stream())
+ .filter(sgRule -> Objects.equals(sgRule.getId(), sgRuleId))
+ .findFirst().orElse(null);
+ }
+
+ private class InternalSecurityGroupStoreDelegate implements OpenstackSecurityGroupStoreDelegate {
+
+ @Override
+ public void notify(OpenstackSecurityGroupEvent event) {
+ if (event != null) {
+ log.trace("send openstack security group event {}", event);
+ process(event);
+ }
+ }
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingArpHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingArpHandler.java
new file mode 100644
index 0000000..a8bec5f
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingArpHandler.java
@@ -0,0 +1,276 @@
+/*
+* Copyright 2016-present Open Networking Foundation
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package org.onosproject.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Sets;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.ARP;
+import org.onlab.packet.EthType;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+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.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkEvent;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkListener;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.openstack4j.model.network.Subnet;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.Dictionary;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC_STR;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+
+/**
+ * Handles ARP packet from VMs.
+ */
+@Component(immediate = true)
+public final class OpenstackSwitchingArpHandler {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final String GATEWAY_MAC = "gatewayMac";
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ PacketService packetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ ComponentConfigService configService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ InstancePortService instancePortService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ OpenstackNetworkService osNetworkService;
+
+ @Property(name = GATEWAY_MAC, value = DEFAULT_GATEWAY_MAC_STR,
+ label = "Fake MAC address for virtual network subnet gateway")
+ private String gatewayMac = DEFAULT_GATEWAY_MAC_STR;
+
+ private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
+ private final InternalOpenstackNetworkListener osNetworkListener =
+ new InternalOpenstackNetworkListener();
+ private final Set<IpAddress> gateways = Sets.newConcurrentHashSet();
+
+ private ApplicationId appId;
+
+ @Activate
+ void activate() {
+ appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+ configService.registerProperties(getClass());
+ osNetworkService.addListener(osNetworkListener);
+ packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
+ osNetworkService.subnets().forEach(this::addSubnetGateway);
+ requestPacket();
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ void deactivate() {
+ packetService.removeProcessor(packetProcessor);
+ osNetworkService.removeListener(osNetworkListener);
+ configService.unregisterProperties(getClass(), false);
+
+ log.info("Stopped");
+ }
+
+ @Modified
+ void modified(ComponentContext context) {
+ Dictionary<?, ?> properties = context.getProperties();
+ String updatedMac;
+
+ updatedMac = Tools.get(properties, GATEWAY_MAC);
+ if (!Strings.isNullOrEmpty(updatedMac) && !updatedMac.equals(gatewayMac)) {
+ gatewayMac = updatedMac;
+ }
+
+ log.info("Modified");
+ }
+
+ private void requestPacket() {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(EthType.EtherType.ARP.ethType().toShort())
+ .build();
+
+ packetService.requestPackets(
+ selector,
+ PacketPriority.CONTROL,
+ appId);
+ }
+
+ private void addSubnetGateway(Subnet osSubnet) {
+ if (Strings.isNullOrEmpty(osSubnet.getGateway())) {
+ return;
+ }
+ IpAddress gatewayIp = IpAddress.valueOf(osSubnet.getGateway());
+ gateways.add(gatewayIp);
+ log.debug("Added ARP proxy entry IP:{}", gatewayIp);
+ }
+
+ private void removeSubnetGateway(Subnet osSubnet) {
+ if (Strings.isNullOrEmpty(osSubnet.getGateway())) {
+ return;
+ }
+ IpAddress gatewayIp = IpAddress.valueOf(osSubnet.getGateway());
+ gateways.remove(gatewayIp);
+ log.debug("Removed ARP proxy entry IP:{}", gatewayIp);
+ }
+
+ /**
+ * Processes ARP request packets.
+ * It checks if the target IP is owned by a known host first and then ask to
+ * OpenStack if it's not. This ARP proxy does not support overlapping IP.
+ *
+ * @param context packet context
+ * @param ethPacket ethernet packet
+ */
+ private void processPacketIn(PacketContext context, Ethernet ethPacket) {
+ ARP arpPacket = (ARP) ethPacket.getPayload();
+ if (arpPacket.getOpCode() != ARP.OP_REQUEST) {
+ return;
+ }
+
+ InstancePort srcInstPort = instancePortService.instancePort(ethPacket.getSourceMAC());
+ if (srcInstPort == null) {
+ log.trace("Failed to find source instance port(MAC:{})",
+ ethPacket.getSourceMAC());
+ return;
+ }
+
+ IpAddress targetIp = Ip4Address.valueOf(arpPacket.getTargetProtocolAddress());
+ MacAddress replyMac = gateways.contains(targetIp) ? MacAddress.valueOf(gatewayMac) :
+ getMacFromHostOpenstack(targetIp, srcInstPort.networkId());
+ if (replyMac == MacAddress.NONE) {
+ log.trace("Failed to find MAC address for {}", targetIp);
+ return;
+ }
+
+ Ethernet ethReply = ARP.buildArpReply(
+ targetIp.getIp4Address(),
+ replyMac,
+ ethPacket);
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(context.inPacket().receivedFrom().port())
+ .build();
+
+ packetService.emit(new DefaultOutboundPacket(
+ context.inPacket().receivedFrom().deviceId(),
+ treatment,
+ ByteBuffer.wrap(ethReply.serialize())));
+ }
+
+ /**
+ * Returns MAC address of a host with a given target IP address by asking to
+ * instance port service.
+ *
+ * @param targetIp target ip
+ * @param osNetId openstack network id of the source instance port
+ * @return mac address, or none mac address if it fails to find the mac
+ */
+ private MacAddress getMacFromHostOpenstack(IpAddress targetIp, String osNetId) {
+ checkNotNull(targetIp);
+
+ InstancePort instPort = instancePortService.instancePort(targetIp, osNetId);
+ if (instPort != null) {
+ log.trace("Found MAC from host service for {}", targetIp);
+ return instPort.macAddress();
+ } else {
+ return MacAddress.NONE;
+ }
+ }
+
+ private class InternalPacketProcessor implements PacketProcessor {
+
+ @Override
+ public void process(PacketContext context) {
+ if (context.isHandled()) {
+ return;
+ }
+
+ Ethernet ethPacket = context.inPacket().parsed();
+ if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_ARP) {
+ return;
+ }
+ processPacketIn(context, ethPacket);
+ }
+ }
+
+ private class InternalOpenstackNetworkListener implements OpenstackNetworkListener {
+
+ @Override
+ public boolean isRelevant(OpenstackNetworkEvent event) {
+ Subnet osSubnet = event.subnet();
+ if (osSubnet == null) {
+ return false;
+ }
+ return !Strings.isNullOrEmpty(osSubnet.getGateway());
+ }
+
+ @Override
+ public void event(OpenstackNetworkEvent event) {
+ switch (event.type()) {
+ case OPENSTACK_SUBNET_CREATED:
+ case OPENSTACK_SUBNET_UPDATED:
+ addSubnetGateway(event.subnet());
+ break;
+ case OPENSTACK_SUBNET_REMOVED:
+ removeSubnetGateway(event.subnet());
+ break;
+ case OPENSTACK_NETWORK_CREATED:
+ case OPENSTACK_NETWORK_UPDATED:
+ case OPENSTACK_NETWORK_REMOVED:
+ case OPENSTACK_PORT_CREATED:
+ case OPENSTACK_PORT_UPDATED:
+ case OPENSTACK_PORT_REMOVED:
+ default:
+ // do nothing for the other events
+ break;
+ }
+ }
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandler.java
new file mode 100644
index 0000000..12e98e9
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingDhcpHandler.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.DHCP;
+import org.onlab.packet.dhcp.DhcpOption;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.UDP;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+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.openstacknetworking.api.Constants;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.openstack4j.model.network.IP;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.Subnet;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.Dictionary;
+import java.util.List;
+
+import static org.onlab.packet.DHCP.DHCPOptionCode.*;
+import static org.onlab.packet.DHCP.MsgType.DHCPACK;
+import static org.onlab.packet.DHCP.MsgType.DHCPOFFER;
+import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC_STR;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Handles DHCP requests for the virtual instances.
+ */
+@Component(immediate = true)
+public class OpenstackSwitchingDhcpHandler {
+ protected final Logger log = getLogger(getClass());
+
+ private static final String DHCP_SERVER_MAC = "dhcpServerMac";
+ private static final Ip4Address DEFAULT_DNS = Ip4Address.valueOf("8.8.8.8");
+ private static final byte PACKET_TTL = (byte) 127;
+ // TODO add MTU, static route option codes to ONOS DHCP and remove here
+ private static final byte DHCP_OPTION_MTU = (byte) 26;
+ private static final byte[] DHCP_DATA_LEASE_INFINITE =
+ ByteBuffer.allocate(4).putInt(-1).array();
+ private static final byte[] DHCP_DATA_MTU_DEFAULT =
+ ByteBuffer.allocate(2).putShort((short) 1450).array();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ComponentConfigService configService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected PacketService packetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected InstancePortService instancePortService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OpenstackNetworkService osNetworkService;
+
+ @Property(name = DHCP_SERVER_MAC, value = DEFAULT_GATEWAY_MAC_STR,
+ label = "Fake MAC address for virtual network subnet gateway")
+ private String dhcpServerMac = DEFAULT_GATEWAY_MAC_STR;
+
+ private final PacketProcessor packetProcessor = new InternalPacketProcessor();
+
+ private ApplicationId appId;
+
+ @Activate
+ protected void activate() {
+ appId = coreService.registerApplication(Constants.OPENSTACK_NETWORKING_APP_ID);
+ configService.registerProperties(getClass());
+ packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
+ requestPackets();
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ cancelPackets();
+ packetService.removeProcessor(packetProcessor);
+ configService.unregisterProperties(getClass(), false);
+
+ log.info("Stopped");
+ }
+
+ @Modified
+ protected void modified(ComponentContext context) {
+ Dictionary<?, ?> properties = context.getProperties();
+ String updatedMac;
+
+ updatedMac = Tools.get(properties, DHCP_SERVER_MAC);
+ if (!Strings.isNullOrEmpty(updatedMac) && !updatedMac.equals(dhcpServerMac)) {
+ dhcpServerMac = updatedMac;
+ }
+
+ log.info("Modified");
+ }
+
+ private void requestPackets() {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPProtocol(IPv4.PROTOCOL_UDP)
+ .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
+ .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
+ .build();
+ packetService.requestPackets(selector, PacketPriority.CONTROL, appId);
+ }
+
+ private void cancelPackets() {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPProtocol(IPv4.PROTOCOL_UDP)
+ .matchUdpDst(TpPort.tpPort(UDP.DHCP_SERVER_PORT))
+ .matchUdpSrc(TpPort.tpPort(UDP.DHCP_CLIENT_PORT))
+ .build();
+ packetService.cancelPackets(selector, PacketPriority.CONTROL, appId);
+ }
+
+ private class InternalPacketProcessor implements PacketProcessor {
+
+ @Override
+ public void process(PacketContext context) {
+ if (context.isHandled()) {
+ return;
+ }
+
+ Ethernet ethPacket = context.inPacket().parsed();
+ if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_IPV4) {
+ return;
+ }
+ IPv4 ipv4Packet = (IPv4) ethPacket.getPayload();
+ if (ipv4Packet.getProtocol() != IPv4.PROTOCOL_UDP) {
+ return;
+ }
+ UDP udpPacket = (UDP) ipv4Packet.getPayload();
+ if (udpPacket.getDestinationPort() != UDP.DHCP_SERVER_PORT ||
+ udpPacket.getSourcePort() != UDP.DHCP_CLIENT_PORT) {
+ return;
+ }
+
+ DHCP dhcpPacket = (DHCP) udpPacket.getPayload();
+ processDhcp(context, dhcpPacket);
+ }
+
+ private void processDhcp(PacketContext context, DHCP dhcpPacket) {
+ if (dhcpPacket == null) {
+ log.trace("DHCP packet without payload received, do nothing");
+ return;
+ }
+
+ DHCP.MsgType inPacketType = getPacketType(dhcpPacket);
+ if (inPacketType == null || dhcpPacket.getClientHardwareAddress() == null) {
+ log.trace("Malformed DHCP packet received, ignore it");
+ return;
+ }
+
+ MacAddress clientMac = MacAddress.valueOf(dhcpPacket.getClientHardwareAddress());
+ InstancePort reqInstPort = instancePortService.instancePort(clientMac);
+ if (reqInstPort == null) {
+ log.trace("Failed to find host(MAC:{})", clientMac);
+ return;
+ }
+ Ethernet ethPacket = context.inPacket().parsed();
+ switch (inPacketType) {
+ case DHCPDISCOVER:
+ log.trace("DHCP DISCOVER received from {}", clientMac);
+ Ethernet discoverReply = buildReply(
+ ethPacket,
+ (byte) DHCPOFFER.getValue(),
+ reqInstPort);
+ sendReply(context, discoverReply);
+ log.trace("DHCP OFFER({}) is sent for {}",
+ reqInstPort.ipAddress(), clientMac);
+ break;
+ case DHCPREQUEST:
+ log.trace("DHCP REQUEST received from {}", clientMac);
+ Ethernet requestReply = buildReply(
+ ethPacket,
+ (byte) DHCPACK.getValue(),
+ reqInstPort);
+ sendReply(context, requestReply);
+ log.trace("DHCP ACK({}) is sent for {}",
+ reqInstPort.ipAddress(), clientMac);
+ break;
+ case DHCPRELEASE:
+ log.trace("DHCP RELEASE received from {}", clientMac);
+ // do nothing
+ break;
+ default:
+ break;
+ }
+ }
+
+ private DHCP.MsgType getPacketType(DHCP dhcpPacket) {
+ DhcpOption optType = dhcpPacket.getOption(OptionCode_MessageType);
+ if (optType == null) {
+ log.trace("DHCP packet with no message type, ignore it");
+ return null;
+ }
+
+ DHCP.MsgType inPacketType = DHCP.MsgType.getType(optType.getData()[0]);
+ if (inPacketType == null) {
+ log.trace("DHCP packet with no packet type, ignore it");
+ }
+ return inPacketType;
+ }
+
+ private Ethernet buildReply(Ethernet ethRequest, byte packetType,
+ InstancePort reqInstPort) {
+ Port osPort = osNetworkService.port(reqInstPort.portId());
+ // pick one IP address to make a reply
+ IP fixedIp = osPort.getFixedIps().stream().findFirst().get();
+ Subnet osSubnet = osNetworkService.subnet(fixedIp.getSubnetId());
+
+ Ethernet ethReply = new Ethernet();
+ ethReply.setSourceMACAddress(dhcpServerMac);
+ ethReply.setDestinationMACAddress(ethRequest.getSourceMAC());
+ ethReply.setEtherType(Ethernet.TYPE_IPV4);
+
+ IPv4 ipv4Request = (IPv4) ethRequest.getPayload();
+ IPv4 ipv4Reply = new IPv4();
+ ipv4Reply.setSourceAddress(Ip4Address.valueOf(osSubnet.getGateway()).toInt());
+ ipv4Reply.setDestinationAddress(reqInstPort.ipAddress().getIp4Address().toInt());
+ ipv4Reply.setTtl(PACKET_TTL);
+
+ UDP udpRequest = (UDP) ipv4Request.getPayload();
+ UDP udpReply = new UDP();
+ udpReply.setSourcePort((byte) UDP.DHCP_SERVER_PORT);
+ udpReply.setDestinationPort((byte) UDP.DHCP_CLIENT_PORT);
+
+ DHCP dhcpRequest = (DHCP) udpRequest.getPayload();
+ DHCP dhcpReply = buildDhcpReply(
+ dhcpRequest,
+ packetType,
+ reqInstPort.ipAddress().getIp4Address(),
+ osSubnet);
+
+ udpReply.setPayload(dhcpReply);
+ ipv4Reply.setPayload(udpReply);
+ ethReply.setPayload(ipv4Reply);
+
+ return ethReply;
+ }
+
+ private void sendReply(PacketContext context, Ethernet ethReply) {
+ if (ethReply == null) {
+ return;
+ }
+ ConnectPoint srcPoint = context.inPacket().receivedFrom();
+ TrafficTreatment treatment = DefaultTrafficTreatment
+ .builder()
+ .setOutput(srcPoint.port())
+ .build();
+
+ packetService.emit(new DefaultOutboundPacket(
+ srcPoint.deviceId(),
+ treatment,
+ ByteBuffer.wrap(ethReply.serialize())));
+ context.block();
+ }
+
+ private DHCP buildDhcpReply(DHCP request, byte msgType, Ip4Address yourIp,
+ Subnet osSubnet) {
+ Ip4Address gatewayIp = Ip4Address.valueOf(osSubnet.getGateway());
+ int subnetPrefixLen = IpPrefix.valueOf(osSubnet.getCidr()).prefixLength();
+
+ DHCP dhcpReply = new DHCP();
+ dhcpReply.setOpCode(DHCP.OPCODE_REPLY);
+ dhcpReply.setHardwareType(DHCP.HWTYPE_ETHERNET);
+ dhcpReply.setHardwareAddressLength((byte) 6);
+ dhcpReply.setTransactionId(request.getTransactionId());
+ dhcpReply.setFlags(request.getFlags());
+ dhcpReply.setYourIPAddress(yourIp.toInt());
+ dhcpReply.setServerIPAddress(gatewayIp.toInt());
+ dhcpReply.setClientHardwareAddress(request.getClientHardwareAddress());
+
+ List<DhcpOption> options = Lists.newArrayList();
+ // message type
+ DhcpOption option = new DhcpOption();
+ option.setCode(OptionCode_MessageType.getValue());
+ option.setLength((byte) 1);
+ byte[] optionData = {msgType};
+ option.setData(optionData);
+ options.add(option);
+
+ // server identifier
+ option = new DhcpOption();
+ option.setCode(OptionCode_DHCPServerIp.getValue());
+ option.setLength((byte) 4);
+ option.setData(gatewayIp.toOctets());
+ options.add(option);
+
+ // lease time
+ option = new DhcpOption();
+ option.setCode(OptionCode_LeaseTime.getValue());
+ option.setLength((byte) 4);
+ option.setData(DHCP_DATA_LEASE_INFINITE);
+ options.add(option);
+
+ // subnet mask
+ Ip4Address subnetMask = Ip4Address.makeMaskPrefix(subnetPrefixLen);
+ option = new DhcpOption();
+ option.setCode(OptionCode_SubnetMask.getValue());
+ option.setLength((byte) 4);
+ option.setData(subnetMask.toOctets());
+ options.add(option);
+
+ // broadcast address
+ Ip4Address broadcast = Ip4Address.makeMaskedAddress(yourIp, subnetPrefixLen);
+ option = new DhcpOption();
+ option.setCode(OptionCode_BroadcastAddress.getValue());
+ option.setLength((byte) 4);
+ option.setData(broadcast.toOctets());
+ options.add(option);
+
+ // domain server
+ option = new DhcpOption();
+ option.setCode(OptionCode_DomainServer.getValue());
+ option.setLength((byte) 4);
+ option.setData(DEFAULT_DNS.toOctets());
+ options.add(option);
+
+ // TODO fix MTU value to be configurable
+ option = new DhcpOption();
+ option.setCode(DHCP_OPTION_MTU);
+ option.setLength((byte) 2);
+ option.setData(DHCP_DATA_MTU_DEFAULT);
+ options.add(option);
+
+ // router address
+ option = new DhcpOption();
+ option.setCode(OptionCode_RouterAddress.getValue());
+ option.setLength((byte) 4);
+ option.setData(gatewayIp.toOctets());
+ options.add(option);
+
+ // end option
+ option = new DhcpOption();
+ option.setCode(OptionCode_END.getValue());
+ option.setLength((byte) 1);
+ options.add(option);
+
+ dhcpReply.setOptions(options);
+ return dhcpReply;
+ }
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingHandler.java
new file mode 100644
index 0000000..37cabf4
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingHandler.java
@@ -0,0 +1,409 @@
+/*
+* Copyright 2016-present Open Networking Foundation
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package org.onosproject.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.DriverService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortEvent;
+import org.onosproject.openstacknetworking.api.InstancePortListener;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.NetworkType;
+import org.openstack4j.model.network.Port;
+import org.slf4j.Logger;
+
+import java.util.concurrent.ExecutorService;
+
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.openstacknetworking.api.Constants.ACL_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.FORWARDING_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ADMIN_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_SWITCHING_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_TUNNEL_TAG_RULE;
+import static org.onosproject.openstacknetworking.api.Constants.SRC_VNI_TABLE;
+import static org.onosproject.openstacknetworking.impl.RulePopulatorUtil.buildExtension;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
+import static org.slf4j.LoggerFactory.getLogger;
+
+
+/**
+ * Populates switching flow rules on OVS for the basic connectivity among the
+ * virtual instances in the same network.
+ */
+@Component(immediate = true)
+public final class OpenstackSwitchingHandler {
+
+ private final Logger log = getLogger(getClass());
+
+ private static final String ERR_SET_FLOWS = "Failed to set flows for %s: ";
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ MastershipService mastershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ OpenstackFlowRuleService osFlowRuleService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ InstancePortService instancePortService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ OpenstackNetworkService osNetworkService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ OpenstackNodeService osNodeService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ DriverService driverService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ OpenstackSecurityGroupService securityGroupService;
+
+ private final ExecutorService eventExecutor = newSingleThreadExecutor(
+ groupedThreads(this.getClass().getSimpleName(), "event-handler"));
+ private final InstancePortListener instancePortListener = new InternalInstancePortListener();
+ private ApplicationId appId;
+
+ @Activate
+ void activate() {
+ appId = coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+ instancePortService.addListener(instancePortListener);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ void deactivate() {
+ instancePortService.removeListener(instancePortListener);
+ eventExecutor.shutdown();
+
+ log.info("Stopped");
+ }
+
+ private void setNetworkRules(InstancePort instPort, boolean install) {
+ switch (osNetworkService.network(instPort.networkId()).getNetworkType()) {
+ case VXLAN:
+ setTunnelTagFlowRules(instPort, install);
+ setForwardingRules(instPort, install);
+ break;
+ case VLAN:
+ setVlanTagFlowRules(instPort, install);
+ setForwardingRulesForVlan(instPort, install);
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void setForwardingRules(InstancePort instPort, boolean install) {
+ // switching rules for the instPorts in the same node
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(instPort.ipAddress().toIpPrefix())
+ .matchTunnelId(getVni(instPort))
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setEthDst(instPort.macAddress())
+ .setOutput(instPort.portNumber())
+ .build();
+
+ osFlowRuleService.setRule(
+ appId,
+ instPort.deviceId(),
+ selector,
+ treatment,
+ PRIORITY_SWITCHING_RULE,
+ FORWARDING_TABLE,
+ install);
+
+ // switching rules for the instPorts in the remote node
+ OpenstackNode localNode = osNodeService.node(instPort.deviceId());
+ if (localNode == null) {
+ final String error = String.format("Cannot find openstack node for %s",
+ instPort.deviceId());
+ throw new IllegalStateException(error);
+ }
+ osNodeService.completeNodes(COMPUTE).stream()
+ .filter(remoteNode -> !remoteNode.intgBridge().equals(localNode.intgBridge()))
+ .forEach(remoteNode -> {
+ TrafficTreatment treatmentToRemote = DefaultTrafficTreatment.builder()
+ .extension(buildExtension(
+ deviceService,
+ remoteNode.intgBridge(),
+ localNode.dataIp().getIp4Address()),
+ remoteNode.intgBridge())
+ .setOutput(remoteNode.tunnelPortNum())
+ .build();
+
+ osFlowRuleService.setRule(
+ appId,
+ remoteNode.intgBridge(),
+ selector,
+ treatmentToRemote,
+ PRIORITY_SWITCHING_RULE,
+ FORWARDING_TABLE,
+ install);
+ });
+ }
+
+ private void setForwardingRulesForVlan(InstancePort instPort, boolean install) {
+ // switching rules for the instPorts in the same node
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(instPort.ipAddress().toIpPrefix())
+ .matchVlanId(getVlanId(instPort))
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .popVlan()
+ .setEthDst(instPort.macAddress())
+ .setOutput(instPort.portNumber())
+ .build();
+
+ osFlowRuleService.setRule(
+ appId,
+ instPort.deviceId(),
+ selector,
+ treatment,
+ PRIORITY_SWITCHING_RULE,
+ FORWARDING_TABLE,
+ install);
+
+ // switching rules for the instPorts in the remote node
+ osNodeService.completeNodes(COMPUTE).stream()
+ .filter(remoteNode -> !remoteNode.intgBridge().equals(instPort.deviceId()) &&
+ remoteNode.vlanIntf() != null)
+ .forEach(remoteNode -> {
+ TrafficTreatment treatmentToRemote = DefaultTrafficTreatment.builder()
+ .setOutput(remoteNode.vlanPortNum())
+ .build();
+
+ osFlowRuleService.setRule(
+ appId,
+ remoteNode.intgBridge(),
+ selector,
+ treatmentToRemote,
+ PRIORITY_SWITCHING_RULE,
+ FORWARDING_TABLE,
+ install);
+ });
+ }
+
+ private void setTunnelTagFlowRules(InstancePort instPort, boolean install) {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchInPort(instPort.portNumber())
+ .build();
+
+ // XXX All egress traffic needs to go through connection tracking module, which might hurt its performance.
+ ExtensionTreatment ctTreatment =
+ RulePopulatorUtil.niciraConnTrackTreatmentBuilder(driverService, instPort.deviceId())
+ .commit(true).build();
+
+ TrafficTreatment.Builder tb = DefaultTrafficTreatment.builder()
+ .setTunnelId(getVni(instPort))
+ .transition(ACL_TABLE);
+
+ if (securityGroupService.isSecurityGroupEnabled()) {
+ tb.extension(ctTreatment, instPort.deviceId());
+ }
+
+ osFlowRuleService.setRule(
+ appId,
+ instPort.deviceId(),
+ selector,
+ tb.build(),
+ PRIORITY_TUNNEL_TAG_RULE,
+ SRC_VNI_TABLE,
+ install);
+ }
+
+ private void setVlanTagFlowRules(InstancePort instPort, boolean install) {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchInPort(instPort.portNumber())
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .pushVlan()
+ .setVlanId(getVlanId(instPort))
+ .transition(ACL_TABLE)
+ .build();
+
+ osFlowRuleService.setRule(
+ appId,
+ instPort.deviceId(),
+ selector,
+ treatment,
+ PRIORITY_TUNNEL_TAG_RULE,
+ SRC_VNI_TABLE,
+ install);
+
+ }
+
+ private void setNetworkAdminRules(Network network, boolean install) {
+ TrafficSelector selector;
+ if (network.getNetworkType() == NetworkType.VXLAN) {
+
+ selector = DefaultTrafficSelector.builder()
+ .matchTunnelId(Long.valueOf(network.getProviderSegID()))
+ .build();
+ } else {
+ selector = DefaultTrafficSelector.builder()
+ .matchVlanId(VlanId.vlanId(network.getProviderSegID()))
+ .build();
+ }
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .drop()
+ .build();
+
+ osNodeService.completeNodes().stream()
+ .filter(osNode -> osNode.type() == COMPUTE)
+ .forEach(osNode -> {
+ osFlowRuleService.setRule(
+ appId,
+ osNode.intgBridge(),
+ selector,
+ treatment,
+ PRIORITY_ADMIN_RULE,
+ ACL_TABLE,
+ install);
+ });
+ }
+
+ private void setPortAdminRules(Port port, boolean install) {
+ InstancePort instancePort = instancePortService.instancePort(MacAddress.valueOf(port.getMacAddress()));
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchInPort(instancePort.portNumber())
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .drop()
+ .build();
+
+ osFlowRuleService.setRule(
+ appId,
+ instancePort.deviceId(),
+ selector,
+ treatment,
+ PRIORITY_ADMIN_RULE,
+ SRC_VNI_TABLE,
+ install);
+ }
+
+ private VlanId getVlanId(InstancePort instPort) {
+ Network osNet = osNetworkService.network(instPort.networkId());
+
+ if (osNet == null || Strings.isNullOrEmpty(osNet.getProviderSegID())) {
+ final String error = String.format(
+ ERR_SET_FLOWS + "Failed to get VNI for %s",
+ instPort, osNet == null ? "<none>" : osNet.getName());
+ throw new IllegalStateException(error);
+ }
+
+ return VlanId.vlanId(osNet.getProviderSegID());
+ }
+
+
+ private Long getVni(InstancePort instPort) {
+ Network osNet = osNetworkService.network(instPort.networkId());
+ if (osNet == null || Strings.isNullOrEmpty(osNet.getProviderSegID())) {
+ final String error = String.format(
+ ERR_SET_FLOWS + "Failed to get VNI for %s",
+ instPort, osNet == null ? "<none>" : osNet.getName());
+ throw new IllegalStateException(error);
+ }
+ return Long.valueOf(osNet.getProviderSegID());
+ }
+
+ private class InternalInstancePortListener implements InstancePortListener {
+
+ @Override
+ public boolean isRelevant(InstancePortEvent event) {
+ InstancePort instPort = event.subject();
+ return mastershipService.isLocalMaster(instPort.deviceId());
+ }
+
+ @Override
+ public void event(InstancePortEvent event) {
+ InstancePort instPort = event.subject();
+ switch (event.type()) {
+ case OPENSTACK_INSTANCE_PORT_UPDATED:
+ case OPENSTACK_INSTANCE_PORT_DETECTED:
+ eventExecutor.execute(() -> {
+ log.info("Instance port detected MAC:{} IP:{}",
+ instPort.macAddress(),
+ instPort.ipAddress());
+ instPortDetected(event.subject());
+ });
+ break;
+ case OPENSTACK_INSTANCE_PORT_VANISHED:
+ eventExecutor.execute(() -> {
+ log.info("Instance port vanished MAC:{} IP:{}",
+ instPort.macAddress(),
+ instPort.ipAddress());
+ instPortRemoved(event.subject());
+ });
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void instPortDetected(InstancePort instPort) {
+ setNetworkRules(instPort, true);
+ // TODO add something else if needed
+ }
+
+ private void instPortRemoved(InstancePort instPort) {
+ setNetworkRules(instPort, false);
+ // TODO add something else if needed
+ }
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingHostProvider.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingHostProvider.java
new file mode 100644
index 0000000..64141b2
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackSwitchingHostProvider.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import com.google.common.base.Strings;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.util.Tools;
+import org.onosproject.core.CoreService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.Device;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.Port;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.host.DefaultHostDescription;
+import org.onosproject.net.host.HostDescription;
+import org.onosproject.net.host.HostProvider;
+import org.onosproject.net.host.HostProviderRegistry;
+import org.onosproject.net.host.HostProviderService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.provider.AbstractProvider;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeEvent;
+import org.onosproject.openstacknode.api.OpenstackNodeListener;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.openstack4j.model.network.Network;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
+
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.net.AnnotationKeys.PORT_NAME;
+import static org.onosproject.openstacknetworking.api.Constants.*;
+import static org.onosproject.openstacknetworking.impl.HostBasedInstancePort.ANNOTATION_CREATE_TIME;
+import static org.onosproject.openstacknetworking.impl.HostBasedInstancePort.ANNOTATION_NETWORK_ID;
+import static org.onosproject.openstacknetworking.impl.HostBasedInstancePort.ANNOTATION_PORT_ID;
+
+@Service
+@Component(immediate = true)
+public final class OpenstackSwitchingHostProvider extends AbstractProvider implements HostProvider {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final String PORT_NAME_PREFIX_VM = "tap";
+ private static final String ERR_ADD_HOST = "Failed to add host: ";
+ private static final String ANNOTATION_SEGMENT_ID = "segId";
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ HostProviderRegistry hostProviderRegistry;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ HostService hostService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ MastershipService mastershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ OpenstackNetworkService osNetworkService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ OpenstackNodeService osNodeService;
+
+ private final ExecutorService deviceEventExecutor =
+ Executors.newSingleThreadExecutor(groupedThreads("openstacknetworking", "device-event"));
+ private final ExecutorService configEventExecutor =
+ Executors.newSingleThreadExecutor(groupedThreads("openstacknetworking", "config-event"));
+ private final InternalDeviceListener internalDeviceListener = new InternalDeviceListener();
+ private final InternalOpenstackNodeListener internalNodeListener = new InternalOpenstackNodeListener();
+
+ private HostProviderService hostProvider;
+
+ /**
+ * Creates OpenStack switching host provider.
+ */
+ public OpenstackSwitchingHostProvider() {
+ super(new ProviderId("sona", OPENSTACK_NETWORKING_APP_ID));
+ }
+
+ @Activate
+ void activate() {
+ coreService.registerApplication(OPENSTACK_NETWORKING_APP_ID);
+ deviceService.addListener(internalDeviceListener);
+ osNodeService.addListener(internalNodeListener);
+ hostProvider = hostProviderRegistry.register(this);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ void deactivate() {
+ hostProviderRegistry.unregister(this);
+ osNodeService.removeListener(internalNodeListener);
+ deviceService.removeListener(internalDeviceListener);
+
+ deviceEventExecutor.shutdown();
+ configEventExecutor.shutdown();
+
+ log.info("Stopped");
+ }
+
+ @Override
+ public void triggerProbe(Host host) {
+ // no probe is required
+ }
+
+ private void processPortAdded(Port port) {
+ // TODO check the node state is COMPLETE
+ org.openstack4j.model.network.Port osPort = osNetworkService.port(port);
+ if (osPort == null) {
+ log.warn(ERR_ADD_HOST + "OpenStack port for {} not found", port);
+ return;
+ }
+
+ Network osNet = osNetworkService.network(osPort.getNetworkId());
+ if (osNet == null) {
+ log.warn(ERR_ADD_HOST + "OpenStack network {} not found",
+ osPort.getNetworkId());
+ return;
+ }
+
+ if (osPort.getFixedIps().isEmpty()) {
+ log.warn(ERR_ADD_HOST + "no fixed IP for port {}", osPort.getId());
+ return;
+ }
+
+ MacAddress macAddr = MacAddress.valueOf(osPort.getMacAddress());
+ Set<IpAddress> fixedIps = osPort.getFixedIps().stream()
+ .map(ip -> IpAddress.valueOf(ip.getIpAddress()))
+ .collect(Collectors.toSet());
+ ConnectPoint connectPoint = new ConnectPoint(port.element().id(), port.number());
+
+ DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
+ .set(ANNOTATION_NETWORK_ID, osPort.getNetworkId())
+ .set(ANNOTATION_PORT_ID, osPort.getId())
+ .set(ANNOTATION_CREATE_TIME, String.valueOf(System.currentTimeMillis()))
+ .set(ANNOTATION_SEGMENT_ID, osNet.getProviderSegID());
+
+ HostDescription hostDesc = new DefaultHostDescription(
+ macAddr,
+ VlanId.NONE,
+ new HostLocation(connectPoint, System.currentTimeMillis()),
+ fixedIps,
+ annotations.build());
+
+ HostId hostId = HostId.hostId(macAddr);
+ hostProvider.hostDetected(hostId, hostDesc, false);
+ }
+
+ private void processPortRemoved(Port port) {
+ ConnectPoint connectPoint = new ConnectPoint(port.element().id(), port.number());
+ hostService.getConnectedHosts(connectPoint).forEach(host -> {
+ hostProvider.hostVanished(host.id());
+ });
+ }
+
+ private class InternalDeviceListener implements DeviceListener {
+
+ @Override
+ public boolean isRelevant(DeviceEvent event) {
+ Device device = event.subject();
+ if (!mastershipService.isLocalMaster(device.id())) {
+ // do not allow to proceed without mastership
+ return false;
+ }
+ Port port = event.port();
+ if (port == null) {
+ return false;
+ }
+ String portName = port.annotations().value(PORT_NAME);
+ if (Strings.isNullOrEmpty(portName) ||
+ !portName.startsWith(PORT_NAME_PREFIX_VM)) {
+ // handles Nova created port event only
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void event(DeviceEvent event) {
+ switch (event.type()) {
+ case PORT_UPDATED:
+ if (!event.port().isEnabled()) {
+ deviceEventExecutor.execute(() -> {
+ log.debug("Instance port {} is removed from {}",
+ event.port().annotations().value(PORT_NAME),
+ event.subject().id());
+ processPortRemoved(event.port());
+ });
+ } else if (event.port().isEnabled()) {
+ deviceEventExecutor.execute(() -> {
+ log.debug("Instance Port {} is detected from {}",
+ event.port().annotations().value(PORT_NAME),
+ event.subject().id());
+ processPortAdded(event.port());
+ });
+ }
+ break;
+ case PORT_ADDED:
+ deviceEventExecutor.execute(() -> {
+ log.debug("Instance port {} is detected from {}",
+ event.port().annotations().value(PORT_NAME),
+ event.subject().id());
+ processPortAdded(event.port());
+ });
+ break;
+ case PORT_REMOVED:
+ deviceEventExecutor.execute(() -> {
+ log.debug("Instance port {} is removed from {}",
+ event.port().annotations().value(PORT_NAME),
+ event.subject().id());
+ processPortRemoved(event.port());
+ });
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private class InternalOpenstackNodeListener implements OpenstackNodeListener {
+
+ @Override
+ public void event(OpenstackNodeEvent event) {
+ OpenstackNode osNode = event.subject();
+ // TODO check leadership of the node and make only the leader process
+
+ switch (event.type()) {
+ case OPENSTACK_NODE_COMPLETE:
+ deviceEventExecutor.execute(() -> {
+ log.info("COMPLETE node {} is detected", osNode.hostname());
+ processCompleteNode(event.subject());
+ });
+ break;
+ case OPENSTACK_NODE_INCOMPLETE:
+ log.warn("{} is changed to INCOMPLETE state", osNode);
+ break;
+ case OPENSTACK_NODE_CREATED:
+ case OPENSTACK_NODE_UPDATED:
+ case OPENSTACK_NODE_REMOVED:
+ default:
+ break;
+ }
+ }
+
+ private void processCompleteNode(OpenstackNode osNode) {
+ deviceService.getPorts(osNode.intgBridge()).stream()
+ .filter(port -> port.annotations().value(PORT_NAME)
+ .startsWith(PORT_NAME_PREFIX_VM) &&
+ port.isEnabled())
+ .forEach(port -> {
+ log.debug("Instance port {} is detected from {}",
+ port.annotations().value(PORT_NAME),
+ osNode.hostname());
+ processPortAdded(port);
+ });
+
+ Tools.stream(hostService.getHosts())
+ .filter(host -> deviceService.getPort(
+ host.location().deviceId(),
+ host.location().port()) == null)
+ .forEach(host -> {
+ log.info("Remove stale host {}", host.id());
+ hostProvider.hostVanished(host.id());
+ });
+ }
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/RulePopulatorUtil.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/RulePopulatorUtil.java
new file mode 100644
index 0000000..5dbf7a7
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/RulePopulatorUtil.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.behaviour.ExtensionSelectorResolver;
+import org.onosproject.net.behaviour.ExtensionTreatmentResolver;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.net.driver.DriverService;
+import org.onosproject.net.flow.criteria.ExtensionSelector;
+import org.onosproject.net.flow.criteria.ExtensionSelectorType;
+import org.onosproject.net.flow.instructions.ExtensionPropertyException;
+import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.net.flow.instructions.ExtensionTreatmentType;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provides common methods to help populating flow rules for SONA applications.
+ */
+public final class RulePopulatorUtil {
+
+ private static final Logger log = getLogger(RulePopulatorUtil.class);
+
+ private static final String TUNNEL_DST = "tunnelDst";
+ private static final String CT_FLAGS = "flags";
+ private static final String CT_ZONE = "zone";
+ private static final String CT_TABLE = "recircTable";
+ private static final String CT = "niciraCt";
+ private static final String CT_STATE = "ctState";
+ private static final String CT_STATE_MASK = "ctStateMask";
+ private static final String CT_PRESENT_FLAGS = "presentFlags";
+ private static final String CT_IPADDRESS_MIN = "ipAddressMin";
+ private static final String CT_IPADDRESS_MAX = "ipAddressMax";
+
+ private static final int ADDRESS_MIN_FLAG = 0;
+ private static final int ADDRESS_MAX_FLAG = 1;
+ private static final int PORT_MIN_FLAG = 2;
+ private static final int PORT_MAX_FLAG = 3;
+
+ // Refer to http://openvswitch.org/support/dist-docs/ovs-fields.7.txt for the values
+ public static final long CT_STATE_NONE = 0;
+ public static final long CT_STATE_NEW = 0x01;
+ public static final long CT_STATE_EST = 0x02;
+ public static final long CT_STATE_NOT_TRK = 0x20;
+ public static final long CT_STATE_TRK = 0x20;
+
+ private RulePopulatorUtil() {
+ }
+
+ /**
+ * Returns a builder for OVS Connection Tracking feature actions.
+ *
+ * @param ds DriverService
+ * @param id DeviceId
+ * @return a builder for OVS Connection Tracking feature actions
+ */
+ public static NiriraConnTrackTreatmentBuilder niciraConnTrackTreatmentBuilder(DriverService ds, DeviceId id) {
+ return new NiriraConnTrackTreatmentBuilder(ds, id);
+ }
+
+ /**
+ * Returns tunnel destination extension treatment object.
+ *
+ * @param deviceService driver service
+ * @param deviceId device id to apply this treatment
+ * @param remoteIp tunnel destination ip address
+ * @return extension treatment
+ */
+ public static ExtensionTreatment buildExtension(DeviceService deviceService,
+ DeviceId deviceId,
+ Ip4Address remoteIp) {
+ Device device = deviceService.getDevice(deviceId);
+ if (device != null && !device.is(ExtensionTreatmentResolver.class)) {
+ log.error("The extension treatment is not supported");
+ return null;
+ }
+
+ if (device == null) {
+ return null;
+ }
+
+ ExtensionTreatmentResolver resolver = device.as(ExtensionTreatmentResolver.class);
+ ExtensionTreatment treatment = resolver.getExtensionInstruction(NICIRA_SET_TUNNEL_DST.type());
+ try {
+ treatment.setPropertyValue(TUNNEL_DST, remoteIp);
+ return treatment;
+ } catch (ExtensionPropertyException e) {
+ log.warn("Failed to get tunnelDst extension treatment for {}", deviceId);
+ return null;
+ }
+ }
+
+ /**
+ * Builds OVS ConnTrack matches.
+ *
+ * @param driverService driver service
+ * @param deviceId device ID
+ * @param ctState connection tracking sate masking value
+ * @param ctSateMask connection tracking sate masking value
+ * @return OVS ConnTrack extension match
+ */
+ public static ExtensionSelector buildCtExtensionSelector(DriverService driverService, DeviceId deviceId,
+ long ctState, long ctSateMask) {
+ DriverHandler handler = driverService.createHandler(deviceId);
+ ExtensionSelectorResolver esr = handler.behaviour(ExtensionSelectorResolver.class);
+
+ ExtensionSelector extensionSelector = esr.getExtensionSelector(
+ ExtensionSelectorType.ExtensionSelectorTypes.NICIRA_MATCH_CONNTRACK_STATE.type());
+ try {
+ extensionSelector.setPropertyValue(CT_STATE, ctState);
+ extensionSelector.setPropertyValue(CT_STATE_MASK, ctSateMask);
+ } catch (Exception e) {
+ log.error("Failed to set nicira match CT state");
+ return null;
+ }
+
+ return extensionSelector;
+ }
+
+ /**
+ * Computes ConnTack State flag values.
+ *
+ * @param isTracking true for +trk, false for -trk
+ * @param isNew true for +new, false for nothing
+ * @param isEstablished true for +est, false for nothing
+ * @return ConnTrack State flags
+ */
+ public static long computeCtStateFlag(boolean isTracking, boolean isNew, boolean isEstablished) {
+ long ctMaskFlag = 0x00;
+
+ if (isTracking) {
+ ctMaskFlag = ctMaskFlag | CT_STATE_TRK;
+ }
+
+ if (isNew) {
+ ctMaskFlag = ctMaskFlag | CT_STATE_TRK;
+ ctMaskFlag = ctMaskFlag | CT_STATE_NEW;
+ }
+
+ if (isEstablished) {
+ ctMaskFlag = ctMaskFlag | CT_STATE_TRK;
+ ctMaskFlag = ctMaskFlag | CT_STATE_EST;
+ }
+
+ return ctMaskFlag;
+ }
+
+ /**
+ * Computes ConnTrack State mask values.
+ *
+ * @param isTracking true for setting +trk/-trk value, false for otherwise
+ * @param isNew true for setting +new value, false for otherwise
+ * @param isEstablished true for setting +est value, false for otherwise
+ * @return ConnTrack State Mask value
+ */
+ public static long computeCtMaskFlag(boolean isTracking, boolean isNew, boolean isEstablished) {
+ long ctMaskFlag = 0x00;
+
+ if (isTracking) {
+ ctMaskFlag = ctMaskFlag | CT_STATE_TRK;
+ }
+
+ if (isNew) {
+ ctMaskFlag = ctMaskFlag | CT_STATE_TRK;
+ ctMaskFlag = ctMaskFlag | CT_STATE_NEW;
+ }
+
+ if (isEstablished) {
+ ctMaskFlag = ctMaskFlag | CT_STATE_TRK;
+ ctMaskFlag = ctMaskFlag | CT_STATE_EST;
+ }
+
+ return ctMaskFlag;
+ }
+
+ /**
+ * Builder class for OVS Connection Tracking feature actions.
+ */
+ public static final class NiriraConnTrackTreatmentBuilder {
+
+ private DriverService driverService;
+ private DeviceId deviceId;
+ private IpAddress natAddress = null;
+ private int zone;
+ private boolean commit;
+ private short table = -1;
+ private boolean natAction;
+
+
+ private NiriraConnTrackTreatmentBuilder(DriverService driverService, DeviceId deviceId) {
+ this.driverService = driverService;
+ this.deviceId = deviceId;
+ }
+
+ /**
+ * Sets commit flag.
+ *
+ * @param c true if commit, false if not.
+ * @return NiriraConnTrackTreatmentBuilder object
+ */
+ public NiriraConnTrackTreatmentBuilder commit(boolean c) {
+ this.commit = c;
+ return this;
+ }
+
+ /**
+ * Sets zone number.
+ *
+ * @param z zone number
+ * @return NiriraConnTrackTreatmentBuilder object
+ */
+ public NiriraConnTrackTreatmentBuilder zone(int z) {
+ this.zone = z;
+ return this;
+ }
+
+ /**
+ * Sets recirculation table number.
+ *
+ * @param t table number to restart
+ * @return NiriraConnTrackTreatmentBuilder object
+ */
+ public NiriraConnTrackTreatmentBuilder table(short t) {
+ this.table = t;
+ return this;
+ }
+
+ /**
+ * Sets IP address for NAT.
+ *
+ * @param ip NAT IP address
+ * @return NiriraConnTrackTreatmentBuilder object
+ */
+ public NiriraConnTrackTreatmentBuilder natIp(IpAddress ip) {
+ this.natAddress = ip;
+ return this;
+ }
+
+ /**
+ * Sets the flag for NAT action.
+ *
+ * @param nat nat action is included if true, no nat action otherwise
+ * @return NiriraConnTrackTreatmentBuilder object
+ */
+ public NiriraConnTrackTreatmentBuilder natAction(boolean nat) {
+ this.natAction = nat;
+ return this;
+ }
+
+ /**
+ * Builds extension treatment for OVS ConnTack and NAT feature.
+ *
+ * @return ExtensionTreatment object
+ */
+ public ExtensionTreatment build() {
+ DriverHandler handler = driverService.createHandler(deviceId);
+ ExtensionTreatmentResolver etr = handler.behaviour(ExtensionTreatmentResolver.class);
+
+ ExtensionTreatment natTreatment
+ = etr.getExtensionInstruction(ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_NAT.type());
+ try {
+ if (natAddress != null) {
+ natTreatment.setPropertyValue(CT_FLAGS, 1);
+ natTreatment.setPropertyValue(CT_PRESENT_FLAGS, buildPresentFlag(false, true));
+ natTreatment.setPropertyValue(CT_IPADDRESS_MIN, natAddress);
+ natTreatment.setPropertyValue(CT_IPADDRESS_MAX, natAddress);
+ } else {
+ natTreatment.setPropertyValue(CT_FLAGS, 0);
+ natTreatment.setPropertyValue(CT_PRESENT_FLAGS, 0);
+ }
+ } catch (Exception e) {
+ log.error("Failed to set NAT due to error : {}", e.getMessage());
+ return null;
+ }
+
+ ExtensionTreatment ctTreatment
+ = etr.getExtensionInstruction(ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_CT.type());
+ try {
+ List<ExtensionTreatment> nat = new ArrayList<>();
+ if (natAction) {
+ nat.add(natTreatment);
+ }
+ ctTreatment.setPropertyValue(CT_FLAGS, commit ? 1 : 0);
+ ctTreatment.setPropertyValue(CT_ZONE, zone);
+ ctTreatment.setPropertyValue(CT_TABLE, table > -1 ? table : 0xff);
+ ctTreatment.setPropertyValue("nestedActions", nat);
+ } catch (Exception e) {
+ log.error("Failed to set CT due to error : {}", e.getMessage());
+ return null;
+ }
+
+ return ctTreatment;
+ }
+
+ private int buildPresentFlag(boolean isPortPresent, boolean isAddressPresent) {
+
+ int presentFlag = 0;
+
+ if (isPortPresent) {
+ presentFlag = presentFlag | 1 << PORT_MIN_FLAG | 1 << PORT_MAX_FLAG;
+ }
+
+ if (isAddressPresent) {
+ presentFlag = 1 << ADDRESS_MIN_FLAG | 1 << ADDRESS_MAX_FLAG;
+ }
+
+ return presentFlag;
+ }
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/package-info.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/package-info.java
new file mode 100644
index 0000000..34ca730
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Implements OpenStack L3 service plugin, which routes packets between subnets,
+ * forwards packets from internal networks to external ones, and accesses instances
+ * from external networks through floating IPs.
+ */
+package org.onosproject.openstacknetworking.impl;
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackUtil.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackUtil.java
new file mode 100644
index 0000000..5fee7cc
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackUtil.java
@@ -0,0 +1,82 @@
+/*
+ * 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.openstacknetworking.util;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.openstack4j.core.transport.ObjectMapperSingleton;
+import org.openstack4j.model.ModelEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+
+import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
+
+/**
+ * An utility that used in openstack networking app.
+ */
+public final class OpenstackUtil {
+
+ protected static final Logger log = LoggerFactory.getLogger(OpenstackUtil.class);
+
+ /**
+ * Prevents object instantiation from external.
+ */
+ private OpenstackUtil() {
+ }
+
+ /**
+ * Interprets JSON string to corresponding openstack model entity object.
+ *
+ * @param input JSON string
+ * @param entityClazz openstack model entity class
+ * @return openstack model entity object
+ */
+ public static ModelEntity jsonToModelEntity(InputStream input, Class entityClazz) {
+ ObjectMapper mapper = new ObjectMapper();
+ try {
+ JsonNode jsonTree = mapper.enable(INDENT_OUTPUT).readTree(input);
+ log.trace(new ObjectMapper().writeValueAsString(jsonTree));
+ return ObjectMapperSingleton.getContext(entityClazz)
+ .readerFor(entityClazz)
+ .readValue(jsonTree);
+ } catch (Exception e) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Converts openstack model entity object into JSON object.
+ *
+ * @param entity openstack model entity object
+ * @param entityClazz openstack model entity class
+ * @return JSON object
+ */
+ public static ObjectNode modelEntityToJson(ModelEntity entity, Class entityClazz) {
+ ObjectMapper mapper = new ObjectMapper();
+ try {
+ String strModelEntity = ObjectMapperSingleton.getContext(entityClazz)
+ .writerFor(entityClazz)
+ .writeValueAsString(entity);
+ log.trace(strModelEntity);
+ return (ObjectNode) mapper.readTree(strModelEntity.getBytes());
+ } catch (Exception e) {
+ throw new IllegalStateException();
+ }
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/package-info.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/package-info.java
new file mode 100644
index 0000000..4b6cfb8
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/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.
+ */
+
+/**
+ * Openstack utility package.
+ */
+package org.onosproject.openstacknetworking.util;
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackFloatingIpWebResource.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackFloatingIpWebResource.java
new file mode 100644
index 0000000..d7ba4a9
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackFloatingIpWebResource.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.openstacknetworking.web;
+
+import org.onosproject.openstacknetworking.api.OpenstackRouterAdminService;
+import org.onosproject.rest.AbstractWebResource;
+import org.openstack4j.openstack.networking.domain.NeutronFloatingIP;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.InputStream;
+
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.noContent;
+import static javax.ws.rs.core.Response.status;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.jsonToModelEntity;
+
+/**
+ * Handles REST API call of Neutron L3 plugin.
+ */
+@Path("floatingips")
+public class OpenstackFloatingIpWebResource extends AbstractWebResource {
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final String MESSAGE = "Received floating IP %s request";
+ private static final String FLOATING_IPS = "floatingips";
+
+ private final OpenstackRouterAdminService adminService =
+ get(OpenstackRouterAdminService.class);
+
+ @Context
+ private UriInfo uriInfo;
+
+ /**
+ * Creates a floating IP from the JSON input stream.
+ *
+ * @param input floating ip JSON input stream
+ * @return 201 CREATED if the JSON is correct, 400 BAD_REQUEST if the JSON
+ * is invalid or duplicated floating ip already exists
+ * @onos.rsModel NeutronFloatingIp
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response createFloatingIp(InputStream input) {
+ log.trace(String.format(MESSAGE, "CREATE"));
+
+ final NeutronFloatingIP floatingIp = (NeutronFloatingIP)
+ jsonToModelEntity(input, NeutronFloatingIP.class);
+
+ adminService.createFloatingIp(floatingIp);
+
+ UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+ .path(FLOATING_IPS)
+ .path(floatingIp.getId());
+
+ return created(locationBuilder.build()).build();
+ }
+
+ /**
+ * Updates the floating IP with the specified identifier.
+ *
+ * @param id floating ip identifier
+ * @param input floating ip JSON input stream
+ * @return 200 OK with the updated floating ip, 400 BAD_REQUEST if the requested
+ * floating ip does not exist
+ * @onos.rsModel NeutronFloatingIp
+ */
+ @PUT
+ @Path("{id}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response updateFloatingIp(@PathParam("id") String id, InputStream input) {
+ log.trace(String.format(MESSAGE, "UPDATE " + id));
+
+ final NeutronFloatingIP floatingIp = (NeutronFloatingIP)
+ jsonToModelEntity(input, NeutronFloatingIP.class);
+
+ adminService.updateFloatingIp(floatingIp);
+
+ return status(Response.Status.OK).build();
+ }
+
+ /**
+ * Removes the floating IP.
+ *
+ * @param id floating ip identifier
+ * @return 204 NO_CONTENT, 400 BAD_REQUEST if the floating ip does not exist
+ */
+ @DELETE
+ @Path("{id}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response deleteFloatingIp(@PathParam("id") String id) {
+ log.trace(String.format(MESSAGE, "DELETE " + id));
+
+ adminService.removeFloatingIp(id);
+ return noContent().build();
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackNetworkWebResource.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackNetworkWebResource.java
new file mode 100644
index 0000000..25d239c
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackNetworkWebResource.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.web;
+
+import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
+import org.onosproject.rest.AbstractWebResource;
+import org.openstack4j.openstack.networking.domain.NeutronNetwork;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.InputStream;
+
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.noContent;
+import static javax.ws.rs.core.Response.status;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.jsonToModelEntity;
+
+/**
+ * Handles REST API call of Neutron ML2 plugin.
+ */
+@Path("networks")
+public class OpenstackNetworkWebResource extends AbstractWebResource {
+ protected final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final String MESSAGE = "Received networks %s request";
+ private static final String NETWORKS = "networks";
+
+ private final OpenstackNetworkAdminService adminService =
+ get(OpenstackNetworkAdminService.class);
+
+ @Context
+ private UriInfo uriInfo;
+
+ /**
+ * Creates a network from the JSON input stream.
+ *
+ * @param input network JSON input stream
+ * @return 201 CREATED if the JSON is correct, 400 BAD_REQUEST if the JSON
+ * is invalid or duplicated network already exists
+ * @onos.rsModel NeutronNetwork
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response createNetwork(InputStream input) {
+ log.trace(String.format(MESSAGE, "CREATE"));
+
+ final NeutronNetwork net = (NeutronNetwork)
+ jsonToModelEntity(input, NeutronNetwork.class);
+
+ adminService.createNetwork(net);
+
+ UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+ .path(NETWORKS)
+ .path(net.getId());
+
+ return created(locationBuilder.build()).build();
+ }
+
+ /**
+ * Updates the network with the specified identifier.
+ *
+ * @param id network identifier
+ * @param input network JSON input stream
+ * @return 200 OK with the updated network, 400 BAD_REQUEST if the requested
+ * network does not exist
+ * @onos.rsModel NeutronNetwork
+ */
+ @PUT
+ @Path("{id}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response updateNetwork(@PathParam("id") String id, InputStream input) {
+ log.trace(String.format(MESSAGE, "UPDATE " + id));
+
+ final NeutronNetwork net = (NeutronNetwork)
+ jsonToModelEntity(input, NeutronNetwork.class);
+
+ adminService.updateNetwork(net);
+
+ return status(Response.Status.OK).build();
+ }
+
+ /**
+ * Removes the service network.
+ *
+ * @param id network identifier
+ * @return 204 NO_CONTENT, 400 BAD_REQUEST if the network does not exist
+ */
+ @DELETE
+ @Path("{id}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response deleteNetwork(@PathParam("id") String id) {
+ log.trace(String.format(MESSAGE, "DELETE " + id));
+
+ adminService.removeNetwork(id);
+ return noContent().build();
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackNetworkingWebApplication.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackNetworkingWebApplication.java
new file mode 100644
index 0000000..8f9e623
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackNetworkingWebApplication.java
@@ -0,0 +1,38 @@
+/*
+ * 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.openstacknetworking.web;
+
+import org.onlab.rest.AbstractWebApplication;
+
+import java.util.Set;
+
+/**
+ * Openstack networking REST APIs web application.
+ */
+public class OpenstackNetworkingWebApplication extends AbstractWebApplication {
+ @Override
+ public Set<Class<?>> getClasses() {
+ return getClasses(
+ OpenstackFloatingIpWebResource.class,
+ OpenstackNetworkWebResource.class,
+ OpenstackPortWebResource.class,
+ OpenstackRouterWebResource.class,
+ OpenstackSecurityGroupRuleWebResource.class,
+ OpenstackSecurityGroupWebResource.class,
+ OpenstackSubnetWebResource.class
+ );
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackPortWebResource.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackPortWebResource.java
new file mode 100644
index 0000000..730eb46
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackPortWebResource.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.web;
+
+import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
+import org.onosproject.rest.AbstractWebResource;
+import org.openstack4j.openstack.networking.domain.NeutronPort;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.InputStream;
+
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.noContent;
+import static javax.ws.rs.core.Response.status;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.jsonToModelEntity;
+
+/**
+ * Handles Rest API call from Neutron ML2 plugin.
+ */
+@Path("ports")
+public class OpenstackPortWebResource extends AbstractWebResource {
+ protected final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final String MESSAGE = "Received ports %s request";
+ private static final String PORTS = "ports";
+
+ private final OpenstackNetworkAdminService adminService =
+ get(OpenstackNetworkAdminService.class);
+
+ @Context
+ private UriInfo uriInfo;
+
+ /**
+ * Creates a port from the JSON input stream.
+ *
+ * @param input port JSON input stream
+ * @return 201 CREATED if the JSON is correct, 400 BAD_REQUEST if the JSON
+ * is invalid or duplicated port already exists
+ * @onos.rsModel NeutronPort
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response createPorts(InputStream input) {
+ log.trace(String.format(MESSAGE, "CREATE"));
+
+ final NeutronPort port = (NeutronPort)
+ jsonToModelEntity(input, NeutronPort.class);
+
+ adminService.createPort(port);
+ UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+ .path(PORTS)
+ .path(port.getId());
+
+ return created(locationBuilder.build()).build();
+ }
+
+ /**
+ * Updates the port with the specified identifier.
+ *
+ * @param id port identifier
+ * @param input port JSON input stream
+ * @return 200 OK with the updated port, 400 BAD_REQUEST if the requested
+ * port does not exist
+ * @onos.rsModel NeutronPort
+ */
+ @PUT
+ @Path("{id}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response updatePort(@PathParam("id") String id, InputStream input) {
+ log.trace(String.format(MESSAGE, "UPDATE " + id));
+
+ final NeutronPort port = (NeutronPort)
+ jsonToModelEntity(input, NeutronPort.class);
+
+ adminService.updatePort(port);
+
+ return status(Response.Status.OK).build();
+ }
+
+ /**
+ * Removes the port with the given id.
+ *
+ * @param id port identifier
+ * @return 204 NO_CONTENT, 400 BAD_REQUEST if the port does not exist
+ */
+ @DELETE
+ @Path("{id}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response deletePorts(@PathParam("id") String id) {
+ log.trace(String.format(MESSAGE, "DELETE " + id));
+
+ adminService.removePort(id);
+ return noContent().build();
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackRouterWebResource.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackRouterWebResource.java
new file mode 100644
index 0000000..8326f40
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackRouterWebResource.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.web;
+
+import org.onosproject.openstacknetworking.api.OpenstackRouterAdminService;
+import org.onosproject.rest.AbstractWebResource;
+import org.openstack4j.openstack.networking.domain.NeutronRouter;
+import org.openstack4j.openstack.networking.domain.NeutronRouterInterface;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.InputStream;
+
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.noContent;
+import static javax.ws.rs.core.Response.status;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.jsonToModelEntity;
+
+/**
+ * Handles REST API call of Neturon L3 plugin.
+ */
+
+@Path("routers")
+public class OpenstackRouterWebResource extends AbstractWebResource {
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final String MESSAGE_ROUTER = "Received router %s request";
+ private static final String MESSAGE_ROUTER_IFACE = "Received router interface %s request";
+ private static final String ROUTERS = "routers";
+
+ private final OpenstackRouterAdminService adminService =
+ get(OpenstackRouterAdminService.class);
+
+ @Context
+ private UriInfo uriInfo;
+
+ /**
+ * Creates a router from the JSON input stream.
+ *
+ * @param input router JSON input stream
+ * @return 201 CREATED if the JSON is correct, 400 BAD_REQUEST if the JSON
+ * is invalid or duplicated router already exists
+ * @onos.rsModel NeutronRouter
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response createRouter(InputStream input) {
+ log.trace(String.format(MESSAGE_ROUTER, "CREATE"));
+
+ final NeutronRouter osRouter = (NeutronRouter)
+ jsonToModelEntity(input, NeutronRouter.class);
+
+ adminService.createRouter(osRouter);
+
+ UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+ .path(ROUTERS)
+ .path(osRouter.getId());
+
+ return created(locationBuilder.build()).build();
+ }
+
+ /**
+ * Updates the router with the specified identifier.
+ *
+ * @param id router identifier
+ * @param input router JSON input stream
+ * @return 200 OK with the updated router, 400 BAD_REQUEST if the requested
+ * router does not exist
+ * @onos.rsModel NeutronRouter
+ */
+ @PUT
+ @Path("{id}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response updateRouter(@PathParam("id") String id, InputStream input) {
+ log.trace(String.format(MESSAGE_ROUTER, "UPDATE " + id));
+
+ final NeutronRouter osRouter = (NeutronRouter)
+ jsonToModelEntity(input, NeutronRouter.class);
+
+ osRouter.setId(id);
+ adminService.updateRouter(osRouter);
+
+ return status(Response.Status.OK).build();
+ }
+
+ /**
+ * Updates the router with the specified router interface.
+ *
+ * @param id router identifier
+ * @param input router interface JSON input stream
+ * @return 200 OK with the updated router interface, 400 BAD_REQUEST if the
+ * requested router does not exist
+ * @onos.rsModel NeutronRouterInterface
+ */
+ @PUT
+ @Path("{id}/add_router_interface")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response addRouterInterface(@PathParam("id") String id, InputStream input) {
+ log.trace(String.format(MESSAGE_ROUTER_IFACE, "UPDATE " + id));
+
+ final NeutronRouterInterface osRouterIface = (NeutronRouterInterface)
+ jsonToModelEntity(input, NeutronRouterInterface.class);
+
+ adminService.addRouterInterface(osRouterIface);
+
+ return status(Response.Status.OK).build();
+ }
+
+ /**
+ * Updates the router with the specified router interface.
+ *
+ * @param id router identifier
+ * @param input router interface JSON input stream
+ * @return 200 OK with the updated router interface, 400 BAD_REQUEST if the
+ * requested router does not exist
+ * @onos.rsModel NeutronRouterInterface
+ */
+ @PUT
+ @Path("{id}/remove_router_interface")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response deleteRouterInterface(@PathParam("id") String id, InputStream input) {
+ log.trace(String.format(MESSAGE_ROUTER_IFACE, "DELETE " + id));
+
+ final NeutronRouterInterface osRouterIface = (NeutronRouterInterface)
+ jsonToModelEntity(input, NeutronRouterInterface.class);
+
+ adminService.removeRouterInterface(osRouterIface.getPortId());
+
+ return status(Response.Status.OK).build();
+ }
+
+ /**
+ * Removes the router.
+ *
+ * @param id router identifier
+ * @return 204 NO_CONTENT, 400 BAD_REQUEST if the router does not exist
+ */
+ @DELETE
+ @Path("{id}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response deleteRouter(@PathParam("id") String id) {
+ log.trace(String.format(MESSAGE_ROUTER, "DELETE " + id));
+
+ adminService.removeRouter(id);
+ return noContent().build();
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackSecurityGroupRuleWebResource.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackSecurityGroupRuleWebResource.java
new file mode 100644
index 0000000..dc12860
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackSecurityGroupRuleWebResource.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.web;
+
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupAdminService;
+import org.onosproject.rest.AbstractWebResource;
+import org.openstack4j.openstack.networking.domain.NeutronSecurityGroupRule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.InputStream;
+
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.noContent;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.jsonToModelEntity;
+
+/**
+ * Handles Security Group Rule Rest API call from Neutron ML2 plugin.
+ */
+@Path("security-group-rules")
+public class OpenstackSecurityGroupRuleWebResource extends AbstractWebResource {
+ protected final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final String MESSAGE = "Received security group rules %s request";
+ private static final String SECURITY_GROUP_RULES = "security-group-rules";
+
+ private final OpenstackSecurityGroupAdminService adminService =
+ get(OpenstackSecurityGroupAdminService.class);
+
+ @Context
+ private UriInfo uriInfo;
+
+ /**
+ * Creates a security group from the JSON input stream.
+ *
+ * @param input security group JSON input stream
+ * @return 201 CREATED if the JSON is correct, 400 BAD_REQUEST if the JSON
+ * is invalid or duplicated security group rule ID already exists
+ * @onos.rsModel NeutronSecurityGroupRule
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response createSecurityGroupRules(InputStream input) {
+ log.trace(String.format(MESSAGE, "CREATE"));
+
+ final NeutronSecurityGroupRule sgRule = (NeutronSecurityGroupRule)
+ jsonToModelEntity(input, NeutronSecurityGroupRule.class);
+
+ adminService.createSecurityGroupRule(sgRule);
+ UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+ .path(SECURITY_GROUP_RULES)
+ .path(sgRule.getId());
+
+ return created(locationBuilder.build()).build();
+ }
+
+ /**
+ * Removes the security group rule.
+ *
+ * @param id security group rule ID
+ * @return 204 NO_CONTENT
+ */
+ @DELETE
+ @Path("{id}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response deleteSecurityGroupRule(@PathParam("id") String id) {
+ log.trace(String.format(MESSAGE, "REMOVE " + id));
+
+ adminService.removeSecurityGroupRule(id);
+ return noContent().build();
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackSecurityGroupWebResource.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackSecurityGroupWebResource.java
new file mode 100644
index 0000000..da335a3
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackSecurityGroupWebResource.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.web;
+
+import org.onosproject.openstacknetworking.api.OpenstackSecurityGroupAdminService;
+import org.onosproject.rest.AbstractWebResource;
+import org.openstack4j.openstack.networking.domain.NeutronSecurityGroup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.InputStream;
+
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.noContent;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.jsonToModelEntity;
+
+/**
+ * Handles Security Group Rest API call from Neutron ML2 plugin.
+ */
+@Path("security-groups")
+public class OpenstackSecurityGroupWebResource extends AbstractWebResource {
+ protected final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final String MESSAGE = "Received security groups %s request";
+ private static final String SECURITY_GROUPS = "security-groups";
+
+ private final OpenstackSecurityGroupAdminService adminService =
+ get(OpenstackSecurityGroupAdminService.class);
+
+ @Context
+ private UriInfo uriInfo;
+
+ /**
+ * Creates a security group from the JSON input stream.
+ *
+ * @param input security group JSON input stream
+ * @return 201 CREATED if the JSON is correct, 400 BAD_REQUEST if the JSON
+ * is invalid or duplicated security group ID already exists
+ * @onos.rsModel NeutronSecurityGroup
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response createSecurityGroups(InputStream input) {
+ log.trace(String.format(MESSAGE, "CREATE"));
+
+ final NeutronSecurityGroup sg = (NeutronSecurityGroup)
+ jsonToModelEntity(input, NeutronSecurityGroup.class);
+
+ adminService.createSecurityGroup(sg);
+ UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+ .path(SECURITY_GROUPS)
+ .path(sg.getId());
+
+ return created(locationBuilder.build()).build();
+ }
+
+ /**
+ * Updates a security group from the JSON input stream.
+ *
+ * @param input security group JSON input stream
+ * @return 200 OK with the updated security group, 400 BAD_REQUEST if the
+ * request is invalid
+ * @onos.rsModel NeutronSecurityGroup
+ */
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response updateSecurityGroups(InputStream input) {
+ log.trace(String.format(MESSAGE, "UPDATE"));
+
+ // Do nothing ...
+ // TODO: this interface will purged sooner or later...
+ return Response.ok().build();
+ }
+
+ /**
+ * Removes the security group.
+ *
+ * @param id security group ID
+ * @return 204 NO_CONTENT
+ */
+ @DELETE
+ @Path("{id}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response removeSecurityGroup(@PathParam("id") String id) {
+ log.trace(String.format(MESSAGE, "REMOVE " + id));
+
+ adminService.removeSecurityGroup(id);
+ return noContent().build();
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackSubnetWebResource.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackSubnetWebResource.java
new file mode 100644
index 0000000..0c46bfc
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/OpenstackSubnetWebResource.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.web;
+
+/**
+ * Handles Rest API call from Neutron ML2 plugin.
+ */
+
+import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
+import org.onosproject.rest.AbstractWebResource;
+import org.openstack4j.openstack.networking.domain.NeutronSubnet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.InputStream;
+
+import static javax.ws.rs.core.Response.created;
+import static javax.ws.rs.core.Response.noContent;
+import static javax.ws.rs.core.Response.status;
+import static org.onosproject.openstacknetworking.util.OpenstackUtil.jsonToModelEntity;
+
+@Path("subnets")
+public class OpenstackSubnetWebResource extends AbstractWebResource {
+ protected final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final String MESSAGE = "Received subnets %s request";
+ private static final String SUBNETS = "subnets";
+
+ private final OpenstackNetworkAdminService adminService =
+ get(OpenstackNetworkAdminService.class);
+
+ @Context
+ private UriInfo uriInfo;
+
+ /**
+ * Creates a subnet from the JSON input stream.
+ *
+ * @param input subnet JSON input stream
+ * @return 201 CREATED if the JSON is correct, 400 BAD_REQUEST if the JSON
+ * is invalid or duplicated subnet already exists
+ * @onos.rsModel NeutronSubnet
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response createSubnet(InputStream input) {
+ log.trace(String.format(MESSAGE, "CREATE"));
+
+ final NeutronSubnet subnet = (NeutronSubnet)
+ jsonToModelEntity(input, NeutronSubnet.class);
+
+ adminService.createSubnet(subnet);
+ UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+ .path(SUBNETS)
+ .path(subnet.getId());
+
+ // TODO fix networking-onos to send Network UPDATE when subnet created
+ return created(locationBuilder.build()).build();
+ }
+
+ /**
+ * Updates the subnet with the specified identifier.
+ *
+ * @param id subnet identifier
+ * @param input subnet JSON input stream
+ * @return 200 OK with the updated subnet, 400 BAD_REQUEST if the requested
+ * subnet does not exist
+ * @onos.rsModel NeutronSubnet
+ */
+ @PUT
+ @Path("{id}")
+ @Produces(MediaType.APPLICATION_JSON)
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response updateSubnet(@PathParam("id") String id, InputStream input) {
+ log.trace(String.format(MESSAGE, "UPDATE " + id));
+
+ final NeutronSubnet subnet = (NeutronSubnet)
+ jsonToModelEntity(input, NeutronSubnet.class);
+
+ adminService.updateSubnet(subnet);
+
+ return status(Response.Status.OK).build();
+ }
+
+ /**
+ * Removes the subnet.
+ *
+ * @param id subnet identifier
+ * @return 204 NO_CONTENT, 400 BAD_REQUEST if the subnet does not exist
+ */
+ @DELETE
+ @Path("{id}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response deleteSubnet(@PathParam("id") String id) {
+ log.trace(String.format(MESSAGE, "DELETE " + id));
+
+ adminService.removeSubnet(id);
+ return noContent().build();
+ }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/package-info.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/package-info.java
new file mode 100644
index 0000000..c8a0eb0
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/web/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * OpenStack networking implementation.
+ */
+package org.onosproject.openstacknetworking.web;
diff --git a/apps/openstacknetworking/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/openstacknetworking/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..d3884df
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,73 @@
+<!--
+~ 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.openstacknetworking.cli.OpenstackNetworkListCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.openstacknetworking.cli.OpenstackPortListCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.openstacknetworking.cli.OpenstackRouterListCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.openstacknetworking.cli.OpenstackFloatingIpListCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.openstacknetworking.cli.OpenstackPurgeStateCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.openstacknetworking.cli.OpenstackSyncStateCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.openstacknetworking.cli.OpenstackSecurityGroupListCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.openstacknetworking.cli.OpenstackPurgeRulesCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.openstacknetworking.cli.OpenstackSyncRulesCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.openstacknetworking.cli.ExternalPeerRouterListCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.openstacknetworking.cli.UpdateExternalPeerRouterCommand"/>
+ <completers>
+ <ref component-id="ipAddressCompleter"/>
+ <ref component-id="macAddressCompleter"/>
+ <ref component-id="vlanIdCompleter"/>
+ </completers>
+ </command>
+ <command>
+ <action class="org.onosproject.openstacknetworking.cli.UpdateExternalPeerRouterVlanCommand"/>
+ <completers>
+ <ref component-id="ipAddressCompleter"/>
+ </completers>
+ </command>
+ <command>
+ <action class="org.onosproject.openstacknetworking.cli.DeleteExternalPeerRouterCommand" />
+ <completers>
+ <ref component-id="ipAddressCompleter"/>
+ </completers>
+ </command>
+ </command-bundle>
+
+ <bean id="ipAddressCompleter" class="org.onosproject.openstacknetworking.cli.IpAddressCompleter"/>
+ <bean id="macAddressCompleter" class="org.onosproject.openstacknetworking.cli.MacAddressCompleter"/>
+ <bean id="vlanIdCompleter" class="org.onosproject.openstacknetworking.cli.VlanIdCompleter"/>
+</blueprint>
diff --git a/apps/openstacknetworking/app/src/main/resources/definitions/NeutronFloatingIp.json b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronFloatingIp.json
new file mode 100644
index 0000000..7976ffe
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronFloatingIp.json
@@ -0,0 +1,58 @@
+{
+ "type": "object",
+ "required": [
+ "floatingip"
+ ],
+ "properties": {
+ "floatingip": {
+ "type": "object",
+ "description": "A floatingip object.",
+ "required": [
+ "id",
+ "router_id",
+ "tenant_id",
+ "floating_network_id",
+ "floating_ip_address",
+ "fixed_ip_address",
+ "port_id"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "example": "2f245a7b-796b-4f26-9cf9-9e82d248fda7",
+ "description": "The ID of the floating IP address."
+ },
+ "router_id": {
+ "type": "string",
+ "example": "d23abc8d-2991-4a55-ba98-2aaea84cc72",
+ "description": "The ID of the router for the floating IP."
+ },
+ "tenant_id": {
+ "type": "string",
+ "example": "4969c491a3c74ee4af974e6d800c62de",
+ "description": "The ID of the project."
+ },
+ "floating_network_id": {
+ "type": "string",
+ "example": "376da547-b977-4cfe-9cba-275c80debf57",
+ "description": "The ID of the network associated with the floating IP."
+ },
+ "floating_ip_address": {
+ "type": "string",
+ "example": "172.24.4.228",
+ "description": "The floating IP address."
+ },
+ "fixed_ip_address": {
+ "type": "string",
+ "example": "10.0.0.3",
+ "description": "The fixed IP address that is associated with the floating IP address."
+ },
+ "port_id": {
+ "type": "string",
+ "example": "ce705c24-c1ef-408a-bda3-7bbd946164ab",
+ "description": "The ID of a port associated with the floating IP."
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/resources/definitions/NeutronNetwork.json b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronNetwork.json
new file mode 100644
index 0000000..ecc86d8
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronNetwork.json
@@ -0,0 +1,86 @@
+
+{
+ "type": "object",
+ "required": [
+ "network"
+ ],
+ "properties": {
+ "network": {
+ "type": "object",
+ "description": "A network object.",
+ "required": [
+ "id",
+ "subnets",
+ "provider:physical_network",
+ "admin_state_up",
+ "tenant_id",
+ "provider:network_type",
+ "router:external",
+ "provider:segmentation_id",
+ "availability_zone_hints",
+ "availability_zones"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "example": "396f12f8-521e-4b91-8e21-2e003500433a",
+ "description": "The ID of the network."
+ },
+ "subnets": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "example": "10.10.0.0/24",
+ "description": "The associated subnets."
+ }
+ },
+ "provider:physical_network": {
+ "type": "string",
+ "example": "physnet1",
+ "description": "The physical network where this network is implemented."
+ },
+ "admin_state_up": {
+ "type": "boolean",
+ "example": true,
+ "description": "The administrative state of the network, which is up (true) or down (false)."
+ },
+ "tenant_id": {
+ "type": "string",
+ "example": "20bd52ff3e1b40039c312395b04683cf",
+ "description": "The ID of the project."
+ },
+ "provider:network_type": {
+ "type": "string",
+ "example": "vlan",
+ "description": "The type of physical network that this network is mapped to."
+ },
+ "router:external": {
+ "type": "boolean",
+ "example": true,
+ "description": "Indicates whether the network has an external routing facility that’s not managed by the networking service."
+ },
+ "provider:segmentation_id": {
+ "type": "string",
+ "example": "1002",
+ "description": "The ID of the isolated segment on the physical network."
+ },
+ "availability_zone_hints": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "example": "1",
+ "description": "The availability zone candidate for the network."
+ }
+ },
+ "availability_zones": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "example": "nova",
+ "description": "The availability zone for the network."
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/resources/definitions/NeutronPort.json b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronPort.json
new file mode 100644
index 0000000..5a833bf
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronPort.json
@@ -0,0 +1,190 @@
+{
+ "type": "object",
+ "required": [
+ "port"
+ ],
+ "properties": {
+ "port": {
+ "type": "object",
+ "description": "A port object.",
+ "required": [
+ "id",
+ "admin_state_up",
+ "device_id",
+ "device_owner",
+ "fixed_ips",
+ "allowed_address_pairs",
+ "mac_address",
+ "network_id",
+ "state",
+ "tenant_id",
+ "security_groups",
+ "extra_dhcp_opts",
+ "port_security_enabled",
+ "binding:host_id",
+ "binding:vif_type",
+ "binding:vif_details",
+ "binding:vnic_type",
+ "binding:profile"
+
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "example": "65c0ee9f-d634-4522-8954-51021b570b0d",
+ "description": "The ID of the resource."
+ },
+ "admin_state_up": {
+ "type": "boolean",
+ "example": true,
+ "description": "The administrative state of the resource, which is up (true) or down (false)."
+ },
+ "device_id": {
+ "type": "string",
+ "example": "1",
+ "description": "The ID of the device that uses this port. For example, a server instance or a logical router."
+ },
+ "device_owner": {
+ "type": "string",
+ "example": "compute:nova",
+ "description": "The entity type that uses this port. For example, compute:nova (server instance)."
+ },
+ "fixed_ips": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "title": "fixed_ips",
+ "description": "The IP addresses for the port. If the port has multiple IP addresses, this field has multiple entries.",
+ "required": [
+ "ip_address",
+ "subnet_id"
+ ],
+ "properties": {
+ "ip_address": {
+ "type": "string",
+ "example": "10.0.0.2",
+ "description": "The IP address of the port."
+ },
+ "subnet_id": {
+ "type": "string",
+ "example": "a0304c3a-4f08-4c43-88af-d796509c97d2",
+ "description": "The ID of the subnet."
+ }
+ }
+ }
+ },
+ "allowed_address_pairs": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "title": "allowed_address_pairs",
+ "description": "A set of zero or more allowed address pair objects each where address pair object contains an ip_address and mac_address.",
+ "required": [
+ "ip_address",
+ "mac_address"
+ ],
+ "properties": {
+ "ip_address": {
+ "type": "string",
+ "example": "12.12.11.12",
+ "description": "The IP address of the port."
+ },
+ "mac_address": {
+ "type": "string",
+ "example": "fa:14:2a:b3:cb:f0",
+ "description": "The MAC address of the port."
+ }
+ }
+ }
+ },
+ "mac_address": {
+ "type": "string",
+ "example": "fa:16:3e:c9:cb:f0",
+ "description": "The MAC address of the port."
+ },
+ "network_id": {
+ "type": "string",
+ "example": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
+ "description": "The ID of the attached network."
+ },
+ "status": {
+ "type": "string",
+ "example": "ACTIVE",
+ "description": "The port status. Values are ACTIVE, DOWN, BUILD and ERROR."
+ },
+ "tenant_id": {
+ "type": "string",
+ "example": "d6700c0c9ffa4f1cb322cd4a1f3906fa",
+ "description": "The ID of the project."
+ },
+ "security_groups": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "example": "f0ac4394-7e4a-4409-9701-ba8be283dbc3",
+ "description": "The IDs of security groups applied to the port."
+ }
+ },
+ "extra_dhcp_opts": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "title": "extra_dhcp_opts",
+ "description": "A set of zero or more extra DHCP option pairs. An option pair consists of an option value and name.",
+ "required": [
+ "opt_value",
+ "opt_name"
+ ],
+ "properties": {
+ "opt_value": {
+ "type": "string",
+ "example": "pxelinux.0",
+ "description": "A value of option pair."
+ },
+ "opt_name": {
+ "type": "string",
+ "example": "bootfile-name",
+ "description": "A name of option pair."
+ }
+ }
+ }
+ },
+ "port_security_enabled": {
+ "type": "boolean",
+ "example": true,
+ "description": "The port security status. A valid value is enabled (true) or disabled (false)."
+ },
+ "binding:host_id": {
+ "type": "string",
+ "example": "4df8d9ff-6f6f-438f-90a1-ef660d4586ad",
+ "description": "The ID of the host where the port resides."
+ },
+ "binding:vif_type": {
+ "type": "string",
+ "example": "unbound",
+ "description": "The type of which mechanism is used for the port."
+ },
+ "binding:vif_details": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string",
+ "example": "",
+ "description": "A dictionary which contains additional information on the port."
+ }
+ },
+ "binding:vnic_type": {
+ "type": "string",
+ "example": "other",
+ "description": "The type of vNIC which this port should be attached to. This is used to determine which mechanism driver(s) to be used to bind the port."
+ },
+ "binding:profile": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string",
+ "description": "A dictionary that enables the application running on the specific host to pass and receive vif port information specific to the networking back-end."
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/resources/definitions/NeutronRouter.json b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronRouter.json
new file mode 100644
index 0000000..47d0f41
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronRouter.json
@@ -0,0 +1,96 @@
+{
+ "type": "object",
+ "required": [
+ "router"
+ ],
+ "properties": {
+ "router": {
+ "type": "object",
+ "description": "A router object.",
+ "required": [
+ "id",
+ "name",
+ "status",
+ "external_gateway_info",
+ "admin_state_up",
+ "tenant_id",
+ "routes",
+ "distributed"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "example": "f49a1319-423a-4ee6-ba54-1d95a4f6cc68",
+ "description": "The ID of the router."
+ },
+ "name": {
+ "type": "string",
+ "example": "router1",
+ "description": "Human-readable name of the resource."
+ },
+ "status": {
+ "type": "string",
+ "example": "ACTIVE",
+ "description": "The router status."
+ },
+ "external_gateway_info": {
+ "type": "object",
+ "description": "The external gateway information of the router.",
+ "required": [
+ "network_id",
+ "enable_snat"
+ ],
+ "properties": {
+ "network_id": {
+ "type": "string",
+ "example": "a87cc70a-3e15-4acf-8205-9b711a3531b7",
+ "description": "Network ID which the router gateway is connected to."
+ },
+ "enable_snat": {
+ "type": "boolean",
+ "example": false,
+ "description": "Enable Source NAT (SNAT) attribute. true means Network Address Translation (NAT) is enabled."
+ }
+ }
+ },
+ "admin_state_up": {
+ "type": "boolean",
+ "example": true,
+ "description": "The administrative state of the resource, which is up (true) or down (false)."
+ },
+ "tenant_id": {
+ "type": "string",
+ "example": "d6700c0c9ffa4f1cb322cd4a1f3906fa",
+ "description": "The ID of the project."
+ },
+ "routes": {
+ "type": "array",
+ "description": "The extra routes configuration for L3 router. A list of dictionaries with destination and nexthop parameters.",
+ "items": {
+ "type": "object",
+ "title": "route",
+ "required": [
+ "destination",
+ "nexthop"
+ ],
+ "properties": {
+ "destination": {
+ "type": "string",
+ "description": "The destination CIDR."
+ },
+ "nexthop": {
+ "type": "string",
+ "description": "The IP address of the next hop for the corresponding destination."
+ }
+ }
+ }
+ },
+ "distributed": {
+ "type": "boolean",
+ "example": false,
+ "description": "true indicates a distributed router. It is available when dvr extension is enabled."
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/resources/definitions/NeutronRouterInterface.json b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronRouterInterface.json
new file mode 100644
index 0000000..59ae058
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronRouterInterface.json
@@ -0,0 +1,32 @@
+{
+ "type": "object",
+ "description": "A router interface object.",
+ "required": [
+ "id",
+ "tenant_id",
+ "subnet_id",
+ "port_id"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "example": "f49a1319-423a-4ee6-ba54-1d95a4f6cc68",
+ "description": "The ID of the router interface."
+ },
+ "tenant_id": {
+ "type": "string",
+ "example": "d6700c0c9ffa4f1cb322cd4a1f3906fa",
+ "description": "The ID of the project."
+ },
+ "subnet_id": {
+ "type": "string",
+ "example": "a0304c3a-4f08-4c43-88af-d796509c97d2",
+ "description": "The subnet ID on which you want to create the floating IP."
+ },
+ "port_id": {
+ "type": "string",
+ "example": "ce705c24-c1ef-408a-bda3-7bbd946164ab",
+ "description": "The ID of a port associated with the floating IP."
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/resources/definitions/NeutronSecurityGroup.json b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronSecurityGroup.json
new file mode 100644
index 0000000..6859b59
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronSecurityGroup.json
@@ -0,0 +1,115 @@
+{
+ "type": "object",
+ "description": "A security_group object.",
+ "required": [
+ "security_group"
+ ],
+ "properties": {
+ "security_group": {
+ "type": "object",
+ "required": [
+ "id",
+ "tenant_id",
+ "description",
+ "name",
+ "security_group_rules"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "example": "2076db17-a522-4506-91de-c6dd8e837028",
+ "description": "The ID of the security group."
+ },
+ "tenant_id": {
+ "type": "string",
+ "example": "e4f50856753b4dc6afee5fa6b9b6c550",
+ "description": "The ID of the project."
+ },
+ "description": {
+ "type": "string",
+ "example": "security group for webservers",
+ "description": "A human-readable description for the resource."
+ },
+ "name": {
+ "type": "string",
+ "example": "new-webservers",
+ "description": "Human-readable name of the resource."
+ },
+ "security_group_rules": {
+ "type": "array",
+ "description": "A list of security_group_rule objects.",
+ "items": {
+ "type": "object",
+ "description": "A security group rule object.",
+ "required": [
+ "id",
+ "tenant_id",
+ "security_group_id",
+ "direction",
+ "ethertype",
+ "port_range_max",
+ "port_range_min",
+ "protocol",
+ "remote_ip_prefix",
+ "remote_group_id"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "example": "2bc0accf-312e-429a-956e-e4407625eb62",
+ "description": "The ID of this security group rule."
+ },
+ "tenant_id": {
+ "type": "string",
+ "example": "e4f50856753b4dc6afee5fa6b9b6c550",
+ "description": "The ID of the project."
+ },
+ "security_group_id": {
+ "type": "string",
+ "example": "a7734e61-b545-452d-a3cd-0189cbd9747a",
+ "description": "The security group ID to associate with this security group rule."
+ },
+ "direction": {
+ "type": "string",
+ "example": "ingress",
+ "description": "Ingress or egress, which is the direction in which the metering rule is applied."
+ },
+ "ethertype": {
+ "type": "string",
+ "example": "IPv4",
+ "description": "Must be IPv4 or IPv6, and addresses represented in CIDR must match the ingress or egress rules."
+ },
+ "port_range_max": {
+ "type": "integer",
+ "format": "int32",
+ "example": 80,
+ "description": "The maximum port number in the range that is matched by the security group rule."
+ },
+ "port_range_min": {
+ "type": "integer",
+ "format": "int32",
+ "example": 80,
+ "description": "The minimum port number in the range that is matched by the security group rule."
+ },
+ "protocol": {
+ "type": "string",
+ "example": "tcp",
+ "description": "The IP protocol can be represented by a string, an integer, or null."
+ },
+ "remote_ip_prefix": {
+ "type": "string",
+ "example": "",
+ "description": "The remote IP prefix to associate with this metering rule packet."
+ },
+ "remote_group_id": {
+ "type": "string",
+ "example": "85cc3048-abc3-43cc-89b3-377341426ac5",
+ "description": "The remote group UUID to associate with this security group rule."
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/resources/definitions/NeutronSecurityGroupRule.json b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronSecurityGroupRule.json
new file mode 100644
index 0000000..d68ea46
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronSecurityGroupRule.json
@@ -0,0 +1,78 @@
+{
+ "type": "object",
+ "description": "",
+ "required": [
+ "security_group_rule"
+ ],
+ "properties": {
+ "security_group_rule": {
+ "type": "object",
+ "required": [
+ "id",
+ "tenant_id",
+ "security_group_id",
+ "direction",
+ "ethertype",
+ "port_range_max",
+ "port_range_min",
+ "protocol",
+ "remote_ip_prefix",
+ "remote_group_id"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "example": "2bc0accf-312e-429a-956e-e4407625eb62",
+ "description": "The ID of this security group rule."
+ },
+ "tenant_id": {
+ "type": "string",
+ "example": "e4f50856753b4dc6afee5fa6b9b6c550",
+ "description": "The ID of the project."
+ },
+ "security_group_id": {
+ "type": "string",
+ "example": "a7734e61-b545-452d-a3cd-0189cbd9747a",
+ "description": "The security group ID to associate with this security group rule."
+ },
+ "direction": {
+ "type": "string",
+ "example": "ingress",
+ "description": "Ingress or egress, which is the direction in which the metering rule is applied."
+ },
+ "ethertype": {
+ "type": "string",
+ "example": "IPv4",
+ "description": "Must be IPv4 or IPv6, and addresses represented in CIDR must match the ingress or egress rules."
+ },
+ "port_range_max": {
+ "type": "integer",
+ "format": "int32",
+ "example": 80,
+ "description": "The maximum port number in the range that is matched by the security group rule."
+ },
+ "port_range_min": {
+ "type": "integer",
+ "format": "int32",
+ "example": 80,
+ "description": "The minimum port number in the range that is matched by the security group rule."
+ },
+ "protocol": {
+ "type": "string",
+ "example": "tcp",
+ "description": "The IP protocol can be represented by a string, an integer, or null."
+ },
+ "remote_ip_prefix": {
+ "type": "string",
+ "example": "",
+ "description": "The remote IP prefix to associate with this metering rule packet."
+ },
+ "remote_group_id": {
+ "type": "string",
+ "example": "85cc3048-abc3-43cc-89b3-377341426ac5",
+ "description": "The remote group UUID to associate with this security group rule."
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/resources/definitions/NeutronSubnet.json b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronSubnet.json
new file mode 100644
index 0000000..3a44ff4
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/definitions/NeutronSubnet.json
@@ -0,0 +1,114 @@
+{
+ "type": "object",
+ "description": "A subnet object.",
+ "required": [
+ "subnets"
+ ],
+ "properties": {
+ "subnets": {
+ "type": "object",
+ "required": [
+ "enable_dhcp",
+ "network_id",
+ "tenant_id",
+ "dns_nameservers",
+ "allocation_pools",
+ "host_routes",
+ "ip_version",
+ "gateway_ip",
+ "ipv6_address_mode",
+ "ipv6_ra_mode"
+ ],
+ "properties": {
+ "enable_dhcp": {
+ "type": "boolean",
+ "example": true,
+ "description": "Indicates whether dhcp is enabled or disabled for the subnet."
+ },
+ "network_id": {
+ "type": "string",
+ "example": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+ "description": "The ID of the network to which the subnet belongs."
+ },
+ "tenant_id": {
+ "type": "string",
+ "example": "4fd44f30292945e481c7b8a0c8908869",
+ "description": "The ID of the project."
+ },
+ "dns_nameservers": {
+ "type": "array",
+ "description": "List of dns name servers associated with the subnet.",
+ "items": {
+ "type": "string",
+ "description": "A dns name server instance."
+ }
+ },
+ "allocation_pools": {
+ "type": "array",
+ "description": "Allocation pools with start and end IP addresses for this subnet.",
+ "items": {
+ "type": "object",
+ "description": "An allocation pool.",
+ "required": [
+ "start",
+ "end"
+ ],
+ "properties": {
+ "start": {
+ "type": "string",
+ "example": "192.168.199.2",
+ "description": "A start IP address for this subnet."
+ },
+ "end": {
+ "type": "string",
+ "example": "10.10.10.254",
+ "description": "An end IP address for this subnet."
+ }
+ }
+ }
+ },
+ "host_routes": {
+ "type": "array",
+ "description": "Additional routes for the subnet. A list of dictionaries with destination and nexthop parameters.",
+ "items": {
+ "type": "object",
+ "description": "A route for the subnet.",
+ "required": [
+ "destination",
+ "nexthop"
+ ],
+ "properties": {
+ "destination": {
+ "type": "string",
+ "description": "The destination CIDR."
+ },
+ "nexthop": {
+ "type": "string",
+ "description": "The IP address of the next hop for the corresponding destination."
+ }
+ }
+ }
+ },
+ "ip_version": {
+ "type": "integer",
+ "format": "int32",
+ "example": 4,
+ "description": "The IP protocol version. Value is 4 or 6."
+ },
+ "gateway_ip": {
+ "type": "string",
+ "example": "192.168.199.1",
+ "description": "Gateway IP of this subnet. If the value is null that implies no gateway is associated with the subnet."
+ },
+ "ipv6_address_mode": {
+ "type": "string",
+ "description": "The IPv6 address modes specifies mechanisms for assigning IP addresses."
+ },
+ "ipv6_ra_mode": {
+ "type": "string",
+ "description": "The IPv6 router advertisement specifies whether the networking service should transmit ICMPv6 packets, for a subnet."
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/main/resources/deps/btf-1.2.jar b/apps/openstacknetworking/app/src/main/resources/deps/btf-1.2.jar
new file mode 100644
index 0000000..bbeee88
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/deps/btf-1.2.jar
Binary files differ
diff --git a/apps/openstacknetworking/app/src/main/resources/deps/jackson-coreutils-1.6.jar b/apps/openstacknetworking/app/src/main/resources/deps/jackson-coreutils-1.6.jar
new file mode 100644
index 0000000..ee7b43d
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/deps/jackson-coreutils-1.6.jar
Binary files differ
diff --git a/apps/openstacknetworking/app/src/main/resources/deps/json-patch-1.9.jar b/apps/openstacknetworking/app/src/main/resources/deps/json-patch-1.9.jar
new file mode 100644
index 0000000..36569b6
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/deps/json-patch-1.9.jar
Binary files differ
diff --git a/apps/openstacknetworking/app/src/main/resources/deps/msg-simple-1.1.jar b/apps/openstacknetworking/app/src/main/resources/deps/msg-simple-1.1.jar
new file mode 100644
index 0000000..db74210
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/deps/msg-simple-1.1.jar
Binary files differ
diff --git a/apps/openstacknetworking/app/src/main/resources/deps/openstack4j-core-3.1.0.jar b/apps/openstacknetworking/app/src/main/resources/deps/openstack4j-core-3.1.0.jar
new file mode 100644
index 0000000..c03203f
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/deps/openstack4j-core-3.1.0.jar
Binary files differ
diff --git a/apps/openstacknetworking/app/src/main/resources/deps/openstack4j-http-connector-3.1.0.jar b/apps/openstacknetworking/app/src/main/resources/deps/openstack4j-http-connector-3.1.0.jar
new file mode 100644
index 0000000..cbe8476
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/deps/openstack4j-http-connector-3.1.0.jar
Binary files differ
diff --git a/apps/openstacknetworking/app/src/main/resources/deps/openstack4j-httpclient-3.1.0.jar b/apps/openstacknetworking/app/src/main/resources/deps/openstack4j-httpclient-3.1.0.jar
new file mode 100644
index 0000000..964b15a
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/deps/openstack4j-httpclient-3.1.0.jar
Binary files differ
diff --git a/apps/openstacknetworking/app/src/main/resources/deps/snakeyaml-1.15.jar b/apps/openstacknetworking/app/src/main/resources/deps/snakeyaml-1.15.jar
new file mode 100644
index 0000000..34084e3
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/resources/deps/snakeyaml-1.15.jar
Binary files differ
diff --git a/apps/openstacknetworking/app/src/main/webapp/WEB-INF/web.xml b/apps/openstacknetworking/app/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..9194183
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2016-present Open Networking Foundation
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<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>Openstack Switching REST API v1.0</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>
+ </auth-constraint>
+ </security-constraint>
+
+ <security-role>
+ <role-name>admin</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.openstacknetworking.web.OpenstackNetworkingWebApplication</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/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackNetworkManagerTest.java b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackNetworkManagerTest.java
new file mode 100644
index 0000000..866aa55
--- /dev/null
+++ b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackNetworkManagerTest.java
@@ -0,0 +1,678 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.util.concurrent.MoreExecutors;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onosproject.cluster.ClusterServiceAdapter;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.event.Event;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceServiceAdapter;
+import org.onosproject.net.packet.PacketServiceAdapter;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkEvent;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkListener;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeAdminService;
+import org.onosproject.openstacknode.api.OpenstackNodeListener;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.onosproject.store.service.TestStorageService;
+import org.openstack4j.model.network.Network;
+import org.openstack4j.model.network.Port;
+import org.openstack4j.model.network.Subnet;
+import org.openstack4j.openstack.networking.domain.NeutronNetwork;
+import org.openstack4j.openstack.networking.domain.NeutronPort;
+import org.openstack4j.openstack.networking.domain.NeutronSubnet;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_NETWORK_CREATED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_NETWORK_REMOVED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_NETWORK_UPDATED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_PORT_CREATED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_PORT_REMOVED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_PORT_UPDATED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_SUBNET_CREATED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_SUBNET_REMOVED;
+import static org.onosproject.openstacknetworking.api.OpenstackNetworkEvent.Type.OPENSTACK_SUBNET_UPDATED;
+import static org.onosproject.openstacknode.api.NodeState.COMPLETE;
+
+/**
+ * Unit tests for OpenStack network manager.
+ */
+public class OpenstackNetworkManagerTest {
+
+ private static final ApplicationId TEST_APP_ID = new DefaultApplicationId(1, "test");
+
+ private static final String UNKNOWN_ID = "unknown_id";
+ private static final String UPDATED_NAME = "updated_name";
+
+ private static final String NETWORK_ID = "network_id";
+ private static final String NETWORK_NAME = "network_name";
+ private static final Network NETWORK = NeutronNetwork.builder()
+ .name(NETWORK_NAME)
+ .build();
+ private static final Network NETWORK_COPY = NeutronNetwork.builder()
+ .name("network")
+ .build();
+
+ private static final String SUBNET_ID = "subnet_id";
+ private static final Subnet SUBNET = NeutronSubnet.builder()
+ .networkId(NETWORK_ID)
+ .cidr("192.168.0.0/24")
+ .build();
+ private static final Subnet SUBNET_COPY = NeutronSubnet.builder()
+ .networkId(NETWORK_ID)
+ .cidr("192.168.0.0/24")
+ .build();
+
+ private static final String PORT_ID = "port_id";
+ private static final Port PORT = NeutronPort.builder()
+ .networkId(NETWORK_ID)
+ .fixedIp("192.168.0.1", SUBNET_ID)
+ .build();
+ private static final Port PORT_COPY = NeutronPort.builder()
+ .networkId(NETWORK_ID)
+ .fixedIp("192.168.0.1", SUBNET_ID)
+ .build();
+
+ private final TestOpenstackNetworkListener testListener = new TestOpenstackNetworkListener();
+
+ private OpenstackNetworkManager target;
+ private DistributedOpenstackNetworkStore osNetworkStore;
+
+ @Before
+ public void setUp() throws Exception {
+ NETWORK.setId(NETWORK_ID);
+ NETWORK_COPY.setId(NETWORK_ID);
+ SUBNET.setId(SUBNET_ID);
+ SUBNET_COPY.setId(SUBNET_ID);
+ PORT.setId(PORT_ID);
+ PORT_COPY.setId(PORT_ID);
+
+ osNetworkStore = new DistributedOpenstackNetworkStore();
+ TestUtils.setField(osNetworkStore, "coreService", new TestCoreService());
+ TestUtils.setField(osNetworkStore, "storageService", new TestStorageService());
+ TestUtils.setField(osNetworkStore, "eventExecutor", MoreExecutors.newDirectExecutorService());
+ osNetworkStore.activate();
+
+ target = new OpenstackNetworkManager();
+ TestUtils.setField(target, "coreService", new TestCoreService());
+ TestUtils.setField(target, "packetService", new PacketServiceAdapter());
+ TestUtils.setField(target, "deviceService", new DeviceServiceAdapter());
+ TestUtils.setField(target, "storageService", new TestStorageService());
+ TestUtils.setField(target, "clusterService", new ClusterServiceAdapter());
+ TestUtils.setField(target, "openstacknodeService", new TestOpenstackNodeManager());
+ target.osNetworkStore = osNetworkStore;
+ target.addListener(testListener);
+ target.activate();
+ }
+
+ @After
+ public void tearDown() {
+ target.removeListener(testListener);
+ osNetworkStore.deactivate();
+ target.deactivate();
+ osNetworkStore = null;
+ target = null;
+ }
+
+ /**
+ * Tests if getting all networks returns the correct set of networks.
+ */
+ @Test
+ public void testGetNetworks() {
+ createBasicNetworks();
+ assertEquals("Number of network did not match", 1, target.networks().size());
+ }
+
+ /**
+ * Tests if getting a network with ID returns the correct network.
+ */
+ @Test
+ public void testGetNetworkById() {
+ createBasicNetworks();
+ assertTrue("Network did not match", target.network(NETWORK_ID) != null);
+ assertTrue("Network did not match", target.network(UNKNOWN_ID) == null);
+ }
+
+ /**
+ * Tests creating and removing a network, and checks if it triggers proper events.
+ */
+ @Test
+ public void testCreateAndRemoveNetwork() {
+ target.createNetwork(NETWORK);
+ assertEquals("Number of networks did not match", 1, target.networks().size());
+ assertTrue("Network was not created", target.network(NETWORK_ID) != null);
+
+ target.removeNetwork(NETWORK_ID);
+ assertEquals("Number of networks did not match", 0, target.networks().size());
+ assertTrue("Network was not removed", target.network(NETWORK_ID) == null);
+
+ validateEvents(OPENSTACK_NETWORK_CREATED, OPENSTACK_NETWORK_REMOVED);
+ }
+
+ /**
+ * Tests updating a network, and checks if it triggers proper events.
+ */
+ @Test
+ public void testCreateAndUpdateNetwork() {
+ target.createNetwork(NETWORK);
+ assertEquals("Number of networks did not match", 1, target.networks().size());
+ assertEquals("Network did not match", NETWORK_NAME, target.network(NETWORK_ID).getName());
+
+ final Network updated = NeutronNetwork.builder()
+ .from(NETWORK_COPY)
+ .name(UPDATED_NAME)
+ .build();
+ target.updateNetwork(updated);
+
+ assertEquals("Number of networks did not match", 1, target.networks().size());
+ assertEquals("Network did not match", UPDATED_NAME, target.network(NETWORK_ID).getName());
+ validateEvents(OPENSTACK_NETWORK_CREATED, OPENSTACK_NETWORK_UPDATED);
+ }
+
+ /**
+ * Tests if creating a null network fails with an exception.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testCreateNullNetwork() {
+ target.createNetwork(null);
+ }
+
+ /**
+ * Tests if creating a network with null ID fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateNetworkWithNullId() {
+ final Network testNet = NeutronNetwork.builder().build();
+ target.createNetwork(testNet);
+ }
+
+ /**
+ * Tests if creating a duplicate network fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateDuplicateNetwork() {
+ target.createNetwork(NETWORK);
+ target.createNetwork(NETWORK);
+ }
+
+ /**
+ * Tests if removing network with null ID fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testRemoveNetworkWithNull() {
+ target.removeNetwork(null);
+ }
+
+ /**
+ * Tests if updating a network with null name fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testUpdateNetworkWithNullName() {
+ final Network updated = NeutronNetwork.builder()
+ .name(null)
+ .build();
+ updated.setId(NETWORK_ID);
+ target.updateNetwork(updated);
+ }
+
+ /**
+ * Tests if updating an unregistered network fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testUpdateUnregisteredNetwork() {
+ target.updateNetwork(NETWORK);
+ }
+
+ /**
+ * Tests if updating a network with null ID fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testUpdateNetworkWithNullId() {
+ final Network testNet = NeutronNetwork.builder().build();
+ target.updateNetwork(testNet);
+ }
+
+
+ /**
+ * Tests if getting all subnets returns the correct set of subnets.
+ */
+ @Test
+ public void testGetSubnets() {
+ createBasicNetworks();
+ assertEquals("Number of subnet did not match", 1, target.subnets().size());
+ }
+
+ @Test
+ public void testGetSubnetsByNetworkId() {
+ createBasicNetworks();
+ assertEquals("Subnet did not match", 1, target.subnets(NETWORK_ID).size());
+ assertEquals("Subnet did not match", 0, target.subnets(UNKNOWN_ID).size());
+ }
+
+ /**
+ * Tests if getting a subnet with ID returns the correct subnet.
+ */
+ @Test
+ public void testGetSubnetById() {
+ createBasicNetworks();
+ assertTrue("Subnet did not match", target.subnet(SUBNET_ID) != null);
+ assertTrue("Subnet did not match", target.subnet(UNKNOWN_ID) == null);
+ }
+
+ /**
+ * Tests creating and removing a subnet, and checks if it triggers proper events.
+ */
+ @Test
+ public void testCreateAndRemoveSubnet() {
+ target.createSubnet(SUBNET);
+ assertEquals("Number of subnet did not match", 1, target.subnets().size());
+ assertTrue("Subnet was not created", target.subnet(SUBNET_ID) != null);
+
+ target.removeSubnet(SUBNET_ID);
+ assertEquals("Number of subnet did not match", 0, target.subnets().size());
+ assertTrue("Subnet was not removed", target.subnet(SUBNET_ID) == null);
+
+ validateEvents(OPENSTACK_SUBNET_CREATED, OPENSTACK_SUBNET_REMOVED);
+ }
+
+ /**
+ * Tests updating a subnet, and checks if it triggers proper events.
+ */
+ @Test
+ public void testCreateAndUpdateSubnet() {
+ target.createSubnet(SUBNET_COPY);
+ assertEquals("Number of subnet did not match", 1, target.subnets().size());
+ assertEquals("Subnet did not match", null, target.subnet(SUBNET_ID).getName());
+
+ // TODO fix NeutronSubnet.builder().from() in openstack4j
+ final Subnet updated = NeutronSubnet.builder()
+ .networkId(NETWORK_ID)
+ .cidr("192.168.0.0/24")
+ .name(UPDATED_NAME)
+ .build();
+ updated.setId(SUBNET_ID);
+ target.updateSubnet(updated);
+
+ assertEquals("Number of subnet did not match", 1, target.subnets().size());
+ assertEquals("Subnet did not match", UPDATED_NAME, target.subnet(SUBNET_ID).getName());
+
+ validateEvents(OPENSTACK_SUBNET_CREATED, OPENSTACK_SUBNET_UPDATED);
+ }
+
+ /**
+ * Tests if creating a null subnet fails with an exception.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testCreateNullSubnet() {
+ target.createSubnet(null);
+ }
+
+ /**
+ * Tests if creating a subnet with null ID fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateSubnetWithNullId() {
+ final Subnet testSubnet = NeutronSubnet.builder()
+ .networkId(NETWORK_ID)
+ .cidr("192.168.0.0/24")
+ .build();
+ target.createSubnet(testSubnet);
+ }
+
+ /**
+ * Tests if creating subnet with null network ID fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateSubnetWithNullNetworkId() {
+ final Subnet testSubnet = NeutronSubnet.builder()
+ .cidr("192.168.0.0/24")
+ .build();
+ testSubnet.setId(SUBNET_ID);
+ target.createSubnet(testSubnet);
+ }
+
+ /**
+ * Tests if creating a subnet with null CIDR fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateSubnetWithNullCidr() {
+ final Subnet testSubnet = NeutronSubnet.builder()
+ .networkId(NETWORK_ID)
+ .build();
+ testSubnet.setId(SUBNET_ID);
+ target.createSubnet(testSubnet);
+ }
+
+ /**
+ * Tests if creating a duplicate subnet fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateDuplicateSubnet() {
+ target.createSubnet(SUBNET);
+ target.createSubnet(SUBNET);
+ }
+
+ /**
+ * Tests if updating an unregistered subnet fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testUpdateUnregisteredSubnet() {
+ target.updateSubnet(SUBNET);
+ }
+
+ /**
+ * Tests if updating a null subnet fails with an exception.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testUpdateSubnetWithNull() {
+ target.updateSubnet(null);
+ }
+
+ /**
+ * Tests if updating a subnet with null ID fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testUpdateSubnetWithNullId() {
+ final Subnet testSubnet = NeutronSubnet.builder()
+ .networkId(NETWORK_ID)
+ .cidr("192.168.0.0/24")
+ .build();
+ target.updateSubnet(testSubnet);
+ }
+
+ /**
+ * Tests if updating a subnet with null network ID fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testUpdateSubnetWithNullNetworkId() {
+ final Subnet testSubnet = NeutronSubnet.builder()
+ .cidr("192.168.0.0/24")
+ .build();
+ testSubnet.setId(SUBNET_ID);
+ target.updateSubnet(testSubnet);
+ }
+
+ /**
+ * Tests if updating a subnet with null CIDR fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testUpdateSubnetWithNullCidr() {
+ final Subnet testSubnet = NeutronSubnet.builder()
+ .networkId(NETWORK_ID)
+ .build();
+ testSubnet.setId(SUBNET_ID);
+ target.updateSubnet(testSubnet);
+ }
+
+ /**
+ * Tests if getting all ports returns correct set of values.
+ */
+ @Test
+ public void testGetPorts() {
+ createBasicNetworks();
+ assertEquals("Number of port did not match", 1, target.ports().size());
+ }
+
+ /**
+ * Tests if getting a port with network ID returns correct set of values.
+ */
+ @Test
+ public void testGetPortsByNetworkId() {
+ createBasicNetworks();
+ assertEquals("Number of port did not match", 1, target.ports(NETWORK_ID).size());
+ assertEquals("Number of port did not match", 0, target.ports(UNKNOWN_ID).size());
+ }
+
+ /**
+ * Tests if getting a port with ID returns correct value.
+ */
+ @Test
+ public void testGetPortById() {
+ createBasicNetworks();
+ assertTrue("Port did not match", target.port(PORT_ID) != null);
+ assertTrue("Port did not match", target.port(UNKNOWN_ID) == null);
+ }
+
+ /**
+ * Tests creating and removing a port, and checks if proper event is triggered.
+ */
+ @Test
+ public void testCreateAndRemovePort() {
+ target.createPort(PORT);
+ assertEquals("Number of port did not match", 1, target.ports().size());
+ assertTrue("Port was not created", target.port(PORT_ID) != null);
+
+ target.removePort(PORT_ID);
+ assertEquals("Number of port did not match", 0, target.ports().size());
+ assertTrue("Port was not created", target.port(PORT_ID) == null);
+
+ validateEvents(OPENSTACK_PORT_CREATED, OPENSTACK_PORT_REMOVED);
+ }
+
+ /**
+ * Tests creating and updating a port, and checks if proper event is triggered.
+ */
+ @Test
+ public void testCreateAndUpdatePort() {
+ target.createPort(PORT);
+ assertEquals("Number of port did not match", 1, target.ports().size());
+ assertEquals("Port did not match", null, target.port(PORT_ID).getName());
+
+ final Port updated = PORT_COPY.toBuilder()
+ .name(UPDATED_NAME)
+ .build();
+ target.updatePort(updated);
+
+ assertEquals("Number of port did not match", 1, target.ports().size());
+ assertEquals("Port did not match", UPDATED_NAME, target.port(PORT_ID).getName());
+
+ validateEvents(OPENSTACK_PORT_CREATED, OPENSTACK_PORT_UPDATED);
+ }
+
+ /**
+ * Tests if creating a null port fails with an exception.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testCreateNullPort() {
+ target.createPort(null);
+ }
+
+ /**
+ * Tests if creating a port with null ID fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreatePortWithNullId() {
+ final Port testPort = NeutronPort.builder()
+ .networkId(NETWORK_ID)
+ .build();
+ target.createPort(testPort);
+ }
+
+ /**
+ * Tests if creating a port with null network ID fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreatePortWithNullNetworkId() {
+ final Port testPort = NeutronPort.builder().build();
+ testPort.setId(PORT_ID);
+ target.createPort(testPort);
+ }
+
+ /**
+ * Tests if creating a duplicate port fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void createDuplicatePort() {
+ target.createPort(PORT);
+ target.createPort(PORT);
+ }
+
+ /**
+ * Tests if updating an unregistered port fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testUpdateUnregisteredPort() {
+ target.updatePort(PORT);
+ }
+
+ /**
+ * Tests if updating a null port fails with an exception.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testUpdateNullPort() {
+ target.updatePort(null);
+ }
+
+ /**
+ * Tests if updating a port with null ID fails with exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testUpdatePortWithNullId() {
+ final Port testPort = NeutronPort.builder()
+ .networkId(NETWORK_ID)
+ .build();
+ target.updatePort(testPort);
+ }
+
+ /**
+ * Tests if updating a port with null network ID fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testUpdatePortWithNullNetworkId() {
+ final Port testPort = NeutronPort.builder().build();
+ testPort.setId(PORT_ID);
+ target.updatePort(testPort);
+ }
+
+ private void createBasicNetworks() {
+ target.createNetwork(NETWORK);
+ target.createSubnet(SUBNET);
+ target.createPort(PORT);
+ }
+
+ private static class TestCoreService extends CoreServiceAdapter {
+
+ @Override
+ public ApplicationId registerApplication(String name) {
+ return TEST_APP_ID;
+ }
+ }
+
+ private static class TestOpenstackNodeManager implements OpenstackNodeService, OpenstackNodeAdminService {
+ Map<String, OpenstackNode> osNodeMap = Maps.newHashMap();
+ List<OpenstackNodeListener> listeners = Lists.newArrayList();
+
+ @Override
+ public Set<OpenstackNode> nodes() {
+ return ImmutableSet.copyOf(osNodeMap.values());
+ }
+
+ @Override
+ public Set<OpenstackNode> nodes(OpenstackNode.NodeType type) {
+ return osNodeMap.values().stream()
+ .filter(osNode -> osNode.type() == type)
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set<OpenstackNode> completeNodes() {
+ return osNodeMap.values().stream()
+ .filter(osNode -> osNode.state() == COMPLETE)
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set<OpenstackNode> completeNodes(OpenstackNode.NodeType type) {
+ return osNodeMap.values().stream()
+ .filter(osNode -> osNode.type() == type && osNode.state() == COMPLETE)
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public OpenstackNode node(String hostname) {
+ return osNodeMap.get(hostname);
+ }
+
+ @Override
+ public OpenstackNode node(DeviceId deviceId) {
+ return osNodeMap.values().stream()
+ .filter(osNode -> Objects.equals(osNode.intgBridge(), deviceId) ||
+ Objects.equals(osNode.ovsdb(), deviceId))
+ .findFirst().orElse(null);
+ }
+
+ @Override
+ public void addListener(OpenstackNodeListener listener) {
+ listeners.add(listener);
+ }
+
+ @Override
+ public void removeListener(OpenstackNodeListener listener) {
+ listeners.remove(listener);
+ }
+
+ @Override
+ public void createNode(OpenstackNode osNode) {
+ osNodeMap.put(osNode.hostname(), osNode);
+ }
+
+ @Override
+ public void updateNode(OpenstackNode osNode) {
+ osNodeMap.put(osNode.hostname(), osNode);
+ }
+
+ @Override
+ public OpenstackNode removeNode(String hostname) {
+ return null;
+ }
+ }
+
+ private static class TestOpenstackNetworkListener implements OpenstackNetworkListener {
+ private List<OpenstackNetworkEvent> events = Lists.newArrayList();
+
+ @Override
+ public void event(OpenstackNetworkEvent event) {
+ events.add(event);
+ }
+ }
+
+ private void validateEvents(Enum... types) {
+ int i = 0;
+ assertEquals("Number of events did not match", types.length, testListener.events.size());
+ for (Event event : testListener.events) {
+ assertEquals("Incorrect event received", types[i], event.type());
+ i++;
+ }
+ testListener.events.clear();
+ }
+}
\ No newline at end of file
diff --git a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackRouterManagerTest.java b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackRouterManagerTest.java
new file mode 100644
index 0000000..b4afb75
--- /dev/null
+++ b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/OpenstackRouterManagerTest.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.openstacknetworking.impl;
+
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.MoreExecutors;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.event.Event;
+import org.onosproject.openstacknetworking.api.OpenstackRouterEvent;
+import org.onosproject.openstacknetworking.api.OpenstackRouterListener;
+import org.onosproject.store.service.TestStorageService;
+import org.openstack4j.model.network.Router;
+import org.openstack4j.openstack.networking.domain.NeutronRouter;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.openstacknetworking.api.OpenstackRouterEvent.Type.*;
+
+/**
+ * Unit tests for OpenStack router manager.
+ */
+public class OpenstackRouterManagerTest {
+
+ private static final ApplicationId TEST_APP_ID = new DefaultApplicationId(1, "test");
+
+ private static final String UNKNOWN_ID = "unknown_id";
+ private static final String ROUTER_ID = "router_1";
+ private static final String ROUTER_NAME = "router_1";
+ private static final Router ROUTER = NeutronRouter.builder()
+ .id(ROUTER_ID)
+ .name(ROUTER_NAME)
+ .build();
+
+ private final TestOpenstackRouterListener testListener = new TestOpenstackRouterListener();
+
+ private OpenstackRouterManager target;
+ private DistributedOpenstackRouterStore osRouterStore;
+
+ @Before
+ public void setUp() throws Exception {
+ osRouterStore = new DistributedOpenstackRouterStore();
+ TestUtils.setField(osRouterStore, "coreService", new TestCoreService());
+ TestUtils.setField(osRouterStore, "storageService", new TestStorageService());
+ TestUtils.setField(osRouterStore, "eventExecutor", MoreExecutors.newDirectExecutorService());
+ osRouterStore.activate();
+
+ target = new OpenstackRouterManager();
+ target.coreService = new TestCoreService();
+ target.osRouterStore = osRouterStore;
+ target.addListener(testListener);
+ target.activate();
+ }
+
+ @After
+ public void tearDown() {
+ target.removeListener(testListener);
+ osRouterStore.deactivate();
+ target.deactivate();
+ osRouterStore = null;
+ target = null;
+ }
+
+ /**
+ * Tests if getting all routers returns correct set of values.
+ */
+ @Test
+ public void testGetRouters() {
+ createBasicRouters();
+ assertEquals("Number of router did not match", 1, target.routers().size());
+ }
+
+ /**
+ * Tests if getting a router with ID returns correct value.
+ */
+ @Test
+ public void testGetRouterById() {
+ createBasicRouters();
+ assertTrue("Router did not exist", target.router(ROUTER_ID) != null);
+ assertTrue("Router did not exist", target.router(UNKNOWN_ID) == null);
+ }
+
+ /**
+ * Tests creating and removing a router, and checks if proper event is triggered.
+ */
+ @Test
+ public void testCreateAndRemoveRouter() {
+ target.createRouter(ROUTER);
+ assertEquals("Number of router did not match", 1, target.routers().size());
+ assertTrue("Router was not created", target.router(ROUTER_ID) != null);
+
+ target.removeRouter(ROUTER.getId());
+ assertEquals("Number of router did not match", 0, target.routers().size());
+ assertTrue("Router was not removed", target.router(ROUTER_ID) == null);
+
+ validateEvents(OPENSTACK_ROUTER_CREATED, OPENSTACK_ROUTER_REMOVED);
+ }
+
+ /**
+ * Tests creating and updating a router, and checks if proper event is triggered.
+ */
+ @Test
+ public void testCreateAndUpdateRouter() {
+ target.createRouter(ROUTER);
+ assertEquals("Number of router did not match", 1, target.routers().size());
+ assertEquals("Router did not match", ROUTER_NAME, target.router(ROUTER_ID).getName());
+
+ final Router updated = NeutronRouter.builder()
+ .id(ROUTER_ID)
+ .name("updated-name")
+ .build();
+
+ target.updateRouter(updated);
+ assertEquals("Number of router did not match", 1, target.routers().size());
+ assertEquals("Router did not match", "updated-name", target.router(ROUTER_ID).getName());
+
+ validateEvents(OPENSTACK_ROUTER_CREATED, OPENSTACK_ROUTER_UPDATED);
+ }
+
+ /**
+ * Tests adding and removing external gateway to a router, and checks if
+ * proper events are triggered.
+ */
+ @Test
+ public void testAddAndRemoveExternalGateway() {
+ target.createRouter(ROUTER);
+ assertTrue("Router did not match", target.router(ROUTER_ID).getExternalGatewayInfo() == null);
+
+ Router updated = NeutronRouter.builder()
+ .id(ROUTER_ID)
+ .name(ROUTER_NAME)
+ .externalGateway("test-network-id")
+ .build();
+
+ target.updateRouter(updated);
+ assertEquals("Router did not match",
+ "test-network-id",
+ target.router(ROUTER_ID).getExternalGatewayInfo().getNetworkId());
+
+ target.updateRouter(ROUTER);
+ assertTrue("Router did not match", target.router(ROUTER_ID).getExternalGatewayInfo() == null);
+
+ validateEvents(OPENSTACK_ROUTER_CREATED, OPENSTACK_ROUTER_UPDATED,
+ OPENSTACK_ROUTER_GATEWAY_ADDED,
+ OPENSTACK_ROUTER_UPDATED,
+ OPENSTACK_ROUTER_GATEWAY_REMOVED);
+ }
+
+ /**
+ * Tests if creating a router with null value fails with an exception.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testCreateRouterWithNull() {
+ target.createRouter(null);
+ }
+
+ /**
+ * Tests if creating a router with null ID fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateRouterWithNullId() {
+ final Router testRouter = NeutronRouter.builder()
+ .id(null)
+ .name(ROUTER_NAME)
+ .build();
+ target.createRouter(testRouter);
+ }
+
+ /**
+ * Tests if updating a router with null name fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateRouterWithNullName() {
+ final Router testRouter = NeutronRouter.builder()
+ .id(ROUTER_ID)
+ .name(null)
+ .build();
+ target.createRouter(testRouter);
+ }
+
+ /**
+ * Tests if creating a duplicate router fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateDuplicateRouter() {
+ target.createRouter(ROUTER);
+ target.createRouter(ROUTER);
+ }
+
+ /**
+ * Tests if updating a router with null value fails with an exception.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testUpdateRouterWithNull() {
+ target.updateRouter(null);
+ }
+
+ /**
+ * Tests if updating a router with null ID fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testUpdateRouterWithNullId() {
+ final Router testRouter = NeutronRouter.builder()
+ .id(null)
+ .name(ROUTER_NAME)
+ .build();
+ target.updateRouter(testRouter);
+ }
+
+ /**
+ * Tests if updating a router with null name fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testUpdateRouterWithNullName() {
+ final Router testRouter = NeutronRouter.builder()
+ .id(ROUTER_ID)
+ .name(null)
+ .build();
+ target.updateRouter(testRouter);
+ }
+
+ /**
+ * Tests if updating an unregistered router fails with an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testUpdateUnregisteredRouter() {
+ target.updateRouter(ROUTER);
+ }
+
+ // TODO fix openstack4j floating IP data model and add unit tests
+
+ private void createBasicRouters() {
+ target.createRouter(ROUTER);
+ }
+
+ private static class TestCoreService extends CoreServiceAdapter {
+
+ @Override
+ public ApplicationId registerApplication(String name) {
+ return TEST_APP_ID;
+ }
+ }
+
+ private static class TestOpenstackRouterListener implements OpenstackRouterListener {
+ private List<OpenstackRouterEvent> events = Lists.newArrayList();
+
+ @Override
+ public void event(OpenstackRouterEvent event) {
+ events.add(event);
+ }
+ }
+
+ private void validateEvents(Enum... types) {
+ int i = 0;
+ assertEquals("Number of events did not match", types.length, testListener.events.size());
+ for (Event event : testListener.events) {
+ assertEquals("Incorrect event received", types[i], event.type());
+ i++;
+ }
+ testListener.events.clear();
+ }
+}
diff --git a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackFloatingIpWebResourceTest.java b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackFloatingIpWebResourceTest.java
new file mode 100644
index 0000000..3e98a5d
--- /dev/null
+++ b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackFloatingIpWebResourceTest.java
@@ -0,0 +1,231 @@
+/*
+ * 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.openstacknetworking.web;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onosproject.openstacknetworking.api.OpenstackRouterAdminService;
+import org.onosproject.rest.resources.ResourceTest;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.InputStream;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit test for openstack floating IP REST API.
+ */
+public class OpenstackFloatingIpWebResourceTest extends ResourceTest {
+
+ final OpenstackRouterAdminService mockOpenstackRouterAdminService =
+ createMock(OpenstackRouterAdminService.class);
+ private static final String PATH = "floatingips";
+
+ /**
+ * Constructs an openstack floating IP test instance.
+ */
+ public OpenstackFloatingIpWebResourceTest() {
+ super(ResourceConfig.forApplicationClass(OpenstackNetworkingWebApplication.class));
+ }
+
+ /**
+ * Sets up the global values for all tests.
+ */
+ @Before
+ public void setUpTest() {
+ ServiceDirectory testDirectory =
+ new TestServiceDirectory()
+ .add(OpenstackRouterAdminService.class,
+ mockOpenstackRouterAdminService);
+ setServiceDirectory(testDirectory);
+
+ }
+
+ /**
+ * Tests the results of the REST API POST with creation operation.
+ */
+ @Test
+ public void testCreateFloatingIpWithCreationOperation() {
+ mockOpenstackRouterAdminService.createFloatingIp(anyObject());
+ replay(mockOpenstackRouterAdminService);
+
+ final WebTarget wt = target();
+ InputStream jsonStream = OpenstackFloatingIpWebResourceTest.class
+ .getResourceAsStream("openstack-floatingip.json");
+
+ Response response = wt.path(PATH).request(MediaType.APPLICATION_JSON_TYPE)
+ .post(Entity.json(jsonStream));
+ final int status = response.getStatus();
+
+ assertThat(status, is(201));
+
+ verify(mockOpenstackRouterAdminService);
+ }
+
+ /**
+ * Tests the results of the REST API POST with incorrect input.
+ */
+ @Test
+ public void testCreateFloatingIpWithIncorrectInput() {
+ final WebTarget wt = target();
+ InputStream jsonStream = OpenstackFloatingIpWebResourceTest.class
+ .getResourceAsStream("dummy.json");
+
+ Response response = wt.path(PATH).request(MediaType.APPLICATION_JSON_TYPE)
+ .post(Entity.json(jsonStream));
+ final int status = response.getStatus();
+
+ assertThat(status, is(400));
+ }
+
+ /**
+ * Tests the results of the REST API POST with duplicated floating IP.
+ */
+ @Test
+ public void testCreateFloatingIpWithDuplicatedIp() {
+ mockOpenstackRouterAdminService.createFloatingIp(anyObject());
+ expectLastCall().andThrow(new IllegalArgumentException());
+ replay(mockOpenstackRouterAdminService);
+
+ final WebTarget wt = target();
+ InputStream jsonStream = OpenstackFloatingIpWebResourceTest.class
+ .getResourceAsStream("openstack-floatingip.json");
+
+ Response response = wt.path(PATH).request(MediaType.APPLICATION_JSON_TYPE)
+ .post(Entity.json(jsonStream));
+ final int status = response.getStatus();
+
+ assertThat(status, is(400));
+
+ verify(mockOpenstackRouterAdminService);
+ }
+
+ /**
+ * Tests the results of the REST API PUT with updating operation.
+ */
+ @Test
+ public void testUpdateFloatingIpWithUpdatingOperation() {
+ mockOpenstackRouterAdminService.updateFloatingIp(anyObject());
+ replay(mockOpenstackRouterAdminService);
+
+ final WebTarget wt = target();
+ InputStream jsonStream = OpenstackFloatingIpWebResourceTest.class
+ .getResourceAsStream("openstack-floatingip.json");
+
+ Response response = wt.path(PATH + "/2f245a7b-796b-4f26-9cf9-9e82d248fda7")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .put(Entity.json(jsonStream));
+ final int status = response.getStatus();
+
+ assertThat(status, is(200));
+
+ verify(mockOpenstackRouterAdminService);
+ }
+
+ /**
+ * Tests the results of the REST API PUT with incorrect input.
+ */
+ @Test
+ public void testUpdateFloatingIpWithIncorrectInput() {
+ final WebTarget wt = target();
+ InputStream jsonStream = OpenstackFloatingIpWebResourceTest.class
+ .getResourceAsStream("dummy.json");
+
+ Response response = wt.path(PATH + "/2f245a7b-796b-4f26-9cf9-9e82d248fda7")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .put(Entity.json(jsonStream));
+ final int status = response.getStatus();
+
+ assertThat(status, is(400));
+ }
+
+ /**
+ * Tests the results of the REST API PUT with non-existing ID.
+ */
+ @Test
+ public void testUpdateFloatingIpWithNonexistId() {
+ mockOpenstackRouterAdminService.updateFloatingIp(anyObject());
+ expectLastCall().andThrow(new IllegalArgumentException());
+ replay(mockOpenstackRouterAdminService);
+
+ final WebTarget wt = target();
+ InputStream jsonStream = OpenstackFloatingIpWebResourceTest.class
+ .getResourceAsStream("openstack-floatingip.json");
+
+ Response response = wt.path(PATH + "/2f245a7b-796b-4f26-9cf9-9e82d248fda7")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .put(Entity.json(jsonStream));
+ final int status = response.getStatus();
+
+ assertThat(status, is(400));
+
+ verify(mockOpenstackRouterAdminService);
+ }
+
+ /**
+ * Tests the results of the REST API DELETE with deletion operation.
+ */
+ @Test
+ public void testDeleteFloatingIpWithDeletionOperation() {
+ mockOpenstackRouterAdminService.removeFloatingIp(anyString());
+ replay(mockOpenstackRouterAdminService);
+
+ final WebTarget wt = target();
+
+ Response response = wt.path(PATH + "/2f245a7b-796b-4f26-9cf9-9e82d248fda7")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .delete();
+ final int status = response.getStatus();
+
+ assertThat(status, is(204));
+
+ verify(mockOpenstackRouterAdminService);
+ }
+
+ /**
+ * Tests the results of the REST API DELETE with non-existing ID.
+ */
+ @Test
+ public void testDeleteFloatingIpWithNonexistId() {
+ mockOpenstackRouterAdminService.removeFloatingIp(anyString());
+ expectLastCall().andThrow(new IllegalArgumentException());
+ replay(mockOpenstackRouterAdminService);
+
+ final WebTarget wt = target();
+
+ Response response = wt.path(PATH + "/non-exist-id")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .delete();
+ final int status = response.getStatus();
+
+ assertThat(status, is(400));
+
+ verify(mockOpenstackRouterAdminService);
+ }
+}
diff --git a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackNetworkWebResourceTest.java b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackNetworkWebResourceTest.java
new file mode 100644
index 0000000..bbac93b
--- /dev/null
+++ b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackNetworkWebResourceTest.java
@@ -0,0 +1,230 @@
+/*
+ * 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.openstacknetworking.web;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
+import org.onosproject.rest.resources.ResourceTest;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.InputStream;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit test for openstack network REST API.
+ */
+public class OpenstackNetworkWebResourceTest extends ResourceTest {
+
+ final OpenstackNetworkAdminService mockOpenstackNetworkAdminService =
+ createMock(OpenstackNetworkAdminService.class);
+ private static final String PATH = "networks";
+
+ /**
+ * Constructs an openstack network test instance.
+ */
+ public OpenstackNetworkWebResourceTest() {
+ super(ResourceConfig.forApplicationClass(OpenstackNetworkingWebApplication.class));
+ }
+
+ /**
+ * Sets up the global values for all tests.
+ */
+ @Before
+ public void setUpTest() {
+ ServiceDirectory testDirectory =
+ new TestServiceDirectory()
+ .add(OpenstackNetworkAdminService.class,
+ mockOpenstackNetworkAdminService);
+ setServiceDirectory(testDirectory);
+ }
+
+ /**
+ * Tests the results of the REST API POST with creation operation.
+ */
+ @Test
+ public void testCreateNetworkWithCreationOperation() {
+ mockOpenstackNetworkAdminService.createNetwork(anyObject());
+ replay(mockOpenstackNetworkAdminService);
+
+ final WebTarget wt = target();
+ InputStream jsonStream = OpenstackNetworkWebResourceTest.class
+ .getResourceAsStream("openstack-network.json");
+
+ Response response = wt.path(PATH).request(MediaType.APPLICATION_JSON_TYPE)
+ .post(Entity.json(jsonStream));
+ final int status = response.getStatus();
+
+ assertThat(status, is(201));
+
+ verify(mockOpenstackNetworkAdminService);
+ }
+
+ /**
+ * Tests the results of the REST API POST with incorrect input.
+ */
+ @Test
+ public void testCreateNetworkWithIncorrectInput() {
+ final WebTarget wt = target();
+ InputStream jsonStream = OpenstackNetworkWebResourceTest.class
+ .getResourceAsStream("dummy.json");
+
+ Response response = wt.path(PATH).request(MediaType.APPLICATION_JSON_TYPE)
+ .post(Entity.json(jsonStream));
+ final int status = response.getStatus();
+
+ assertThat(status, is(400));
+ }
+
+ /**
+ * Tests the results of the REST API POST with duplicated network ID.
+ */
+ @Test
+ public void testCreateNetworkWithDuplicatedId() {
+ mockOpenstackNetworkAdminService.createNetwork(anyObject());
+ expectLastCall().andThrow(new IllegalArgumentException());
+ replay(mockOpenstackNetworkAdminService);
+
+ final WebTarget wt = target();
+ InputStream jsonStream = OpenstackNetworkWebResourceTest.class
+ .getResourceAsStream("openstack-network.json");
+
+ Response response = wt.path(PATH).request(MediaType.APPLICATION_JSON_TYPE)
+ .post(Entity.json(jsonStream));
+ final int status = response.getStatus();
+
+ assertThat(status, is(400));
+
+ verify(mockOpenstackNetworkAdminService);
+ }
+
+ /**
+ * Tests the results of the REST API PUT with updating operation.
+ */
+ @Test
+ public void testUpdateNetworkWithUpdatingOperation() {
+ mockOpenstackNetworkAdminService.updateNetwork(anyObject());
+ replay(mockOpenstackNetworkAdminService);
+
+ final WebTarget wt = target();
+ InputStream jsonStream = OpenstackNetworkWebResourceTest.class
+ .getResourceAsStream("openstack-network.json");
+
+ Response response = wt.path(PATH + "/396f12f8-521e-4b91-8e21-2e003500433a")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .put(Entity.json(jsonStream));
+ final int status = response.getStatus();
+
+ assertThat(status, is(200));
+
+ verify(mockOpenstackNetworkAdminService);
+ }
+
+ /**
+ * Tests the results of the REST API PUT with incorrect input.
+ */
+ @Test
+ public void testUpdateNetworkWithIncorrectInput() {
+ final WebTarget wt = target();
+ InputStream jsonStream = OpenstackNetworkWebResourceTest.class
+ .getResourceAsStream("dummy.json");
+
+ Response response = wt.path(PATH + "/396f12f8-521e-4b91-8e21-2e003500433a")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .put(Entity.json(jsonStream));
+ final int status = response.getStatus();
+
+ assertThat(status, is(400));
+ }
+
+ /**
+ * Tests the results of the REST API PUT with non-existing network ID.
+ */
+ @Test
+ public void testUpdateNetworkWithNonexistId() {
+ mockOpenstackNetworkAdminService.updateNetwork(anyObject());
+ expectLastCall().andThrow(new IllegalArgumentException());
+ replay(mockOpenstackNetworkAdminService);
+
+ final WebTarget wt = target();
+ InputStream jsonStream = OpenstackNetworkWebResourceTest.class
+ .getResourceAsStream("openstack-network.json");
+
+ Response response = wt.path(PATH + "/396f12f8-521e-4b91-8e21-2e003500433a")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .put(Entity.json(jsonStream));
+ final int status = response.getStatus();
+
+ assertThat(status, is(400));
+
+ verify(mockOpenstackNetworkAdminService);
+ }
+
+ /**
+ * Tests the results of the REST API DELETE with deletion operation.
+ */
+ @Test
+ public void testDeleteNetworkWithDeletionOperation() {
+ mockOpenstackNetworkAdminService.removeNetwork(anyString());
+ replay(mockOpenstackNetworkAdminService);
+
+ final WebTarget wt = target();
+
+ Response response = wt.path(PATH + "/396f12f8-521e-4b91-8e21-2e003500433a")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .delete();
+ final int status = response.getStatus();
+
+ assertThat(status, is(204));
+
+ verify(mockOpenstackNetworkAdminService);
+ }
+
+ /**
+ * Tests the results of the REST API DELETE with non-existing network ID.
+ */
+ @Test
+ public void testDeleteNetworkWithNonexistId() {
+ mockOpenstackNetworkAdminService.removeNetwork(anyString());
+ expectLastCall().andThrow(new IllegalArgumentException());
+ replay(mockOpenstackNetworkAdminService);
+
+ final WebTarget wt = target();
+
+ Response response = wt.path(PATH + "/non-exist-id")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .delete();
+ final int status = response.getStatus();
+
+ assertThat(status, is(400));
+
+ verify(mockOpenstackNetworkAdminService);
+ }
+}
diff --git a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackPortWebResourceTest.java b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackPortWebResourceTest.java
new file mode 100644
index 0000000..e3f4bf0
--- /dev/null
+++ b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/web/OpenstackPortWebResourceTest.java
@@ -0,0 +1,231 @@
+/*
+ * 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.openstacknetworking.web;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.osgi.ServiceDirectory;
+import org.onlab.osgi.TestServiceDirectory;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
+import org.onosproject.rest.resources.ResourceTest;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.InputStream;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit test for openstack port REST API.
+ */
+public class OpenstackPortWebResourceTest extends ResourceTest {
+
+ final OpenstackNetworkAdminService mockOpenstackNetworkAdminService =
+ createMock(OpenstackNetworkAdminService.class);
+ private static final String PATH = "ports";
+
+ /**
+ * Constructs an openstack port test instance.
+ */
+ public OpenstackPortWebResourceTest() {
+ super(ResourceConfig.forApplicationClass(OpenstackNetworkingWebApplication.class));
+ }
+
+ /**
+ * Sets up the global values for all tests.
+ */
+ @Before
+ public void setUpTest() {
+ ServiceDirectory testDirectory =
+ new TestServiceDirectory()
+ .add(OpenstackNetworkAdminService.class,
+ mockOpenstackNetworkAdminService);
+ setServiceDirectory(testDirectory);
+
+ }
+
+ /**
+ * Tests the results of the REST API POST with creation operation.
+ */
+ @Test
+ public void testCreatePortWithCreationOperation() {
+ mockOpenstackNetworkAdminService.createPort(anyObject());
+ replay(mockOpenstackNetworkAdminService);
+
+ final WebTarget wt = target();
+ InputStream jsonStream = OpenstackNetworkWebResourceTest.class
+ .getResourceAsStream("openstack-port.json");
+
+ Response response = wt.path(PATH).request(MediaType.APPLICATION_JSON_TYPE)
+ .post(Entity.json(jsonStream));
+ final int status = response.getStatus();
+
+ assertThat(status, is(201));
+
+ verify(mockOpenstackNetworkAdminService);
+ }
+
+ /**
+ * Tests the results of the REST API POST with incorrect input.
+ */
+ @Test
+ public void testCreatePortWithIncorrectInput() {
+ final WebTarget wt = target();
+ InputStream jsonStream = OpenstackPortWebResourceTest.class
+ .getResourceAsStream("dummy.json");
+
+ Response response = wt.path(PATH).request(MediaType.APPLICATION_JSON_TYPE)
+ .post(Entity.json(jsonStream));
+ final int status = response.getStatus();
+
+ assertThat(status, is(400));
+ }
+
+ /**
+ * Tests the results of the REST API POST with duplicated port ID.
+ */
+ @Test
+ public void testCreatePortWithDuplicatedId() {
+ mockOpenstackNetworkAdminService.createPort(anyObject());
+ expectLastCall().andThrow(new IllegalArgumentException());
+ replay(mockOpenstackNetworkAdminService);
+
+ final WebTarget wt = target();
+ InputStream jsonStream = OpenstackNetworkWebResourceTest.class
+ .getResourceAsStream("openstack-port.json");
+
+ Response response = wt.path(PATH).request(MediaType.APPLICATION_JSON_TYPE)
+ .post(Entity.json(jsonStream));
+ final int status = response.getStatus();
+
+ assertThat(status, is(400));
+
+ verify(mockOpenstackNetworkAdminService);
+ }
+