Merge branch 'cordvtn-to-cord' of /Users/ash/work/onos-next
diff --git a/BUCK b/BUCK
new file mode 100644
index 0000000..2a7d8ca
--- /dev/null
+++ b/BUCK
@@ -0,0 +1,40 @@
+# app builds but is currently non functional. It needs transitive runtime
+# dependencies.
+
+COMPILE_DEPS = [
+ '//lib:CORE_DEPS',
+ '//lib:org.apache.karaf.shell.console',
+ '//lib:javax.ws.rs-api',
+ '//lib:jsch',
+ '//utils/rest:onlab-rest',
+ '//cli:onos-cli',
+ '//core/store/serializers:onos-core-serializers',
+ '//apps/dhcp/api:onos-apps-dhcp-api',
+ '//apps/xosclient:onos-apps-xosclient',
+ '//apps/cordconfig:onos-apps-cordconfig',
+ '//protocols/ovsdb/api:onos-protocols-ovsdb-api',
+ '//protocols/ovsdb/rfc:onos-protocols-ovsdb-rfc',
+]
+
+BUNDLES = [
+ '//apps/cordvtn:onos-apps-cordvtn',
+]
+
+EXCLUDED_BUNDLES = [
+ '//lib:jsch',
+]
+
+osgi_jar_with_tests (
+ deps = COMPILE_DEPS,
+ web_context = '/onos/cordvtn',
+)
+
+onos_app (
+ title = 'CORD VTN REST API',
+ category = 'Traffic Steering',
+ url = 'http://onosproject.org',
+ included_bundles = BUNDLES,
+ excluded_bundles = EXCLUDED_BUNDLES,
+ description = 'APIs for interacting with the CORD VTN application.',
+ required_apps = [ 'org.onosproject.cord-config', 'org.onosproject.xosclient', 'org.onosproject.dhcp', 'org.onosproject.ovsdb' ],
+)
diff --git a/app.xml b/app.xml
new file mode 100644
index 0000000..17583ce
--- /dev/null
+++ b/app.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2016-present Open Networking Laboratory
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<app name="org.onosproject.cordvtn" origin="ON.Lab" version="${project.version}"
+ category="Traffic Steering" url="http://onosproject.org" title="CORD Virtual Tenant Network"
+ featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features"
+ features="${project.artifactId}"
+ apps="org.onosproject.ovsdb-base,org.onosproject.dhcp,org.onosproject.xosclient,org.onosproject.cord-config">
+ <description>${project.description}</description>
+ <artifact>mvn:${project.groupId}/onos-app-cordvtn/${project.version}</artifact>
+</app>
diff --git a/features.xml b/features.xml
new file mode 100644
index 0000000..1425b03
--- /dev/null
+++ b/features.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+ ~ Copyright 2016-present Open Networking Laboratory
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}">
+ <feature name="${project.artifactId}" version="${project.version}" description="${project.description}">
+ <feature>onos-api</feature>
+ <bundle>mvn:${project.groupId}/onos-app-cordvtn/${project.version}</bundle>
+ <bundle>wrap:mvn:com.jcraft/jsch/0.1.53$Bundle-SymbolicName=jsch&Bundle-Version=0.1.53</bundle>
+ </feature>
+</features>
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..fa6e068
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2015-present Open Networking Laboratory
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-apps</artifactId>
+ <version>1.7.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>onos-app-cordvtn</artifactId>
+ <packaging>bundle</packaging>
+
+ <description>Virtual tenant network service for CORD</description>
+
+ <properties>
+ <web.context>/onos/cordvtn</web.context>
+ <api.version>1.0.0</api.version>
+ <api.title>CORD VTN REST API</api.title>
+ <api.description>
+ APIs for interacting with the CORD VTN application.
+ </api.description>
+ <api.package>org.onosproject.cordvtn.rest</api.package>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core-serializers</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-ovsdb-api</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>
+ <version>3.0.5</version>
+ </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>javax.ws.rs</groupId>
+ <artifactId>javax.ws.rs-api</artifactId>
+ <version>2.0.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish.jersey.containers</groupId>
+ <artifactId>jersey-container-servlet</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.onosproject</groupId>
+ <artifactId>onos-app-dhcp-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-app-xos-client</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-cord-config</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.jcraft</groupId>
+ <artifactId>jsch</artifactId>
+ <version>0.1.53</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <_wab>src/main/webapp/</_wab>
+ <Include-Resource>
+ WEB-INF/classes/apidoc/swagger.json=target/swagger.json,
+ {maven-resources}
+ </Include-Resource>
+ <Bundle-SymbolicName>
+ ${project.groupId}.${project.artifactId}
+ </Bundle-SymbolicName>
+ <Import-Package>
+ *,org.glassfish.jersey.servlet
+ </Import-Package>
+ <Web-ContextPath>${web.context}</Web-ContextPath>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/src/main/java/org/onosproject/cordvtn/api/ConnectionHandler.java b/src/main/java/org/onosproject/cordvtn/api/ConnectionHandler.java
new file mode 100644
index 0000000..5676221
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/api/ConnectionHandler.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2015-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.api;
+
+/**
+ * Entity capable of handling a subject connected and disconnected situation.
+ */
+public interface ConnectionHandler<T> {
+
+ /**
+ * Processes the connected subject.
+ *
+ * @param subject subject
+ */
+ void connected(T subject);
+
+ /**
+ * Processes the disconnected subject.
+ *
+ * @param subject subject.
+ */
+ void disconnected(T subject);
+}
diff --git a/src/main/java/org/onosproject/cordvtn/api/CordVtnConfig.java b/src/main/java/org/onosproject/cordvtn/api/CordVtnConfig.java
new file mode 100644
index 0000000..be097ce
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/api/CordVtnConfig.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2015-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.api;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.TpPort;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.config.Config;
+import org.onosproject.xosclient.api.OpenStackAccess;
+import org.onosproject.xosclient.api.XosAccess;
+import org.slf4j.Logger;
+
+import java.util.Map;
+import java.util.Set;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Configuration object for CordVtn service.
+ */
+public class CordVtnConfig extends Config<ApplicationId> {
+
+ protected final Logger log = getLogger(getClass());
+
+ public static final String PRIVATE_GATEWAY_MAC = "privateGatewayMac";
+ public static final String PUBLIC_GATEWAYS = "publicGateways";
+ public static final String GATEWAY_IP = "gatewayIp";
+ public static final String GATEWAY_MAC = "gatewayMac";
+ public static final String LOCAL_MANAGEMENT_IP = "localManagementIp";
+ public static final String MANAGEMENT_IP = "managementIpRange";
+ public static final String OVSDB_PORT = "ovsdbPort";
+
+ public static final String CORDVTN_NODES = "nodes";
+ public static final String HOSTNAME = "hostname";
+ public static final String HOST_MANAGEMENT_IP = "hostManagementIp";
+ public static final String DATA_PLANE_IP = "dataPlaneIp";
+ public static final String DATA_PLANE_INTF = "dataPlaneIntf";
+ public static final String BRIDGE_ID = "bridgeId";
+
+ public static final String SSH = "ssh";
+ public static final String SSH_PORT = "sshPort";
+ public static final String SSH_USER = "sshUser";
+ public static final String SSH_KEY_FILE = "sshKeyFile";
+
+ public static final String OPENSTACK = "openstack";
+ public static final String XOS = "xos";
+
+ public static final String ENDPOINT = "endpoint";
+ public static final String TENANT = "tenant";
+ public static final String USER = "user";
+ public static final String PASSWORD = "password";
+
+ /**
+ * Returns the set of nodes read from network config.
+ *
+ * @return set of CordVtnNodeConfig or empty set
+ */
+ public Set<CordVtnNode> cordVtnNodes() {
+
+ Set<CordVtnNode> nodes = Sets.newHashSet();
+
+ JsonNode cordvtnNodes = object.get(CORDVTN_NODES);
+ if (cordvtnNodes == null) {
+ log.debug("No CORD VTN nodes found");
+ return nodes;
+ }
+
+ JsonNode sshNode = object.get(SSH);
+ if (sshNode == null) {
+ log.warn("SSH information not found");
+ return nodes;
+ }
+
+ for (JsonNode cordvtnNode : cordvtnNodes) {
+ try {
+ NetworkAddress hostMgmt = NetworkAddress.valueOf(getConfig(cordvtnNode, HOST_MANAGEMENT_IP));
+ NetworkAddress localMgmt = NetworkAddress.valueOf(getConfig(object, LOCAL_MANAGEMENT_IP));
+ if (hostMgmt.prefix().contains(localMgmt.prefix()) ||
+ localMgmt.prefix().contains(hostMgmt.prefix())) {
+ log.error("hostMamt and localMgmt cannot be overlapped, skip this node");
+ continue;
+ }
+
+ Ip4Address hostMgmtIp = hostMgmt.ip().getIp4Address();
+ SshAccessInfo sshInfo = new SshAccessInfo(
+ hostMgmtIp,
+ TpPort.tpPort(Integer.parseInt(getConfig(sshNode, SSH_PORT))),
+ getConfig(sshNode, SSH_USER), getConfig(sshNode, SSH_KEY_FILE));
+
+ String hostname = getConfig(cordvtnNode, HOSTNAME);
+ CordVtnNode newNode = new CordVtnNode(
+ hostname, hostMgmt, localMgmt,
+ NetworkAddress.valueOf(getConfig(cordvtnNode, DATA_PLANE_IP)),
+ TpPort.tpPort(Integer.parseInt(getConfig(object, OVSDB_PORT))),
+ sshInfo,
+ DeviceId.deviceId(getConfig(cordvtnNode, BRIDGE_ID)),
+ getConfig(cordvtnNode, DATA_PLANE_INTF),
+ CordVtnNodeState.noState());
+
+ nodes.add(newNode);
+ } catch (IllegalArgumentException | NullPointerException e) {
+ log.error("{}", e);
+ }
+ }
+
+ return nodes;
+ }
+
+ /**
+ * Returns value of a given path. If the path is missing, show log and return
+ * null.
+ *
+ * @param path path
+ * @return value or null
+ */
+ private String getConfig(JsonNode jsonNode, String path) {
+ jsonNode = jsonNode.path(path);
+
+ if (jsonNode.isMissingNode()) {
+ log.error("{} is not configured", path);
+ return null;
+ } else {
+ return jsonNode.asText();
+ }
+ }
+
+ /**
+ * Returns private network gateway MAC address.
+ *
+ * @return mac address, or null
+ */
+ public MacAddress privateGatewayMac() {
+ JsonNode jsonNode = object.get(PRIVATE_GATEWAY_MAC);
+ if (jsonNode == null) {
+ return null;
+ }
+
+ try {
+ return MacAddress.valueOf(jsonNode.asText());
+ } catch (IllegalArgumentException e) {
+ log.error("Wrong MAC address format {}", jsonNode.asText());
+ return null;
+ }
+ }
+
+ /**
+ * Returns public network gateway IP and MAC address pairs.
+ *
+ * @return map of ip and mac address
+ */
+ public Map<IpAddress, MacAddress> publicGateways() {
+ JsonNode jsonNodes = object.get(PUBLIC_GATEWAYS);
+ if (jsonNodes == null) {
+ return Maps.newHashMap();
+ }
+
+ Map<IpAddress, MacAddress> publicGateways = Maps.newHashMap();
+ jsonNodes.forEach(jsonNode -> {
+ try {
+ publicGateways.put(
+ IpAddress.valueOf(jsonNode.path(GATEWAY_IP).asText()),
+ MacAddress.valueOf(jsonNode.path(GATEWAY_MAC).asText()));
+ } catch (IllegalArgumentException | NullPointerException e) {
+ log.error("Wrong address format {}", e.toString());
+ }
+ });
+
+ return publicGateways;
+ }
+
+ /**
+ * Returns management IP address range.
+ *
+ * @return management network ip prefix, or null
+ */
+ public IpPrefix managementIpRange() {
+ JsonNode jsonNode = object.get(MANAGEMENT_IP);
+ if (jsonNode == null) {
+ return null;
+ }
+
+ try {
+ return IpPrefix.valueOf(jsonNode.asText());
+ } catch (IllegalArgumentException e) {
+ log.error("{}:{} wrong address format", MANAGEMENT_IP, jsonNode);
+ return null;
+ }
+ }
+
+ /**
+ * Returns XOS access information.
+ *
+ * @return XOS access, or null
+ */
+ public XosAccess xosAccess() {
+ JsonNode jsonNode = object.get(XOS);
+ if (jsonNode == null) {
+ return null;
+ }
+
+ try {
+ return new XosAccess(getConfig(jsonNode, ENDPOINT),
+ getConfig(jsonNode, USER),
+ getConfig(jsonNode, PASSWORD));
+ } catch (NullPointerException e) {
+ log.error("Failed to get XOS access");
+ return null;
+ }
+ }
+
+ /**
+ * Returns OpenStack API access information.
+ *
+ * @return openstack access
+ */
+ public OpenStackAccess openstackAccess() {
+ JsonNode jsonNode = object.get(OPENSTACK);
+ if (jsonNode == null) {
+ log.error("Failed to get OpenStack configurations");
+ return null;
+ }
+
+ try {
+ return new OpenStackAccess(
+ jsonNode.path(ENDPOINT).asText(),
+ jsonNode.path(TENANT).asText(),
+ jsonNode.path(USER).asText(),
+ jsonNode.path(PASSWORD).asText());
+ } catch (IllegalArgumentException | NullPointerException e) {
+ log.error("Failed to get OpenStack configurations");
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/api/CordVtnNode.java b/src/main/java/org/onosproject/cordvtn/api/CordVtnNode.java
new file mode 100644
index 0000000..c63a9e4
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/api/CordVtnNode.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2015-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.api;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.TpPort;
+import org.onosproject.net.DeviceId;
+
+import java.util.Comparator;
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Representation of a compute infrastructure node for CORD VTN service.
+ */
+public final class CordVtnNode {
+
+ private final String hostname;
+ private final NetworkAddress hostMgmtIp;
+ private final NetworkAddress localMgmtIp;
+ private final NetworkAddress dpIp;
+ private final TpPort ovsdbPort;
+ private final SshAccessInfo sshInfo;
+ private final DeviceId bridgeId;
+ private final String dpIntf;
+ private final CordVtnNodeState state;
+
+ public static final Comparator<CordVtnNode> CORDVTN_NODE_COMPARATOR =
+ (node1, node2) -> node1.hostname().compareTo(node2.hostname());
+
+ /**
+ * Creates a new node.
+ *
+ * @param hostname hostname
+ * @param hostMgmtIp host management network address
+ * @param localMgmtIp local management network address
+ * @param dpIp data plane network address
+ * @param ovsdbPort port number for OVSDB connection
+ * @param sshInfo SSH access information
+ * @param bridgeId integration bridge identifier
+ * @param dpIntf data plane interface name
+ * @param state cordvtn node state
+ */
+ public CordVtnNode(String hostname, NetworkAddress hostMgmtIp, NetworkAddress localMgmtIp,
+ NetworkAddress dpIp, TpPort ovsdbPort, SshAccessInfo sshInfo,
+ DeviceId bridgeId, String dpIntf, CordVtnNodeState state) {
+ this.hostname = checkNotNull(hostname, "hostname cannot be null");
+ this.hostMgmtIp = checkNotNull(hostMgmtIp, "hostMgmtIp cannot be null");
+ this.localMgmtIp = checkNotNull(localMgmtIp, "localMgmtIp cannot be null");
+ this.dpIp = checkNotNull(dpIp, "dpIp cannot be null");
+ this.ovsdbPort = checkNotNull(ovsdbPort, "ovsdbPort cannot be null");
+ this.sshInfo = checkNotNull(sshInfo, "sshInfo cannot be null");
+ this.bridgeId = checkNotNull(bridgeId, "bridgeId cannot be null");
+ this.dpIntf = checkNotNull(dpIntf, "dpIntf cannot be null");
+ this.state = state;
+ }
+
+ /**
+ * Returns cordvtn node with new state.
+ *
+ * @param node cordvtn node
+ * @param state cordvtn node init state
+ * @return cordvtn node
+ */
+ public static CordVtnNode getUpdatedNode(CordVtnNode node, CordVtnNodeState state) {
+ return new CordVtnNode(node.hostname,
+ node.hostMgmtIp, node.localMgmtIp, node.dpIp,
+ node.ovsdbPort,
+ node.sshInfo,
+ node.bridgeId,
+ node.dpIntf, state);
+ }
+
+ /**
+ * Returns the hostname.
+ *
+ * @return hostname
+ */
+ public String hostname() {
+ return this.hostname;
+ }
+
+ /**
+ * Returns the host management network address.
+ *
+ * @return network address
+ */
+ public NetworkAddress hostMgmtIp() {
+ return this.hostMgmtIp;
+ }
+
+ /**
+ * Returns the local management network address.
+ *
+ * @return network address
+ */
+ public NetworkAddress localMgmtIp() {
+ return this.localMgmtIp;
+ }
+
+ /**
+ * Returns the data plane network address.
+ *
+ * @return network address
+ */
+ public NetworkAddress dpIp() {
+ return this.dpIp;
+ }
+
+ /**
+ * Returns the port number used for OVSDB connection.
+ *
+ * @return port number
+ */
+ public TpPort ovsdbPort() {
+ return this.ovsdbPort;
+ }
+
+ /**
+ * Returns the SSH access information.
+ *
+ * @return ssh access information
+ */
+ public SshAccessInfo sshInfo() {
+ return this.sshInfo;
+ }
+
+ /**
+ * Returns the identifier of the integration bridge.
+ *
+ * @return device id
+ */
+ public DeviceId intBrId() {
+ return this.bridgeId;
+ }
+
+ /**
+ * Returns the identifier of the OVSDB device.
+ *
+ * @return device id
+ */
+ public DeviceId ovsdbId() {
+ return DeviceId.deviceId("ovsdb:" + this.hostMgmtIp.ip().toString());
+ }
+
+ /**
+ * Returns data plane interface name.
+ *
+ * @return data plane interface name
+ */
+ public String dpIntf() {
+ return this.dpIntf;
+ }
+
+ /**
+ * Returns the state of the node.
+ *
+ * @return state
+ */
+ public CordVtnNodeState state() {
+ return this.state;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj instanceof CordVtnNode) {
+ CordVtnNode that = (CordVtnNode) obj;
+ if (Objects.equals(hostname, that.hostname) &&
+ Objects.equals(hostMgmtIp, that.hostMgmtIp) &&
+ Objects.equals(localMgmtIp, that.localMgmtIp) &&
+ Objects.equals(dpIp, that.dpIp) &&
+ Objects.equals(ovsdbPort, that.ovsdbPort) &&
+ Objects.equals(sshInfo, that.sshInfo) &&
+ Objects.equals(bridgeId, that.bridgeId) &&
+ Objects.equals(dpIntf, that.dpIntf)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(hostname, hostMgmtIp, localMgmtIp, dpIp,
+ ovsdbPort, sshInfo, bridgeId, dpIntf);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("hostname", hostname)
+ .add("hostMgmtIp", hostMgmtIp)
+ .add("localMgmtIp", localMgmtIp)
+ .add("dpIp", dpIp)
+ .add("port", ovsdbPort)
+ .add("sshInfo", sshInfo)
+ .add("bridgeId", bridgeId)
+ .add("dpIntf", dpIntf)
+ .add("state", state)
+ .toString();
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/api/CordVtnNodeState.java b/src/main/java/org/onosproject/cordvtn/api/CordVtnNodeState.java
new file mode 100644
index 0000000..1121390
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/api/CordVtnNodeState.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.api;
+
+/**
+ * Entity that defines possible init state of the cordvtn node.
+ */
+public interface CordVtnNodeState {
+ /**
+ * Returns null for no state.
+ *
+ * @return null
+ */
+ static CordVtnNodeState noState() {
+ return null;
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/api/CordVtnService.java b/src/main/java/org/onosproject/cordvtn/api/CordVtnService.java
new file mode 100644
index 0000000..1a8849e
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/api/CordVtnService.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2015-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.api;
+
+import org.onosproject.xosclient.api.VtnServiceId;
+
+/**
+ * Service for provisioning overlay virtual networks on compute nodes.
+ */
+public interface CordVtnService {
+
+ String CORDVTN_APP_ID = "org.onosproject.cordvtn";
+
+ /**
+ * Creates dependencies for a given tenant service.
+ *
+ * @param tServiceId id of the service which has a dependency
+ * @param pServiceId id of the service which provide dependency
+ * @param isBidirectional true to enable bidirectional connectivity between two services
+ */
+ void createServiceDependency(VtnServiceId tServiceId, VtnServiceId pServiceId,
+ boolean isBidirectional);
+
+ /**
+ * Removes all dependencies from a given tenant service.
+ *
+ * @param tServiceId id of the service which has a dependency
+ * @param pServiceId id of the service which provide dependency
+ */
+ void removeServiceDependency(VtnServiceId tServiceId, VtnServiceId pServiceId);
+}
diff --git a/src/main/java/org/onosproject/cordvtn/api/Instance.java b/src/main/java/org/onosproject/cordvtn/api/Instance.java
new file mode 100644
index 0000000..83c7c08
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/api/Instance.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.api;
+
+import com.google.common.base.Strings;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.PortNumber;
+import org.onosproject.xosclient.api.VtnPortId;
+import org.onosproject.xosclient.api.VtnService;
+import org.onosproject.xosclient.api.VtnServiceId;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Provides methods to help to handle network service instance.
+ */
+public final class Instance {
+
+ public static final String SERVICE_ID = "serviceId";
+ public static final String SERVICE_TYPE = "serviceType";
+ public static final String PORT_ID = "vtnPortId";
+ public static final String CREATE_TIME = "createTime";
+ public static final String NESTED_INSTANCE = "nestedInstance";
+ public static final String TRUE = "true";
+
+ private final Host host;
+
+ /**
+ * Default constructor.
+ *
+ * @param instance host object of this instance
+ */
+ private Instance(Host instance) {
+ this.host = instance;
+ }
+
+ /**
+ * Returns host object of this instance.
+ *
+ * @return host
+ */
+ public Host host() {
+ return this.host;
+ }
+
+ /**
+ * Returns new instance.
+ *
+ * @param host host object of this instance
+ * @return instance
+ */
+ public static Instance of(Host host) {
+ checkNotNull(host);
+ checkArgument(!Strings.isNullOrEmpty(host.annotations().value(SERVICE_ID)));
+ checkArgument(!Strings.isNullOrEmpty(host.annotations().value(SERVICE_TYPE)));
+ checkArgument(!Strings.isNullOrEmpty(host.annotations().value(PORT_ID)));
+ checkArgument(!Strings.isNullOrEmpty(host.annotations().value(CREATE_TIME)));
+
+ return new Instance(host);
+ }
+
+ /**
+ * Returns service ID of a given host.
+ *
+ * @return vtn service id
+ */
+ public VtnServiceId serviceId() {
+ String serviceId = host.annotations().value(SERVICE_ID);
+ return VtnServiceId.of(serviceId);
+ }
+
+ /**
+ * Returns service type of a given host.
+ *
+ * @return vtn service type
+ */
+ public VtnService.ServiceType serviceType() {
+ String serviceType = host.annotations().value(SERVICE_TYPE);
+ return VtnService.ServiceType.valueOf(serviceType);
+ }
+
+ /**
+ * Returns port ID of a given host.
+ *
+ * @return vtn port id
+ */
+ public VtnPortId portId() {
+ String portId = host.annotations().value(PORT_ID);
+ return VtnPortId.of(portId);
+ }
+
+ /**
+ * Returns if the instance is nested container or not.
+ *
+ * @return true if it's nested container; false otherwise
+ */
+ public boolean isNestedInstance() {
+ return host.annotations().value(NESTED_INSTANCE) != null;
+ }
+
+ /**
+ * Returns MAC address of this instance.
+ *
+ * @return mac address
+ */
+ public MacAddress mac() {
+ return host.mac();
+ }
+
+ /**
+ * Returns IP address of this instance.
+ *
+ * @return ip address
+ */
+ public Ip4Address ipAddress() {
+ // assume all instance has only one IP address, and only IP4 is supported now
+ return host.ipAddresses().stream().findFirst().get().getIp4Address();
+ }
+
+ /**
+ * Returns device ID of this host.
+ *
+ * @return device id
+ */
+ public DeviceId deviceId() {
+ return host.location().deviceId();
+ }
+
+ /**
+ * Returns the port number where this host is.
+ *
+ * @return port number
+ */
+ public PortNumber portNumber() {
+ return host.location().port();
+ }
+
+ /**
+ * Returns annotation value with a given key.
+ *
+ * @param annotationKey annotation key
+ * @return annotation value
+ */
+ public String getAnnotation(String annotationKey) {
+ return host.annotations().value(annotationKey);
+ }
+
+ @Override
+ public String toString() {
+ return host.toString();
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/api/InstanceHandler.java b/src/main/java/org/onosproject/cordvtn/api/InstanceHandler.java
new file mode 100644
index 0000000..3e5be2f
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/api/InstanceHandler.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.api;
+
+/**
+ * Handles service instance detection and removal.
+ */
+public interface InstanceHandler {
+
+ /**
+ * Handles newly detected instance.
+ *
+ * @param instance instance
+ */
+ void instanceDetected(Instance instance);
+
+ /**
+ * Handles removed instance.
+ *
+ * @param instance instance
+ */
+ void instanceRemoved(Instance instance);
+}
diff --git a/src/main/java/org/onosproject/cordvtn/api/NetworkAddress.java b/src/main/java/org/onosproject/cordvtn/api/NetworkAddress.java
new file mode 100644
index 0000000..23c7318
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/api/NetworkAddress.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.api;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Representation of a network address, which consists of IP address and prefix.
+ */
+public final class NetworkAddress {
+ private final IpAddress ip;
+ private final IpPrefix prefix;
+
+ /**
+ * Constructor for a given IP address and prefix.
+ *
+ * @param ip ip address
+ * @param prefix ip prefix
+ */
+ public NetworkAddress(IpAddress ip, IpPrefix prefix) {
+ this.ip = ip;
+ this.prefix = prefix;
+ }
+
+ /**
+ * Converts a CIDR notation string into a network address.
+ *
+ * @param cidr cidr
+ * @return network address
+ * @throws IllegalArgumentException if the cidr is not valid
+ */
+ public static NetworkAddress valueOf(String cidr) {
+ checkArgument(cidr.contains("/"));
+
+ IpAddress ipAddress = IpAddress.valueOf(cidr.split("/")[0]);
+ IpPrefix ipPrefix = IpPrefix.valueOf(cidr);
+
+ return new NetworkAddress(ipAddress, ipPrefix);
+ }
+
+ /**
+ * Returns the IP address value of the network address.
+ *
+ * @return ip address
+ */
+ public IpAddress ip() {
+ return this.ip;
+ }
+
+ /**
+ * Returns the IP prefix value of the network address.
+ *
+ * @return ip prefix
+ */
+ public IpPrefix prefix() {
+ return this.prefix;
+ }
+
+ /**
+ * Converts a network address to a CIDR notation.
+ *
+ * @return cidr notation string
+ */
+ public String cidr() {
+ return ip.toString() + "/" + prefix.prefixLength();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj instanceof NetworkAddress) {
+ NetworkAddress that = (NetworkAddress) obj;
+ if (Objects.equals(ip, that.ip) && Objects.equals(prefix, that.prefix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ip, prefix);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("IpAddress", ip)
+ .add("IpPrefix", prefix)
+ .toString();
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/api/SshAccessInfo.java b/src/main/java/org/onosproject/cordvtn/api/SshAccessInfo.java
new file mode 100644
index 0000000..7679348
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/api/SshAccessInfo.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.api;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.TpPort;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Representation of SSH access information.
+ */
+public final class SshAccessInfo {
+
+ private final Ip4Address remoteIp;
+ private final TpPort port;
+ private final String user;
+ private final String privateKey;
+
+ /**
+ * Creates a new SSH access information.
+ *
+ * @param remoteIp ssh remote ip address
+ * @param port ssh port number
+ * @param user user name
+ * @param privateKey path of ssh private key
+ */
+ public SshAccessInfo(Ip4Address remoteIp, TpPort port, String user, String privateKey) {
+ this.remoteIp = checkNotNull(remoteIp);
+ this.port = checkNotNull(port);
+ this.user = checkNotNull(user);
+ this.privateKey = checkNotNull(privateKey);
+ }
+
+ /**
+ * Returns the remote IP address.
+ *
+ * @return ip address
+ */
+ public Ip4Address remoteIp() {
+ return this.remoteIp;
+ }
+
+ /**
+ * Returns the port number.
+ *
+ * @return ssh port
+ */
+ public TpPort port() {
+ return this.port;
+ }
+
+ /**
+ * Returns the user name.
+ *
+ * @return user name
+ */
+ public String user() {
+ return this.user;
+ }
+
+ /**
+ * Returns the private key path.
+ *
+ * @return privateKey
+ */
+ public String privateKey() {
+ return privateKey;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj instanceof SshAccessInfo) {
+ SshAccessInfo that = (SshAccessInfo) obj;
+ if (Objects.equals(remoteIp, that.remoteIp) &&
+ Objects.equals(port, that.port) &&
+ Objects.equals(user, that.user) &&
+ Objects.equals(privateKey, that.privateKey)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(remoteIp, port, user, privateKey);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("remoteIp", remoteIp)
+ .add("port", port)
+ .add("user", user)
+ .add("privateKey", privateKey)
+ .toString();
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/api/package-info.java b/src/main/java/org/onosproject/cordvtn/api/package-info.java
new file mode 100644
index 0000000..7d41963
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/api/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * API for CORD VTN application.
+ */
+package org.onosproject.cordvtn.api;
\ No newline at end of file
diff --git a/src/main/java/org/onosproject/cordvtn/cli/CordVtnFlushRules.java b/src/main/java/org/onosproject/cordvtn/cli/CordVtnFlushRules.java
new file mode 100644
index 0000000..228d06c
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/cli/CordVtnFlushRules.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.cordvtn.cli;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cordvtn.impl.CordVtnPipeline;
+
+/**
+ * Deletes nodes from the service.
+ */
+@Command(scope = "onos", name = "cordvtn-flush-rules",
+ description = "Flush flow rules installed by CORD VTN")
+public class CordVtnFlushRules extends AbstractShellCommand {
+
+ @Override
+ protected void execute() {
+ CordVtnPipeline pipeline = AbstractShellCommand.get(CordVtnPipeline.class);
+ pipeline.flushRules();
+ print("Successfully flushed");
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeCheckCommand.java b/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeCheckCommand.java
new file mode 100644
index 0000000..782003e
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeCheckCommand.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.cordvtn.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cordvtn.api.CordVtnNode;
+import org.onosproject.cordvtn.impl.CordVtnNodeManager;
+import org.onosproject.net.Device;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.DriverService;
+
+/**
+ * Checks detailed node init state.
+ */
+@Command(scope = "onos", name = "cordvtn-node-check",
+ description = "Shows detailed node init state")
+public class CordVtnNodeCheckCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "hostname", description = "Hostname",
+ required = true, multiValued = false)
+ private String hostname = null;
+
+ @Override
+ protected void execute() {
+ CordVtnNodeManager nodeManager = AbstractShellCommand.get(CordVtnNodeManager.class);
+ DeviceService deviceService = AbstractShellCommand.get(DeviceService.class);
+
+ CordVtnNode node = nodeManager.getNodes()
+ .stream()
+ .filter(n -> n.hostname().equals(hostname))
+ .findFirst()
+ .orElse(null);
+
+ if (node == null) {
+ print("Cannot find %s from registered nodes", hostname);
+ return;
+ }
+
+ print(nodeManager.checkNodeInitState(node));
+
+ print("%n[DEBUG]");
+ Device device = deviceService.getDevice(node.intBrId());
+ String driver = get(DriverService.class).getDriver(device.id()).name();
+ print("%s available=%s driver=%s %s",
+ device.id(),
+ deviceService.isAvailable(device.id()),
+ driver,
+ device.annotations());
+
+ deviceService.getPorts(node.intBrId()).forEach(port -> {
+ Object portIsEnabled = port.isEnabled() ? "enabled" : "disabled";
+ print("port=%s state=%s %s",
+ port.number(),
+ portIsEnabled,
+ port.annotations());
+ });
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeDeleteCommand.java b/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeDeleteCommand.java
new file mode 100644
index 0000000..c3b49e1
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeDeleteCommand.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2015-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.cordvtn.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cordvtn.impl.CordVtnNodeManager;
+import org.onosproject.cordvtn.api.CordVtnNode;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Deletes nodes from the service.
+ */
+@Command(scope = "onos", name = "cordvtn-node-delete",
+ description = "Deletes nodes from CORD VTN service")
+public class CordVtnNodeDeleteCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "hostnames", description = "Hostname(s)",
+ required = true, multiValued = true)
+ private String[] hostnames = null;
+
+ @Override
+ protected void execute() {
+ CordVtnNodeManager nodeManager = AbstractShellCommand.get(CordVtnNodeManager.class);
+
+ for (String hostname : hostnames) {
+ CordVtnNode node;
+ try {
+ node = nodeManager.getNodes()
+ .stream()
+ .filter(n -> n.hostname().equals(hostname))
+ .findFirst().get();
+ } catch (NoSuchElementException e) {
+ print("Unable to find %s", hostname);
+ continue;
+ }
+
+ nodeManager.deleteNode(node);
+ }
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeInitCommand.java b/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeInitCommand.java
new file mode 100644
index 0000000..b47ec7d
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeInitCommand.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2015-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.cordvtn.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cordvtn.impl.CordVtnNodeManager;
+import org.onosproject.cordvtn.api.CordVtnNode;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Initializes nodes for CordVtn service.
+ */
+@Command(scope = "onos", name = "cordvtn-node-init",
+ description = "Initializes nodes for CORD VTN service")
+public class CordVtnNodeInitCommand extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "hostnames", description = "Hostname(s)",
+ required = true, multiValued = true)
+ private String[] hostnames = null;
+
+ @Override
+ protected void execute() {
+ CordVtnNodeManager nodeManager = AbstractShellCommand.get(CordVtnNodeManager.class);
+
+ for (String hostname : hostnames) {
+ CordVtnNode node;
+ try {
+ node = nodeManager.getNodes()
+ .stream()
+ .filter(n -> n.hostname().equals(hostname))
+ .findFirst().get();
+ } catch (NoSuchElementException e) {
+ print("Unable to find %s", hostname);
+ continue;
+ }
+
+ nodeManager.addOrUpdateNode(node);
+ }
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeListCommand.java b/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeListCommand.java
new file mode 100644
index 0000000..a6e4039
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/cli/CordVtnNodeListCommand.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2015-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.cordvtn.cli;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cordvtn.impl.CordVtnNodeManager;
+import org.onosproject.cordvtn.api.CordVtnNode;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Lists all nodes registered to the service.
+ */
+@Command(scope = "onos", name = "cordvtn-nodes",
+ description = "Lists all nodes registered in CORD VTN service")
+public class CordVtnNodeListCommand extends AbstractShellCommand {
+
+ private static final String COMPLETE = "COMPLETE";
+ private static final String INCOMPLETE = "INCOMPLETE";
+
+ @Override
+ protected void execute() {
+ CordVtnNodeManager nodeManager = AbstractShellCommand.get(CordVtnNodeManager.class);
+ List<CordVtnNode> nodes = nodeManager.getNodes();
+ Collections.sort(nodes, CordVtnNode.CORDVTN_NODE_COMPARATOR);
+
+ if (outputJson()) {
+ print("%s", json(nodeManager, nodes));
+ } else {
+ for (CordVtnNode node : nodes) {
+ print("hostname=%s, hostMgmtIp=%s, dpIp=%s, br-int=%s, dpIntf=%s, init=%s",
+ node.hostname(),
+ node.hostMgmtIp().cidr(),
+ node.dpIp().cidr(),
+ node.intBrId().toString(),
+ node.dpIntf(),
+ getState(nodeManager, node));
+ }
+ print("Total %s nodes", nodeManager.getNodeCount());
+ }
+ }
+
+ private JsonNode json(CordVtnNodeManager nodeManager, List<CordVtnNode> nodes) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode result = mapper.createArrayNode();
+ for (CordVtnNode node : nodes) {
+ result.add(mapper.createObjectNode()
+ .put("hostname", node.hostname())
+ .put("hostManagementIp", node.hostMgmtIp().cidr())
+ .put("dataPlaneIp", node.dpIp().cidr())
+ .put("bridgeId", node.intBrId().toString())
+ .put("dataPlaneInterface", node.dpIntf())
+ .put("init", getState(nodeManager, node)));
+ }
+ return result;
+ }
+
+ private String getState(CordVtnNodeManager nodeManager, CordVtnNode node) {
+ return nodeManager.isNodeInitComplete(node) ? COMPLETE : INCOMPLETE;
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/cli/package-info.java b/src/main/java/org/onosproject/cordvtn/cli/package-info.java
new file mode 100644
index 0000000..f5c69a1
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/cli/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Console commands to manage OVSDB nodes for cordvtn.
+ */
+package org.onosproject.cordvtn.cli;
\ No newline at end of file
diff --git a/src/main/java/org/onosproject/cordvtn/impl/CordVtn.java b/src/main/java/org/onosproject/cordvtn/impl/CordVtn.java
new file mode 100644
index 0000000..efa0e4a
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/impl/CordVtn.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright 2015-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.impl;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onosproject.cordvtn.api.CordVtnNode;
+import org.onosproject.cordvtn.api.CordVtnService;
+import org.onosproject.cordvtn.api.Instance;
+import org.onosproject.core.DefaultGroupId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.PortNumber;
+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.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.DefaultGroupKey;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.xosclient.api.VtnService;
+import org.onosproject.xosclient.api.VtnServiceId;
+import org.slf4j.Logger;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.cordvtn.impl.CordVtnPipeline.*;
+import static org.onosproject.net.group.DefaultGroupBucket.createSelectGroupBucket;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provisions service dependency capabilities between network services.
+ */
+@Component(immediate = true)
+@Service
+public class CordVtn extends CordVtnInstanceHandler implements CordVtnService {
+
+ protected final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected GroupService groupService;
+
+ @Activate
+ protected void activate() {
+ eventExecutor = newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn", "event-handler"));
+ hostListener = new InternalHostListener();
+ super.activate();
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ super.deactivate();
+ }
+
+ @Override
+ public void createServiceDependency(VtnServiceId tServiceId, VtnServiceId pServiceId,
+ boolean isBidirectional) {
+ VtnService tService = getVtnService(tServiceId);
+ VtnService pService = getVtnService(pServiceId);
+
+ if (tService == null || pService == null) {
+ log.error("Failed to create dependency between {} and {}",
+ tServiceId, pServiceId);
+ return;
+ }
+
+ log.info("Created dependency between {} and {}", tService.name(), pService.name());
+ serviceDependencyRules(tService, pService, isBidirectional, true);
+ }
+
+ @Override
+ public void removeServiceDependency(VtnServiceId tServiceId, VtnServiceId pServiceId) {
+ VtnService tService = getVtnService(tServiceId);
+ VtnService pService = getVtnService(pServiceId);
+
+ if (tService == null || pService == null) {
+ log.error("Failed to remove dependency between {} and {}",
+ tServiceId, pServiceId);
+ return;
+ }
+
+ log.info("Removed dependency between {} and {}", tService.name(), pService.name());
+ serviceDependencyRules(tService, pService, true, false);
+ }
+
+ @Override
+ public void instanceDetected(Instance instance) {
+ VtnService service = getVtnService(instance.serviceId());
+ if (service == null) {
+ return;
+ }
+
+ // TODO get bidirectional information from XOS once XOS supports
+ service.tenantServices().stream().forEach(
+ tServiceId -> createServiceDependency(tServiceId, service.id(), true));
+ service.providerServices().stream().forEach(
+ pServiceId -> createServiceDependency(service.id(), pServiceId, true));
+
+ updateProviderServiceInstances(service);
+ }
+
+ @Override
+ public void instanceRemoved(Instance instance) {
+ VtnService service = getVtnService(instance.serviceId());
+ if (service == null) {
+ return;
+ }
+
+ if (!service.providerServices().isEmpty()) {
+ removeInstanceFromTenantService(instance, service);
+ }
+ if (!service.tenantServices().isEmpty()) {
+ updateProviderServiceInstances(service);
+ }
+ }
+
+ private void updateProviderServiceInstances(VtnService service) {
+ GroupKey groupKey = getGroupKey(service.id());
+
+ Set<DeviceId> devices = nodeManager.completeNodes().stream()
+ .map(CordVtnNode::intBrId)
+ .collect(Collectors.toSet());
+
+ for (DeviceId deviceId : devices) {
+ Group group = groupService.getGroup(deviceId, groupKey);
+ if (group == null) {
+ log.trace("No group exists for service {} in {}", service.id(), deviceId);
+ continue;
+ }
+
+ List<GroupBucket> oldBuckets = group.buckets().buckets();
+ List<GroupBucket> newBuckets = getServiceGroupBuckets(
+ deviceId, service.vni(), getInstances(service.id())).buckets();
+
+ if (oldBuckets.equals(newBuckets)) {
+ continue;
+ }
+
+ List<GroupBucket> bucketsToRemove = Lists.newArrayList(oldBuckets);
+ bucketsToRemove.removeAll(newBuckets);
+ if (!bucketsToRemove.isEmpty()) {
+ groupService.removeBucketsFromGroup(
+ deviceId,
+ groupKey,
+ new GroupBuckets(bucketsToRemove),
+ groupKey, appId);
+ }
+
+ List<GroupBucket> bucketsToAdd = Lists.newArrayList(newBuckets);
+ bucketsToAdd.removeAll(oldBuckets);
+ if (!bucketsToAdd.isEmpty()) {
+ groupService.addBucketsToGroup(
+ deviceId,
+ groupKey,
+ new GroupBuckets(bucketsToAdd),
+ groupKey, appId);
+ }
+ }
+ }
+
+ private void removeInstanceFromTenantService(Instance instance, VtnService service) {
+ service.providerServices().stream().forEach(pServiceId -> {
+ Map<DeviceId, Set<PortNumber>> inPorts = Maps.newHashMap();
+ Map<DeviceId, GroupId> outGroups = Maps.newHashMap();
+
+ inPorts.put(instance.deviceId(), Sets.newHashSet(instance.portNumber()));
+ outGroups.put(instance.deviceId(), getGroupId(pServiceId, instance.deviceId()));
+
+ inServiceRule(inPorts, outGroups, false);
+ });
+ }
+
+ private void serviceDependencyRules(VtnService tService, VtnService pService,
+ boolean isBidirectional, boolean install) {
+ Map<DeviceId, GroupId> outGroups = Maps.newHashMap();
+ Map<DeviceId, Set<PortNumber>> inPorts = Maps.newHashMap();
+
+ nodeManager.completeNodes().stream().forEach(node -> {
+ DeviceId deviceId = node.intBrId();
+ GroupId groupId = createServiceGroup(deviceId, pService);
+ outGroups.put(deviceId, groupId);
+
+ Set<PortNumber> tServiceInstances = getInstances(tService.id())
+ .stream()
+ .filter(instance -> instance.deviceId().equals(deviceId))
+ .map(Instance::portNumber)
+ .collect(Collectors.toSet());
+ inPorts.put(deviceId, tServiceInstances);
+ });
+
+ Ip4Prefix srcRange = tService.subnet().getIp4Prefix();
+ Ip4Prefix dstRange = pService.subnet().getIp4Prefix();
+
+ indirectAccessRule(srcRange, pService.serviceIp().getIp4Address(), outGroups, install);
+ directAccessRule(srcRange, dstRange, install);
+ if (isBidirectional) {
+ directAccessRule(dstRange, srcRange, install);
+ }
+ inServiceRule(inPorts, outGroups, install);
+ }
+
+ private void indirectAccessRule(Ip4Prefix srcRange, Ip4Address serviceIp,
+ Map<DeviceId, GroupId> outGroups, boolean install) {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPSrc(srcRange)
+ .matchIPDst(serviceIp.toIpPrefix())
+ .build();
+
+ for (Map.Entry<DeviceId, GroupId> outGroup : outGroups.entrySet()) {
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .group(outGroup.getValue())
+ .build();
+
+ FlowRule flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_HIGH)
+ .forDevice(outGroup.getKey())
+ .forTable(TABLE_ACCESS_TYPE)
+ .makePermanent()
+ .build();
+
+ pipeline.processFlowRule(install, flowRule);
+ }
+ }
+
+ private void directAccessRule(Ip4Prefix srcRange, Ip4Prefix dstRange, boolean install) {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPSrc(srcRange)
+ .matchIPDst(dstRange)
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .transition(TABLE_DST_IP)
+ .build();
+
+ nodeManager.completeNodes().stream().forEach(node -> {
+ DeviceId deviceId = node.intBrId();
+ FlowRule flowRuleDirect = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_DEFAULT)
+ .forDevice(deviceId)
+ .forTable(TABLE_ACCESS_TYPE)
+ .makePermanent()
+ .build();
+
+ pipeline.processFlowRule(install, flowRuleDirect);
+ });
+ }
+
+ private void inServiceRule(Map<DeviceId, Set<PortNumber>> inPorts,
+ Map<DeviceId, GroupId> outGroups, boolean install) {
+ for (Map.Entry<DeviceId, Set<PortNumber>> entry : inPorts.entrySet()) {
+ Set<PortNumber> ports = entry.getValue();
+ DeviceId deviceId = entry.getKey();
+
+ GroupId groupId = outGroups.get(deviceId);
+ if (groupId == null) {
+ continue;
+ }
+
+ ports.stream().forEach(port -> {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchInPort(port)
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .group(groupId)
+ .build();
+
+ FlowRule flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_DEFAULT)
+ .forDevice(deviceId)
+ .forTable(TABLE_IN_SERVICE)
+ .makePermanent()
+ .build();
+
+ pipeline.processFlowRule(install, flowRule);
+ });
+ }
+ }
+
+ private GroupId getGroupId(VtnServiceId serviceId, DeviceId deviceId) {
+ return new DefaultGroupId(Objects.hash(serviceId, deviceId));
+ }
+
+ private GroupKey getGroupKey(VtnServiceId serviceId) {
+ return new DefaultGroupKey(serviceId.id().getBytes());
+ }
+
+ private GroupId createServiceGroup(DeviceId deviceId, VtnService service) {
+ GroupKey groupKey = getGroupKey(service.id());
+ Group group = groupService.getGroup(deviceId, groupKey);
+ GroupId groupId = getGroupId(service.id(), deviceId);
+
+ if (group != null) {
+ log.debug("Group {} is already exist in {}", service.id(), deviceId);
+ return groupId;
+ }
+
+ GroupBuckets buckets = getServiceGroupBuckets(
+ deviceId, service.vni(), getInstances(service.id()));
+ GroupDescription groupDescription = new DefaultGroupDescription(
+ deviceId,
+ GroupDescription.Type.SELECT,
+ buckets,
+ groupKey,
+ groupId.id(),
+ appId);
+
+ groupService.addGroup(groupDescription);
+ return groupId;
+ }
+
+ private GroupBuckets getServiceGroupBuckets(DeviceId deviceId, long tunnelId,
+ Set<Instance> instances) {
+ List<GroupBucket> buckets = Lists.newArrayList();
+ instances.stream().forEach(instance -> {
+ Ip4Address tunnelIp = nodeManager.dpIp(instance.deviceId()).getIp4Address();
+ TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+
+ if (deviceId.equals(instance.deviceId())) {
+ tBuilder.setEthDst(instance.mac())
+ .setOutput(instance.portNumber());
+ } else {
+ ExtensionTreatment tunnelDst =
+ pipeline.tunnelDstTreatment(deviceId, tunnelIp);
+ tBuilder.setEthDst(instance.mac())
+ .extension(tunnelDst, deviceId)
+ .setTunnelId(tunnelId)
+ .setOutput(nodeManager.tunnelPort(instance.deviceId()));
+ }
+ buckets.add(createSelectGroupBucket(tBuilder.build()));
+ });
+ return new GroupBuckets(buckets);
+ }
+
+ private class InternalHostListener implements HostListener {
+
+ @Override
+ public void event(HostEvent event) {
+ Host host = event.subject();
+ if (!mastershipService.isLocalMaster(host.location().deviceId())) {
+ // do not allow to proceed without mastership
+ return;
+ }
+
+ Instance instance = Instance.of(host);
+ switch (event.type()) {
+ case HOST_UPDATED:
+ case HOST_ADDED:
+ eventExecutor.execute(() -> instanceDetected(instance));
+ break;
+ case HOST_REMOVED:
+ eventExecutor.execute(() -> instanceRemoved(instance));
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/impl/CordVtnArpProxy.java b/src/main/java/org/onosproject/cordvtn/impl/CordVtnArpProxy.java
new file mode 100644
index 0000000..8200fcc
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/impl/CordVtnArpProxy.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.impl;
+
+import com.google.common.collect.Maps;
+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.onosproject.cordvtn.api.Instance;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.Host;
+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.host.HostService;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.packet.PacketService;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Handles ARP requests for virtual network service IPs.
+ */
+public class CordVtnArpProxy {
+ protected final Logger log = getLogger(getClass());
+
+ private final ApplicationId appId;
+ private final PacketService packetService;
+ private final HostService hostService;
+
+ private final Map<Ip4Address, MacAddress> gateways = Maps.newConcurrentMap();
+
+ /**
+ * Default constructor.
+ *
+ * @param appId application id
+ * @param packetService packet service
+ * @param hostService host service reference
+ */
+ public CordVtnArpProxy(ApplicationId appId, PacketService packetService, HostService hostService) {
+ this.appId = appId;
+ this.packetService = packetService;
+ this.hostService = hostService;
+ }
+
+ /**
+ * Requests ARP packet.
+ */
+ public void requestPacket() {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(EthType.EtherType.ARP.ethType().toShort())
+ .build();
+
+ packetService.requestPackets(selector,
+ PacketPriority.CONTROL,
+ appId,
+ Optional.empty());
+ }
+
+ /**
+ * Cancels ARP packet.
+ */
+ public void cancelPacket() {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(EthType.EtherType.ARP.ethType().toShort())
+ .build();
+
+ packetService.cancelPackets(selector,
+ PacketPriority.CONTROL,
+ appId,
+ Optional.empty());
+ }
+
+ /**
+ * Adds a given gateway IP and MAC address to this ARP proxy.
+ *
+ * @param gatewayIp gateway ip address
+ * @param gatewayMac gateway mac address
+ */
+ public void addGateway(IpAddress gatewayIp, MacAddress gatewayMac) {
+ checkNotNull(gatewayIp);
+ checkNotNull(gatewayMac);
+ gateways.put(gatewayIp.getIp4Address(), gatewayMac);
+ }
+
+ /**
+ * Removes a given service IP address from this ARP proxy.
+ *
+ * @param gatewayIp gateway ip address
+ */
+ public void removeGateway(IpAddress gatewayIp) {
+ checkNotNull(gatewayIp);
+ gateways.remove(gatewayIp.getIp4Address());
+ }
+
+ /**
+ * Emits ARP reply with fake MAC address for a given ARP request.
+ * It only handles requests for the registered service IPs, and the other
+ * requests can be handled by other ARP handlers like openstackSwitching or
+ * proxyArp, for example.
+ *
+ * @param context packet context
+ * @param ethPacket ethernet packet
+ */
+ public void processArpPacket(PacketContext context, Ethernet ethPacket) {
+ ARP arpPacket = (ARP) ethPacket.getPayload();
+ if (arpPacket.getOpCode() != ARP.OP_REQUEST) {
+ return;
+ }
+
+ Ip4Address targetIp = Ip4Address.valueOf(arpPacket.getTargetProtocolAddress());
+
+ MacAddress gatewayMac = gateways.get(targetIp);
+ MacAddress replyMac = gatewayMac != null ? gatewayMac : getMacFromHostService(targetIp);
+
+ if (replyMac.equals(MacAddress.NONE)) {
+ log.debug("Failed to find MAC for {}", targetIp.toString());
+ context.block();
+ return;
+ }
+
+ log.trace("Send ARP reply for {} with {}", targetIp.toString(), replyMac.toString());
+ Ethernet ethReply = ARP.buildArpReply(
+ targetIp,
+ 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())));
+
+ context.block();
+ }
+
+ /**
+ * Emits gratuitous ARP when a gateway mac address has been changed.
+ *
+ * @param gatewayIp gateway ip address to update MAC
+ * @param instances set of instances to send gratuitous ARP packet
+ */
+ public void sendGratuitousArpForGateway(IpAddress gatewayIp, Set<Instance> instances) {
+ MacAddress gatewayMac = gateways.get(gatewayIp.getIp4Address());
+ if (gatewayMac == null) {
+ log.debug("Gateway {} is not registered to ARP proxy", gatewayIp.toString());
+ return;
+ }
+
+ Ethernet ethArp = buildGratuitousArp(gatewayIp.getIp4Address(), gatewayMac);
+ instances.stream().forEach(instance -> {
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(instance.portNumber())
+ .build();
+
+ packetService.emit(new DefaultOutboundPacket(
+ instance.deviceId(),
+ treatment,
+ ByteBuffer.wrap(ethArp.serialize())));
+ });
+ }
+
+ /**
+ * Builds gratuitous ARP packet with a given IP and MAC address.
+ *
+ * @param ip ip address for TPA and SPA
+ * @param mac new mac address
+ * @return ethernet packet
+ */
+ private Ethernet buildGratuitousArp(IpAddress ip, MacAddress mac) {
+ Ethernet eth = new Ethernet();
+
+ eth.setEtherType(Ethernet.TYPE_ARP);
+ eth.setSourceMACAddress(mac);
+ eth.setDestinationMACAddress(MacAddress.BROADCAST);
+
+ ARP arp = new ARP();
+ arp.setOpCode(ARP.OP_REQUEST);
+ arp.setHardwareType(ARP.HW_TYPE_ETHERNET);
+ arp.setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH);
+ arp.setProtocolType(ARP.PROTO_TYPE_IP);
+ arp.setProtocolAddressLength((byte) Ip4Address.BYTE_LENGTH);
+
+ arp.setSenderHardwareAddress(mac.toBytes());
+ arp.setTargetHardwareAddress(MacAddress.BROADCAST.toBytes());
+ arp.setSenderProtocolAddress(ip.getIp4Address().toOctets());
+ arp.setTargetProtocolAddress(ip.getIp4Address().toOctets());
+
+ eth.setPayload(arp);
+ return eth;
+ }
+
+ /**
+ * Returns MAC address of a host with a given target IP address by asking to
+ * host service. It does not support overlapping IP.
+ *
+ * @param targetIp target ip
+ * @return mac address, or NONE mac address if it fails to find the mac
+ */
+ private MacAddress getMacFromHostService(IpAddress targetIp) {
+ checkNotNull(targetIp);
+
+ Host host = hostService.getHostsByIp(targetIp)
+ .stream()
+ .findFirst()
+ .orElse(null);
+
+ if (host != null) {
+ log.trace("Found MAC from host service for {}", targetIp.toString());
+ return host.mac();
+ } else {
+ return MacAddress.NONE;
+ }
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/impl/CordVtnInstanceHandler.java b/src/main/java/org/onosproject/cordvtn/impl/CordVtnInstanceHandler.java
new file mode 100644
index 0000000..197f8df
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/impl/CordVtnInstanceHandler.java
@@ -0,0 +1,536 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.impl;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip4Prefix;
+import org.onosproject.cordvtn.api.CordVtnConfig;
+import org.onosproject.cordvtn.api.CordVtnNode;
+import org.onosproject.cordvtn.api.CordVtnService;
+import org.onosproject.cordvtn.api.Instance;
+import org.onosproject.cordvtn.api.InstanceHandler;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.Host;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigRegistry;
+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.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+import org.onosproject.net.host.HostService;
+import org.onosproject.xosclient.api.OpenStackAccess;
+import org.onosproject.xosclient.api.VtnService;
+import org.onosproject.xosclient.api.VtnServiceApi;
+import org.onosproject.xosclient.api.VtnServiceId;
+import org.onosproject.xosclient.api.XosAccess;
+import org.onosproject.xosclient.api.XosClientService;
+import org.slf4j.Logger;
+
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.cordvtn.impl.CordVtnPipeline.*;
+import static org.onosproject.xosclient.api.VtnService.NetworkType.MANAGEMENT;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provides default virtual network connectivity for service instances.
+ */
+@Component(immediate = true)
+public abstract class CordVtnInstanceHandler implements InstanceHandler {
+
+ protected final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipService mastershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected NetworkConfigRegistry configRegistry;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostService hostService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected XosClientService xosClient;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CordVtnNodeManager nodeManager;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CordVtnPipeline pipeline;
+
+ protected static final String OPENSTACK_ACCESS_ERROR = "OpenStack access is not configured";
+ protected static final String XOS_ACCESS_ERROR = "XOS access is not configured";
+
+ protected XosAccess xosAccess = null;
+ protected OpenStackAccess osAccess = null;
+ protected ApplicationId appId;
+ protected VtnService.ServiceType serviceType;
+ protected ExecutorService eventExecutor;
+
+ protected HostListener hostListener = new InternalHostListener();
+ protected NetworkConfigListener configListener = new InternalConfigListener();
+
+ protected void activate() {
+ // sub class should set service type and event executor in its activate method
+ appId = coreService.registerApplication(CordVtnService.CORDVTN_APP_ID);
+
+ hostService.addListener(hostListener);
+ configRegistry.addListener(configListener);
+
+ log.info("Started");
+ }
+
+ protected void deactivate() {
+ hostService.removeListener(hostListener);
+ configRegistry.removeListener(configListener);
+ eventExecutor.shutdown();
+
+ log.info("Stopped");
+ }
+
+ @Override
+ public void instanceDetected(Instance instance) {
+ log.info("Instance is detected {}", instance);
+
+ VtnService service = getVtnService(instance.serviceId());
+ if (service == null) {
+ log.warn("Failed to get VtnService for {}", instance);
+ return;
+ }
+
+ if (service.networkType().equals(MANAGEMENT)) {
+ managementNetworkRules(instance, service, true);
+ }
+
+ defaultConnectionRules(instance, service, true);
+ }
+
+ @Override
+ public void instanceRemoved(Instance instance) {
+ log.info("Instance is removed {}", instance);
+
+ VtnService service = getVtnService(instance.serviceId());
+ if (service == null) {
+ log.warn("Failed to get VtnService for {}", instance);
+ return;
+ }
+
+ if (service.networkType().equals(MANAGEMENT)) {
+ managementNetworkRules(instance, service, false);
+ }
+
+ // TODO check if any stale management network rules are
+ defaultConnectionRules(instance, service, false);
+ }
+
+ protected VtnService getVtnService(VtnServiceId serviceId) {
+ checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
+ checkNotNull(xosAccess, XOS_ACCESS_ERROR);
+
+ // TODO remove openstack access when XOS provides all information
+ VtnServiceApi serviceApi = xosClient.getClient(xosAccess).vtnService();
+ VtnService service = serviceApi.service(serviceId, osAccess);
+ if (service == null) {
+ log.warn("Failed to get VtnService for {}", serviceId);
+ }
+ return service;
+ }
+
+ protected Set<Instance> getInstances(VtnServiceId serviceId) {
+ return StreamSupport.stream(hostService.getHosts().spliterator(), false)
+ .filter(host -> Objects.equals(
+ serviceId.id(),
+ host.annotations().value(Instance.SERVICE_ID)))
+ .map(Instance::of)
+ .collect(Collectors.toSet());
+ }
+
+ private void defaultConnectionRules(Instance instance, VtnService service, boolean install) {
+ long vni = service.vni();
+ Ip4Prefix serviceIpRange = service.subnet().getIp4Prefix();
+
+ inPortRule(instance, install);
+ dstIpRule(instance, vni, install);
+ tunnelInRule(instance, vni, install);
+
+ if (install) {
+ directAccessRule(serviceIpRange, serviceIpRange, true);
+ serviceIsolationRule(serviceIpRange, true);
+ } else if (getInstances(service.id()).isEmpty()) {
+ directAccessRule(serviceIpRange, serviceIpRange, false);
+ serviceIsolationRule(serviceIpRange, false);
+ }
+ }
+
+ private void managementNetworkRules(Instance instance, VtnService service, boolean install) {
+
+ managementPerInstanceRule(instance, install);
+ if (install) {
+ managementBaseRule(instance, service, true);
+ } else if (!hostService.getConnectedHosts(instance.deviceId()).stream()
+ .filter(host -> Instance.of(host).serviceId().equals(service.id()))
+ .findAny()
+ .isPresent()) {
+ managementBaseRule(instance, service, false);
+ }
+ }
+
+ private void managementBaseRule(Instance instance, VtnService service, boolean install) {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_ARP)
+ .matchArpTpa(service.serviceIp().getIp4Address())
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.LOCAL)
+ .build();
+
+ FlowRule flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_MANAGEMENT)
+ .forDevice(instance.deviceId())
+ .forTable(TABLE_ZERO)
+ .makePermanent()
+ .build();
+
+ pipeline.processFlowRule(install, flowRule);
+
+ selector = DefaultTrafficSelector.builder()
+ .matchInPort(PortNumber.LOCAL)
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(service.subnet())
+ .build();
+
+ treatment = DefaultTrafficTreatment.builder()
+ .transition(TABLE_DST_IP)
+ .build();
+
+ flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_MANAGEMENT)
+ .forDevice(instance.deviceId())
+ .forTable(TABLE_ZERO)
+ .makePermanent()
+ .build();
+
+ pipeline.processFlowRule(install, flowRule);
+
+ selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(service.serviceIp().toIpPrefix())
+ .build();
+
+ treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.LOCAL)
+ .build();
+
+ flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_MANAGEMENT)
+ .forDevice(instance.deviceId())
+ .forTable(TABLE_ACCESS_TYPE)
+ .makePermanent()
+ .build();
+
+ pipeline.processFlowRule(install, flowRule);
+ }
+
+ private void managementPerInstanceRule(Instance instance, boolean install) {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchInPort(PortNumber.LOCAL)
+ .matchEthType(Ethernet.TYPE_ARP)
+ .matchArpTpa(instance.ipAddress().getIp4Address())
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(instance.portNumber())
+ .build();
+
+ FlowRule flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_MANAGEMENT)
+ .forDevice(instance.deviceId())
+ .forTable(TABLE_ZERO)
+ .makePermanent()
+ .build();
+
+ pipeline.processFlowRule(install, flowRule);
+ }
+
+ private void inPortRule(Instance instance, boolean install) {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchInPort(instance.portNumber())
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPSrc(instance.ipAddress().toIpPrefix())
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .transition(TABLE_ACCESS_TYPE)
+ .build();
+
+
+ FlowRule flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_DEFAULT)
+ .forDevice(instance.deviceId())
+ .forTable(TABLE_IN_PORT)
+ .makePermanent()
+ .build();
+
+ pipeline.processFlowRule(install, flowRule);
+
+ selector = DefaultTrafficSelector.builder()
+ .matchInPort(instance.portNumber())
+ .build();
+
+ treatment = DefaultTrafficTreatment.builder()
+ .transition(TABLE_IN_SERVICE)
+ .build();
+
+ flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_LOW)
+ .forDevice(instance.deviceId())
+ .forTable(TABLE_IN_PORT)
+ .makePermanent()
+ .build();
+
+ pipeline.processFlowRule(install, flowRule);
+ }
+
+ private void dstIpRule(Instance instance, long vni, boolean install) {
+ Ip4Address tunnelIp = nodeManager.dpIp(instance.deviceId()).getIp4Address();
+
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(instance.ipAddress().toIpPrefix())
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setEthDst(instance.mac())
+ .setOutput(instance.portNumber())
+ .build();
+
+ FlowRule flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_DEFAULT)
+ .forDevice(instance.deviceId())
+ .forTable(TABLE_DST_IP)
+ .makePermanent()
+ .build();
+
+ pipeline.processFlowRule(install, flowRule);
+
+ for (CordVtnNode node : nodeManager.completeNodes()) {
+ if (node.intBrId().equals(instance.deviceId())) {
+ continue;
+ }
+
+ ExtensionTreatment tunnelDst = pipeline.tunnelDstTreatment(node.intBrId(), tunnelIp);
+ if (tunnelDst == null) {
+ continue;
+ }
+
+ treatment = DefaultTrafficTreatment.builder()
+ .setEthDst(instance.mac())
+ .setTunnelId(vni)
+ .extension(tunnelDst, node.intBrId())
+ .setOutput(nodeManager.tunnelPort(node.intBrId()))
+ .build();
+
+ flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_DEFAULT)
+ .forDevice(node.intBrId())
+ .forTable(TABLE_DST_IP)
+ .makePermanent()
+ .build();
+
+ pipeline.processFlowRule(install, flowRule);
+ }
+ }
+
+ private void tunnelInRule(Instance instance, long vni, boolean install) {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchTunnelId(vni)
+ .matchEthDst(instance.mac())
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(instance.portNumber())
+ .build();
+
+ FlowRule flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_DEFAULT)
+ .forDevice(instance.deviceId())
+ .forTable(TABLE_TUNNEL_IN)
+ .makePermanent()
+ .build();
+
+ pipeline.processFlowRule(install, flowRule);
+ }
+
+ private void directAccessRule(Ip4Prefix srcRange, Ip4Prefix dstRange, boolean install) {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPSrc(srcRange)
+ .matchIPDst(dstRange)
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .transition(TABLE_DST_IP)
+ .build();
+
+
+ nodeManager.completeNodes().stream().forEach(node -> {
+ FlowRule flowRuleDirect = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_DEFAULT)
+ .forDevice(node.intBrId())
+ .forTable(TABLE_ACCESS_TYPE)
+ .makePermanent()
+ .build();
+
+ pipeline.processFlowRule(install, flowRuleDirect);
+ });
+ }
+
+ private void serviceIsolationRule(Ip4Prefix dstRange, boolean install) {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(dstRange)
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .drop()
+ .build();
+
+ nodeManager.completeNodes().stream().forEach(node -> {
+ FlowRule flowRuleDirect = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_LOW)
+ .forDevice(node.intBrId())
+ .forTable(TABLE_ACCESS_TYPE)
+ .makePermanent()
+ .build();
+
+ pipeline.processFlowRule(install, flowRuleDirect);
+ });
+ }
+
+ protected void readConfiguration() {
+ CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
+ if (config == null) {
+ log.debug("No configuration found");
+ return;
+ }
+ osAccess = config.openstackAccess();
+ xosAccess = config.xosAccess();
+ }
+
+ public class InternalHostListener implements HostListener {
+
+ @Override
+ public void event(HostEvent event) {
+ Host host = event.subject();
+ if (!mastershipService.isLocalMaster(host.location().deviceId())) {
+ // do not allow to proceed without mastership
+ return;
+ }
+
+ Instance instance = Instance.of(host);
+ if (!Objects.equals(instance.serviceType(), serviceType)) {
+ // not my service instance, do nothing
+ return;
+ }
+
+ switch (event.type()) {
+ case HOST_UPDATED:
+ case HOST_ADDED:
+ eventExecutor.execute(() -> instanceDetected(instance));
+ break;
+ case HOST_REMOVED:
+ eventExecutor.execute(() -> instanceRemoved(instance));
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ public class InternalConfigListener implements NetworkConfigListener {
+
+ @Override
+ public void event(NetworkConfigEvent event) {
+ if (!event.configClass().equals(CordVtnConfig.class)) {
+ return;
+ }
+
+ switch (event.type()) {
+ case CONFIG_ADDED:
+ case CONFIG_UPDATED:
+ readConfiguration();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/impl/CordVtnInstanceManager.java b/src/main/java/org/onosproject/cordvtn/impl/CordVtnInstanceManager.java
new file mode 100644
index 0000000..3ca7361
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/impl/CordVtnInstanceManager.java
@@ -0,0 +1,438 @@
+/*
+ * Copyright 2015-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.impl;
+
+import com.google.common.collect.Sets;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.cordvtn.api.CordVtnConfig;
+import org.onosproject.cordvtn.api.CordVtnService;
+import org.onosproject.cordvtn.api.Instance;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.dhcp.DhcpService;
+import org.onosproject.dhcp.IpAssignment;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.Port;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.basics.SubjectFactories;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.host.DefaultHostDescription;
+import org.onosproject.net.host.HostDescription;
+import org.onosproject.net.host.HostEvent;
+import org.onosproject.net.host.HostListener;
+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.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.onosproject.net.provider.AbstractProvider;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.xosclient.api.OpenStackAccess;
+import org.onosproject.xosclient.api.VtnPort;
+import org.onosproject.xosclient.api.VtnPortApi;
+import org.onosproject.xosclient.api.VtnService;
+import org.onosproject.xosclient.api.VtnServiceApi;
+import org.onosproject.xosclient.api.VtnServiceId;
+import org.onosproject.xosclient.api.XosAccess;
+import org.onosproject.xosclient.api.XosClientService;
+import org.slf4j.Logger;
+
+import java.util.Date;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.cordvtn.api.Instance.*;
+import static org.onosproject.dhcp.IpAssignment.AssignmentStatus.Option_RangeNotEnforced;
+import static org.onosproject.xosclient.api.VtnService.NetworkType.MANAGEMENT;
+import static org.onosproject.xosclient.api.VtnService.NetworkType.PRIVATE;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Adds or removes instances to network services.
+ */
+@Component(immediate = true)
+@Service(value = CordVtnInstanceManager.class)
+public class CordVtnInstanceManager extends AbstractProvider implements HostProvider {
+
+ protected final Logger log = getLogger(getClass());
+
+ private static final String XOS_ACCESS_ERROR = "XOS access is not configured";
+ private static final String OPENSTACK_ACCESS_ERROR = "OpenStack access is not configured";
+ private static final Ip4Address DEFAULT_DNS = Ip4Address.valueOf("8.8.8.8");
+ private static final int DHCP_INFINITE_LEASE = -1;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected NetworkConfigRegistry configRegistry;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostProviderRegistry hostProviderRegistry;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostService hostService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected PacketService packetService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DhcpService dhcpService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipService mastershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected XosClientService xosClient;
+
+ private final ConfigFactory configFactory =
+ new ConfigFactory(SubjectFactories.APP_SUBJECT_FACTORY, CordVtnConfig.class, "cordvtn") {
+ @Override
+ public CordVtnConfig createConfig() {
+ return new CordVtnConfig();
+ }
+ };
+
+ private final ExecutorService eventExecutor =
+ newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn-instance", "event-handler"));
+ private final PacketProcessor packetProcessor = new InternalPacketProcessor();
+ private final HostListener hostListener = new InternalHostListener();
+ private final NetworkConfigListener configListener = new InternalConfigListener();
+
+ private ApplicationId appId;
+ private HostProviderService hostProvider;
+ private CordVtnArpProxy arpProxy; // TODO make it a component service
+ private MacAddress privateGatewayMac = MacAddress.NONE;
+ private XosAccess xosAccess = null;
+ private OpenStackAccess osAccess = null;
+
+ /**
+ * Creates an cordvtn host location provider.
+ */
+ public CordVtnInstanceManager() {
+ super(new ProviderId("host", CordVtnService.CORDVTN_APP_ID));
+ }
+
+ @Activate
+ protected void activate() {
+ appId = coreService.registerApplication(CordVtnService.CORDVTN_APP_ID);
+
+ arpProxy = new CordVtnArpProxy(appId, packetService, hostService);
+ packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
+ arpProxy.requestPacket();
+
+ hostService.addListener(hostListener);
+ hostProvider = hostProviderRegistry.register(this);
+
+ configRegistry.registerConfigFactory(configFactory);
+ configRegistry.addListener(configListener);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ hostProviderRegistry.unregister(this);
+ hostService.removeListener(hostListener);
+
+ packetService.removeProcessor(packetProcessor);
+
+ configRegistry.unregisterConfigFactory(configFactory);
+ configRegistry.removeListener(configListener);
+
+ eventExecutor.shutdown();
+ log.info("Stopped");
+ }
+
+ @Override
+ public void triggerProbe(Host host) {
+ /*
+ * Note: In CORD deployment, we assume that all hosts are configured.
+ * Therefore no probe is required.
+ */
+ }
+
+ /**
+ * Adds a service instance at a given connect point.
+ *
+ * @param connectPoint connect point of the instance
+ */
+ public void addInstance(ConnectPoint connectPoint) {
+ Port port = deviceService.getPort(connectPoint.deviceId(), connectPoint.port());
+ if (port == null) {
+ log.debug("No port found from {}", connectPoint);
+ return;
+ }
+
+ VtnPort vtnPort = getVtnPort(port.annotations().value("portName"));
+ if (vtnPort == null) {
+ return;
+ }
+
+ VtnService vtnService = getVtnService(vtnPort.serviceId());
+ if (vtnService == null) {
+ return;
+ }
+
+ // Added CREATE_TIME intentionally to trigger HOST_UPDATED event for the
+ // existing instances.
+ DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
+ .set(SERVICE_TYPE, vtnService.serviceType().toString())
+ .set(SERVICE_ID, vtnPort.serviceId().id())
+ .set(PORT_ID, vtnPort.id().id())
+ .set(CREATE_TIME, String.valueOf(System.currentTimeMillis()));
+
+ HostDescription hostDesc = new DefaultHostDescription(
+ vtnPort.mac(),
+ VlanId.NONE,
+ new HostLocation(connectPoint, System.currentTimeMillis()),
+ Sets.newHashSet(vtnPort.ip()),
+ annotations.build());
+
+ HostId hostId = HostId.hostId(vtnPort.mac());
+ hostProvider.hostDetected(hostId, hostDesc, false);
+ }
+
+ /**
+ * Adds a service instance with given host ID and host description.
+ *
+ * @param hostId host id
+ * @param description host description
+ */
+ public void addInstance(HostId hostId, HostDescription description) {
+ hostProvider.hostDetected(hostId, description, false);
+ }
+
+ /**
+ * Removes a service instance from a given connect point.
+ *
+ * @param connectPoint connect point
+ */
+ public void removeInstance(ConnectPoint connectPoint) {
+ hostService.getConnectedHosts(connectPoint)
+ .stream()
+ .forEach(host -> hostProvider.hostVanished(host.id()));
+ }
+
+ /**
+ * Removes service instance with given host ID.
+ *
+ * @param hostId host id
+ */
+ public void removeInstance(HostId hostId) {
+ hostProvider.hostVanished(hostId);
+ }
+
+ private void instanceDetected(Instance instance) {
+ VtnService service = getVtnService(instance.serviceId());
+ if (service == null) {
+ return;
+ }
+
+ if (service.networkType().equals(PRIVATE)) {
+ arpProxy.addGateway(service.serviceIp(), privateGatewayMac);
+ arpProxy.sendGratuitousArpForGateway(service.serviceIp(), Sets.newHashSet(instance));
+ }
+ if (!instance.isNestedInstance()) {
+ registerDhcpLease(instance, service);
+ }
+ }
+
+ private void instanceRemoved(Instance instance) {
+ VtnService service = getVtnService(instance.serviceId());
+ if (service == null) {
+ return;
+ }
+
+ if (service.networkType().equals(PRIVATE) && getInstances(service.id()).isEmpty()) {
+ arpProxy.removeGateway(service.serviceIp());
+ }
+
+ if (!instance.isNestedInstance()) {
+ dhcpService.removeStaticMapping(instance.mac());
+ }
+ }
+
+ private void registerDhcpLease(Instance instance, VtnService service) {
+ Ip4Address broadcast = Ip4Address.makeMaskedAddress(
+ instance.ipAddress(),
+ service.subnet().prefixLength());
+
+ IpAssignment.Builder ipBuilder = IpAssignment.builder()
+ .ipAddress(instance.ipAddress())
+ .leasePeriod(DHCP_INFINITE_LEASE)
+ .timestamp(new Date())
+ .subnetMask(Ip4Address.makeMaskPrefix(service.subnet().prefixLength()))
+ .broadcast(broadcast)
+ .domainServer(DEFAULT_DNS)
+ .assignmentStatus(Option_RangeNotEnforced);
+
+ if (service.networkType() != MANAGEMENT) {
+ ipBuilder = ipBuilder.routerAddress(service.serviceIp().getIp4Address());
+ }
+
+ log.debug("Set static DHCP mapping for {} {}", instance.mac(), instance.ipAddress());
+ dhcpService.setStaticMapping(instance.mac(), ipBuilder.build());
+ }
+
+ private VtnService getVtnService(VtnServiceId serviceId) {
+ checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
+ checkNotNull(xosAccess, XOS_ACCESS_ERROR);
+
+ // TODO remove openstack access when XOS provides all information
+ VtnServiceApi serviceApi = xosClient.getClient(xosAccess).vtnService();
+ VtnService service = serviceApi.service(serviceId, osAccess);
+ if (service == null) {
+ log.warn("Failed to get VtnService for {}", serviceId);
+ }
+ return service;
+ }
+
+ private VtnPort getVtnPort(String portName) {
+ checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
+ checkNotNull(xosAccess, XOS_ACCESS_ERROR);
+
+ // TODO remove openstack access when XOS provides all information
+ VtnPortApi portApi = xosClient.getClient(xosAccess).vtnPort();
+ VtnPort vtnPort = portApi.vtnPort(portName, osAccess);
+ if (vtnPort == null) {
+ log.warn("Failed to get port information of {}", portName);
+ }
+ return vtnPort;
+ }
+
+ private Set<Instance> getInstances(VtnServiceId serviceId) {
+ return StreamSupport.stream(hostService.getHosts().spliterator(), false)
+ .filter(host -> Objects.equals(
+ serviceId.id(),
+ host.annotations().value(Instance.SERVICE_ID)))
+ .map(Instance::of)
+ .collect(Collectors.toSet());
+ }
+
+ private void readConfiguration() {
+ CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
+ if (config == null) {
+ log.debug("No configuration found");
+ return;
+ }
+
+ log.info("Load CORD-VTN configurations");
+
+ xosAccess = config.xosAccess();
+ osAccess = config.openstackAccess();
+ privateGatewayMac = config.privateGatewayMac();
+
+ Map<IpAddress, MacAddress> publicGateways = config.publicGateways();
+ publicGateways.entrySet()
+ .stream()
+ .forEach(entry -> {
+ arpProxy.addGateway(entry.getKey(), entry.getValue());
+ log.debug("Added public gateway IP {}, MAC {}",
+ entry.getKey(), entry.getValue());
+ });
+ // TODO notice gateway MAC change to VMs holds this gateway IP
+ }
+
+ private class InternalHostListener implements HostListener {
+
+ @Override
+ public void event(HostEvent event) {
+ Host host = event.subject();
+ if (!mastershipService.isLocalMaster(host.location().deviceId())) {
+ // do not allow to proceed without mastership
+ return;
+ }
+
+ Instance instance = Instance.of(host);
+ switch (event.type()) {
+ case HOST_UPDATED:
+ case HOST_ADDED:
+ eventExecutor.execute(() -> instanceDetected(instance));
+ break;
+ case HOST_REMOVED:
+ eventExecutor.execute(() -> instanceRemoved(instance));
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ 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;
+ }
+ arpProxy.processArpPacket(context, ethPacket);
+ }
+ }
+
+ private class InternalConfigListener implements NetworkConfigListener {
+
+ @Override
+ public void event(NetworkConfigEvent event) {
+ if (!event.configClass().equals(CordVtnConfig.class)) {
+ return;
+ }
+
+ switch (event.type()) {
+ case CONFIG_ADDED:
+ case CONFIG_UPDATED:
+ readConfiguration();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/impl/CordVtnNodeManager.java b/src/main/java/org/onosproject/cordvtn/impl/CordVtnNodeManager.java
new file mode 100644
index 0000000..7fa6c5c
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/impl/CordVtnNodeManager.java
@@ -0,0 +1,1029 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.impl;
+
+import com.google.common.collect.Sets;
+import com.jcraft.jsch.Session;
+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.util.ItemNotFoundException;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.cordvtn.api.ConnectionHandler;
+import org.onosproject.cordvtn.api.CordVtnConfig;
+import org.onosproject.cordvtn.api.CordVtnNode;
+import org.onosproject.cordvtn.api.CordVtnNodeState;
+import org.onosproject.cordvtn.api.CordVtnService;
+import org.onosproject.cordvtn.api.NetworkAddress;
+import org.onosproject.cordvtn.api.SshAccessInfo;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.behaviour.BridgeConfig;
+import org.onosproject.net.behaviour.BridgeName;
+import org.onosproject.net.behaviour.ControllerInfo;
+import org.onosproject.net.behaviour.DefaultTunnelDescription;
+import org.onosproject.net.behaviour.TunnelConfig;
+import org.onosproject.net.behaviour.TunnelDescription;
+import org.onosproject.net.behaviour.TunnelName;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.device.DeviceAdminService;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.ovsdb.controller.OvsdbClientService;
+import org.onosproject.ovsdb.controller.OvsdbController;
+import org.onosproject.ovsdb.controller.OvsdbNodeId;
+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.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.cordvtn.impl.CordVtnPipeline.DEFAULT_TUNNEL;
+import static org.onosproject.cordvtn.impl.RemoteIpCommandUtil.*;
+import static org.onosproject.net.Device.Type.SWITCH;
+import static org.onosproject.net.behaviour.TunnelDescription.Type.VXLAN;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Reads node information from the network config file and handles the config
+ * update events.
+ * Only a leader controller performs the node addition or deletion.
+ */
+@Component(immediate = true)
+@Service(value = CordVtnNodeManager.class)
+public class CordVtnNodeManager {
+
+ protected final Logger log = getLogger(getClass());
+
+ private static final KryoNamespace.Builder NODE_SERIALIZER = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(CordVtnNode.class)
+ .register(NodeState.class)
+ .register(SshAccessInfo.class)
+ .register(NetworkAddress.class);
+
+ private static final String DEFAULT_BRIDGE = "br-int";
+ private static final String VPORT_PREFIX = "tap";
+ private static final String OK = "OK";
+ private static final String NO = "NO";
+
+ private static final Map<String, String> DEFAULT_TUNNEL_OPTIONS = new HashMap<String, String>() {
+ {
+ put("key", "flow");
+ put("remote_ip", "flow");
+ }
+ };
+ private static final int DPID_BEGIN = 3;
+ private static final int OFPORT = 6653;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected NetworkConfigRegistry configRegistry;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected NetworkConfigService configService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceAdminService adminService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OvsdbController controller;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostService hostService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected LeadershipService leadershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CordVtnInstanceManager instanceManager;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CordVtnPipeline pipeline;
+
+ private final ExecutorService eventExecutor =
+ newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn-node", "event-handler"));
+
+ private final NetworkConfigListener configListener = new InternalConfigListener();
+ private final DeviceListener deviceListener = new InternalDeviceListener();
+ private final MapEventListener<String, CordVtnNode> nodeStoreListener = new InternalMapListener();
+
+ private final OvsdbHandler ovsdbHandler = new OvsdbHandler();
+ private final BridgeHandler bridgeHandler = new BridgeHandler();
+
+ private ConsistentMap<String, CordVtnNode> nodeStore;
+ private ApplicationId appId;
+ private NodeId localNodeId;
+
+ private enum NodeState implements CordVtnNodeState {
+
+ INIT {
+ @Override
+ public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
+ if (!nodeManager.isOvsdbConnected(node)) {
+ nodeManager.connectOvsdb(node);
+ } else {
+ nodeManager.createIntegrationBridge(node);
+ }
+ }
+ },
+ BRIDGE_CREATED {
+ @Override
+ public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
+ if (!nodeManager.isOvsdbConnected(node)) {
+ nodeManager.connectOvsdb(node);
+ } else {
+ nodeManager.createTunnelInterface(node);
+ nodeManager.addDataPlaneInterface(node);
+ }
+ }
+ },
+ PORTS_ADDED {
+ @Override
+ public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
+ nodeManager.setIpAddress(node);
+ }
+ },
+ COMPLETE {
+ @Override
+ public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
+ nodeManager.postInit(node);
+ }
+ },
+ INCOMPLETE {
+ @Override
+ public void process(CordVtnNodeManager nodeManager, CordVtnNode node) {
+ }
+ };
+
+ public abstract void process(CordVtnNodeManager nodeManager, CordVtnNode node);
+ }
+
+ @Activate
+ protected void activate() {
+ appId = coreService.getAppId(CordVtnService.CORDVTN_APP_ID);
+
+ localNodeId = clusterService.getLocalNode().id();
+ leadershipService.runForLeadership(appId.name());
+
+ nodeStore = storageService.<String, CordVtnNode>consistentMapBuilder()
+ .withSerializer(Serializer.using(NODE_SERIALIZER.build()))
+ .withName("cordvtn-nodestore")
+ .withApplicationId(appId)
+ .build();
+
+ nodeStore.addListener(nodeStoreListener);
+ deviceService.addListener(deviceListener);
+ configService.addListener(configListener);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ configService.removeListener(configListener);
+ deviceService.removeListener(deviceListener);
+ nodeStore.removeListener(nodeStoreListener);
+
+ leadershipService.withdraw(appId.name());
+ eventExecutor.shutdown();
+
+ log.info("Stopped");
+ }
+
+ /**
+ * Adds or updates a new node to the service.
+ *
+ * @param node cordvtn node
+ */
+ public void addOrUpdateNode(CordVtnNode node) {
+ checkNotNull(node);
+ nodeStore.put(node.hostname(), CordVtnNode.getUpdatedNode(node, getNodeState(node)));
+ }
+
+ /**
+ * Deletes a node from the service.
+ *
+ * @param node cordvtn node
+ */
+ public void deleteNode(CordVtnNode node) {
+ checkNotNull(node);
+
+ if (isOvsdbConnected(node)) {
+ disconnectOvsdb(node);
+ }
+
+ nodeStore.remove(node.hostname());
+ }
+
+ /**
+ * Returns node initialization state.
+ *
+ * @param node cordvtn node
+ * @return true if initial node setup is completed, otherwise false
+ */
+ public boolean isNodeInitComplete(CordVtnNode node) {
+ checkNotNull(node);
+ return nodeStore.containsKey(node.hostname()) && getNodeState(node).equals(NodeState.COMPLETE);
+ }
+
+ /**
+ * Returns detailed node initialization state.
+ *
+ * @param node cordvtn node
+ * @return string including detailed node init state
+ */
+ public String checkNodeInitState(CordVtnNode node) {
+ checkNotNull(node);
+
+ if (!nodeStore.containsKey(node.hostname())) {
+ log.warn("Node {} does not exist, add node first", node.hostname());
+ return null;
+ }
+
+ Session session = connect(node.sshInfo());
+ if (session == null) {
+ log.debug("Failed to SSH to {}", node.hostname());
+ return null;
+ }
+
+ Set<IpAddress> intBrIps = getCurrentIps(session, DEFAULT_BRIDGE);
+ String result = String.format(
+ "Current state : %s%n" +
+ "br-int created and connected to ONOS : %s (%s)%n" +
+ "VXLAN interface added to br-int : %s%n" +
+ "Data plane interface is added to br-int and enabled : %s (%s)%n" +
+ "IP flushed from data plane interface : %s (%s)%n" +
+ "Data plane IP added to br-int : %s (%s)%n" +
+ "Local management IP added to br-int : %s (%s)",
+ node.state(),
+ isBrIntCreated(node) ? OK : NO, node.intBrId(),
+ isTunnelIntfCreated(node) ? OK : NO,
+ isDataPlaneIntfAdded(node) ? OK : NO, node.dpIntf(),
+ isInterfaceUp(session, node.dpIntf()) &&
+ getCurrentIps(session, node.dpIntf()).isEmpty() ? OK : NO, node.dpIntf(),
+ intBrIps.contains(node.dpIp().ip()) ? OK : NO, node.dpIp().cidr(),
+ intBrIps.contains(node.localMgmtIp().ip()) ? OK : NO, node.localMgmtIp().cidr());
+
+ disconnect(session);
+
+ return result;
+ }
+
+ /**
+ * Returns the number of the nodes known to the service.
+ *
+ * @return number of nodes
+ */
+ public int getNodeCount() {
+ return nodeStore.size();
+ }
+
+ /**
+ * Returns all nodes known to the service.
+ *
+ * @return list of nodes
+ */
+ public List<CordVtnNode> getNodes() {
+ return nodeStore.values().stream().map(Versioned::value).collect(Collectors.toList());
+ }
+
+ /**
+ * Returns all nodes in complete state.
+ *
+ * @return set of nodes
+ */
+ public Set<CordVtnNode> completeNodes() {
+ return getNodes().stream().filter(this::isNodeInitComplete).collect(Collectors.toSet());
+ }
+
+ /**
+ * Returns physical data plane port number of a given device.
+ *
+ * @param deviceId integration bridge device id
+ * @return port number; null otherwise
+ */
+ public PortNumber dpPort(DeviceId deviceId) {
+ CordVtnNode node = nodeByBridgeId(deviceId);
+ if (node == null) {
+ log.warn("Failed to get node for {}", deviceId);
+ return null;
+ }
+ Port port = deviceService.getPorts(deviceId).stream()
+ .filter(p -> portName(p).contains(node.dpIntf()) &&
+ p.isEnabled())
+ .findFirst().orElse(null);
+
+ return port == null ? null : port.number();
+ }
+
+ /**
+ * Returns physical data plane IP address of a given device.
+ *
+ * @param deviceId integration bridge device id
+ * @return ip address; null otherwise
+ */
+ public IpAddress dpIp(DeviceId deviceId) {
+ CordVtnNode node = nodeByBridgeId(deviceId);
+ if (node == null) {
+ log.warn("Failed to get node for {}", deviceId);
+ return null;
+ }
+ return node.dpIp().ip();
+ }
+
+ /**
+ * Returns tunnel port number of a given device.
+ *
+ * @param deviceId integration bridge device id
+ * @return port number
+ */
+ public PortNumber tunnelPort(DeviceId deviceId) {
+ Port port = deviceService.getPorts(deviceId).stream()
+ .filter(p -> portName(p).contains(DEFAULT_TUNNEL))
+ .findFirst().orElse(null);
+
+ return port == null ? null : port.number();
+ }
+
+ /**
+ * Returns if current node state saved in nodeStore is COMPLETE or not.
+ *
+ * @param node cordvtn node
+ * @return true if it's complete state, otherwise false
+ */
+ private boolean isNodeStateComplete(CordVtnNode node) {
+ checkNotNull(node);
+
+ // the state saved in nodeStore can be wrong if IP address settings are changed
+ // after the node init has been completed since there's no way to detect it
+ // getNodeState and checkNodeInitState always return correct answer but can be slow
+ Versioned<CordVtnNode> versionedNode = nodeStore.get(node.hostname());
+ CordVtnNodeState state = versionedNode.value().state();
+ return state != null && state.equals(NodeState.COMPLETE);
+ }
+
+ /**
+ * Initiates node to serve virtual tenant network.
+ *
+ * @param node cordvtn node
+ */
+ private void initNode(CordVtnNode node) {
+ checkNotNull(node);
+
+ NodeState state = (NodeState) node.state();
+ log.debug("Processing node: {} state: {}", node.hostname(), state);
+
+ state.process(this, node);
+ }
+
+ /**
+ * Performs tasks after node initialization.
+ * It disconnects unnecessary OVSDB connection and installs initial flow
+ * rules on the device.
+ *
+ * @param node cordvtn node
+ */
+ private void postInit(CordVtnNode node) {
+ disconnectOvsdb(node);
+ pipeline.initPipeline(node, dpPort(node.intBrId()), tunnelPort(node.intBrId()));
+
+ deviceService.getPorts(node.intBrId()).stream()
+ .filter(port -> portName(port).startsWith(VPORT_PREFIX) &&
+ port.isEnabled())
+ .forEach(port -> instanceManager.addInstance(connectPoint(port)));
+
+ hostService.getHosts().forEach(host -> {
+ if (deviceService.getPort(host.location().deviceId(),
+ host.location().port()) == null) {
+ instanceManager.removeInstance(connectPoint(host));
+ }
+ });
+
+ log.info("Finished init {}", node.hostname());
+ }
+
+ /**
+ * Sets a new state for a given cordvtn node.
+ *
+ * @param node cordvtn node
+ * @param newState new node state
+ */
+ private void setNodeState(CordVtnNode node, NodeState newState) {
+ checkNotNull(node);
+
+ log.debug("Changed {} state: {}", node.hostname(), newState);
+ nodeStore.put(node.hostname(), CordVtnNode.getUpdatedNode(node, newState));
+ }
+
+ /**
+ * Checks current state of a given cordvtn node and returns it.
+ *
+ * @param node cordvtn node
+ * @return node state
+ */
+ private NodeState getNodeState(CordVtnNode node) {
+ checkNotNull(node);
+
+ if (isBrIntCreated(node) && isTunnelIntfCreated(node) &&
+ isDataPlaneIntfAdded(node) && isIpAddressSet(node)) {
+ return NodeState.COMPLETE;
+ } else if (isDataPlaneIntfAdded(node) && isTunnelIntfCreated(node)) {
+ return NodeState.PORTS_ADDED;
+ } else if (isBrIntCreated(node)) {
+ return NodeState.BRIDGE_CREATED;
+ } else {
+ return NodeState.INIT;
+ }
+ }
+
+ /**
+ * Returns connection state of OVSDB server for a given node.
+ *
+ * @param node cordvtn node
+ * @return true if it is connected, false otherwise
+ */
+ private boolean isOvsdbConnected(CordVtnNode node) {
+ checkNotNull(node);
+
+ OvsdbClientService ovsdbClient = getOvsdbClient(node);
+ return deviceService.isAvailable(node.ovsdbId()) &&
+ ovsdbClient != null && ovsdbClient.isConnected();
+ }
+
+ /**
+ * Connects to OVSDB server for a given node.
+ *
+ * @param node cordvtn node
+ */
+ private void connectOvsdb(CordVtnNode node) {
+ checkNotNull(node);
+
+ if (!nodeStore.containsKey(node.hostname())) {
+ log.warn("Node {} does not exist", node.hostname());
+ return;
+ }
+
+ if (!isOvsdbConnected(node)) {
+ controller.connect(node.hostMgmtIp().ip(), node.ovsdbPort());
+ }
+ }
+
+ /**
+ * Disconnects OVSDB server for a given node.
+ *
+ * @param node cordvtn node
+ */
+ private void disconnectOvsdb(CordVtnNode node) {
+ checkNotNull(node);
+
+ if (!nodeStore.containsKey(node.hostname())) {
+ log.warn("Node {} does not exist", node.hostname());
+ return;
+ }
+
+ if (isOvsdbConnected(node)) {
+ OvsdbClientService ovsdbClient = getOvsdbClient(node);
+ ovsdbClient.disconnect();
+ }
+ }
+
+ /**
+ * Returns OVSDB client for a given node.
+ *
+ * @param node cordvtn node
+ * @return OVSDB client, or null if it fails to get OVSDB client
+ */
+ private OvsdbClientService getOvsdbClient(CordVtnNode node) {
+ checkNotNull(node);
+
+ OvsdbClientService ovsdbClient = controller.getOvsdbClient(
+ new OvsdbNodeId(node.hostMgmtIp().ip(), node.ovsdbPort().toInt()));
+ if (ovsdbClient == null) {
+ log.trace("Couldn't find OVSDB client for {}", node.hostname());
+ }
+ return ovsdbClient;
+ }
+
+ /**
+ * Creates an integration bridge for a given node.
+ *
+ * @param node cordvtn node
+ */
+ private void createIntegrationBridge(CordVtnNode node) {
+ if (isBrIntCreated(node)) {
+ return;
+ }
+
+ List<ControllerInfo> controllers = new ArrayList<>();
+ Sets.newHashSet(clusterService.getNodes()).stream()
+ .forEach(controller -> {
+ ControllerInfo ctrlInfo = new ControllerInfo(controller.ip(), OFPORT, "tcp");
+ controllers.add(ctrlInfo);
+ });
+
+ String dpid = node.intBrId().toString().substring(DPID_BEGIN);
+
+ try {
+ Device device = deviceService.getDevice(node.ovsdbId());
+ if (device.is(BridgeConfig.class)) {
+ BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
+ bridgeConfig.addBridge(BridgeName.bridgeName(DEFAULT_BRIDGE), dpid, controllers);
+ } else {
+ log.warn("The bridging behaviour is not supported in device {}", device.id());
+ }
+ } catch (ItemNotFoundException e) {
+ log.warn("Failed to create integration bridge on {}", node.hostname());
+ }
+ }
+
+ /**
+ * Creates tunnel interface to the integration bridge for a given node.
+ *
+ * @param node cordvtn node
+ */
+ private void createTunnelInterface(CordVtnNode node) {
+ if (isTunnelIntfCreated(node)) {
+ return;
+ }
+
+ DefaultAnnotations.Builder optionBuilder = DefaultAnnotations.builder();
+ for (String key : DEFAULT_TUNNEL_OPTIONS.keySet()) {
+ optionBuilder.set(key, DEFAULT_TUNNEL_OPTIONS.get(key));
+ }
+
+ TunnelDescription description = new DefaultTunnelDescription(
+ null, null, VXLAN, TunnelName.tunnelName(DEFAULT_TUNNEL),
+ optionBuilder.build());
+
+ try {
+ Device device = deviceService.getDevice(node.ovsdbId());
+ if (device.is(TunnelConfig.class)) {
+ TunnelConfig tunnelConfig = device.as(TunnelConfig.class);
+ tunnelConfig.createTunnelInterface(BridgeName.bridgeName(DEFAULT_BRIDGE), description);
+ } else {
+ log.warn("The tunneling behaviour is not supported in device {}", device.id());
+ }
+ } catch (ItemNotFoundException e) {
+ log.warn("Failed to create tunnel interface on {}", node.hostname());
+ }
+ }
+
+ /**
+ * Adds data plane interface to a given node.
+ *
+ * @param node cordvtn node
+ */
+ private void addDataPlaneInterface(CordVtnNode node) {
+ if (isDataPlaneIntfAdded(node)) {
+ return;
+ }
+
+ Session session = connect(node.sshInfo());
+ if (session == null) {
+ log.debug("Failed to SSH to {}", node.hostname());
+ return;
+ }
+
+ if (!isInterfaceUp(session, node.dpIntf())) {
+ log.warn("Interface {} is not available", node.dpIntf());
+ return;
+ }
+ disconnect(session);
+
+ try {
+ Device device = deviceService.getDevice(node.ovsdbId());
+ if (device.is(BridgeConfig.class)) {
+ BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
+ bridgeConfig.addPort(BridgeName.bridgeName(DEFAULT_BRIDGE), node.dpIntf());
+ } else {
+ log.warn("The bridging behaviour is not supported in device {}", device.id());
+ }
+ } catch (ItemNotFoundException e) {
+ log.warn("Failed to add {} on {}", node.dpIntf(), node.hostname());
+ }
+ }
+
+ /**
+ * Flushes IP address from data plane interface and adds data plane IP address
+ * to integration bridge.
+ *
+ * @param node cordvtn node
+ */
+ private void setIpAddress(CordVtnNode node) {
+ Session session = connect(node.sshInfo());
+ if (session == null) {
+ log.debug("Failed to SSH to {}", node.hostname());
+ return;
+ }
+
+ getCurrentIps(session, DEFAULT_BRIDGE).stream()
+ .filter(ip -> !ip.equals(node.localMgmtIp().ip()))
+ .filter(ip -> !ip.equals(node.dpIp().ip()))
+ .forEach(ip -> deleteIp(session, ip, DEFAULT_BRIDGE));
+
+ boolean result = flushIp(session, node.dpIntf()) &&
+ setInterfaceUp(session, node.dpIntf()) &&
+ addIp(session, node.dpIp(), DEFAULT_BRIDGE) &&
+ addIp(session, node.localMgmtIp(), DEFAULT_BRIDGE) &&
+ setInterfaceUp(session, DEFAULT_BRIDGE);
+
+ disconnect(session);
+
+ if (result) {
+ setNodeState(node, NodeState.COMPLETE);
+ }
+ }
+
+ /**
+ * Checks if integration bridge exists and available.
+ *
+ * @param node cordvtn node
+ * @return true if the bridge is available, false otherwise
+ */
+ private boolean isBrIntCreated(CordVtnNode node) {
+ return (deviceService.getDevice(node.intBrId()) != null
+ && deviceService.isAvailable(node.intBrId()));
+ }
+
+ /**
+ * Checks if tunnel interface exists.
+ *
+ * @param node cordvtn node
+ * @return true if the interface exists, false otherwise
+ */
+ private boolean isTunnelIntfCreated(CordVtnNode node) {
+ return deviceService.getPorts(node.intBrId())
+ .stream()
+ .filter(p -> portName(p).contains(DEFAULT_TUNNEL) &&
+ p.isEnabled())
+ .findAny().isPresent();
+ }
+
+ /**
+ * Checks if data plane interface exists.
+ *
+ * @param node cordvtn node
+ * @return true if the interface exists, false otherwise
+ */
+ private boolean isDataPlaneIntfAdded(CordVtnNode node) {
+ return deviceService.getPorts(node.intBrId())
+ .stream()
+ .filter(p -> portName(p).contains(node.dpIntf()) &&
+ p.isEnabled())
+ .findAny().isPresent();
+ }
+
+ /**
+ * Checks if the IP addresses are correctly set.
+ *
+ * @param node cordvtn node
+ * @return true if the IP is set, false otherwise
+ */
+ private boolean isIpAddressSet(CordVtnNode node) {
+ Session session = connect(node.sshInfo());
+ if (session == null) {
+ log.debug("Failed to SSH to {}", node.hostname());
+ return false;
+ }
+
+ Set<IpAddress> intBrIps = getCurrentIps(session, DEFAULT_BRIDGE);
+ boolean result = getCurrentIps(session, node.dpIntf()).isEmpty() &&
+ isInterfaceUp(session, node.dpIntf()) &&
+ intBrIps.contains(node.dpIp().ip()) &&
+ intBrIps.contains(node.localMgmtIp().ip()) &&
+ isInterfaceUp(session, DEFAULT_BRIDGE);
+
+ disconnect(session);
+ return result;
+ }
+
+ /**
+ * Returns connect point of a given port.
+ *
+ * @param port port
+ * @return connect point
+ */
+ private ConnectPoint connectPoint(Port port) {
+ return new ConnectPoint(port.element().id(), port.number());
+ }
+
+ /**
+ * Returns connect point of a given host.
+ *
+ * @param host host
+ * @return connect point
+ */
+ private ConnectPoint connectPoint(Host host) {
+ return new ConnectPoint(host.location().deviceId(), host.location().port());
+ }
+
+ /**
+ * Returns cordvtn node associated with a given OVSDB device.
+ *
+ * @param ovsdbId OVSDB device id
+ * @return cordvtn node, null if it fails to find the node
+ */
+ private CordVtnNode nodeByOvsdbId(DeviceId ovsdbId) {
+ return getNodes().stream()
+ .filter(node -> node.ovsdbId().equals(ovsdbId))
+ .findFirst().orElse(null);
+ }
+
+ /**
+ * Returns cordvtn node associated with a given integration bridge.
+ *
+ * @param bridgeId device id of integration bridge
+ * @return cordvtn node, null if it fails to find the node
+ */
+ private CordVtnNode nodeByBridgeId(DeviceId bridgeId) {
+ return getNodes().stream()
+ .filter(node -> node.intBrId().equals(bridgeId))
+ .findFirst().orElse(null);
+ }
+
+ /**
+ * Returns port name.
+ *
+ * @param port port
+ * @return port name
+ */
+ private String portName(Port port) {
+ return port.annotations().value("portName");
+ }
+
+ private class OvsdbHandler implements ConnectionHandler<Device> {
+
+ @Override
+ public void connected(Device device) {
+ CordVtnNode node = nodeByOvsdbId(device.id());
+ if (node != null) {
+ setNodeState(node, getNodeState(node));
+ } else {
+ log.debug("{} is detected on unregistered node, ignore it.", device.id());
+ }
+ }
+
+ @Override
+ public void disconnected(Device device) {
+ if (!deviceService.isAvailable(device.id())) {
+ log.debug("Device {} is disconnected", device.id());
+ adminService.removeDevice(device.id());
+ }
+ }
+ }
+
+ private class BridgeHandler implements ConnectionHandler<Device> {
+
+ @Override
+ public void connected(Device device) {
+ CordVtnNode node = nodeByBridgeId(device.id());
+ if (node != null) {
+ setNodeState(node, getNodeState(node));
+ } else {
+ log.debug("{} is detected on unregistered node, ignore it.", device.id());
+ }
+ }
+
+ @Override
+ public void disconnected(Device device) {
+ CordVtnNode node = nodeByBridgeId(device.id());
+ if (node != null) {
+ log.debug("Integration Bridge is disconnected from {}", node.hostname());
+ setNodeState(node, NodeState.INCOMPLETE);
+ }
+ }
+
+ /**
+ * Handles port added situation.
+ * If the added port is tunnel or data plane interface, proceed to the remaining
+ * node initialization. Otherwise, do nothing.
+ *
+ * @param port port
+ */
+ public void portAdded(Port port) {
+ CordVtnNode node = nodeByBridgeId((DeviceId) port.element().id());
+ String portName = portName(port);
+
+ if (node == null) {
+ log.debug("{} is added to unregistered node, ignore it.", portName);
+ return;
+ }
+
+ log.info("Port {} is added to {}", portName, node.hostname());
+
+ if (portName.startsWith(VPORT_PREFIX)) {
+ if (isNodeStateComplete(node)) {
+ instanceManager.addInstance(connectPoint(port));
+ } else {
+ log.debug("VM is detected on incomplete node, ignore it.", portName);
+ }
+ } else if (portName.contains(DEFAULT_TUNNEL) || portName.equals(node.dpIntf())) {
+ setNodeState(node, getNodeState(node));
+ }
+ }
+
+ /**
+ * Handles port removed situation.
+ * If the removed port is tunnel or data plane interface, proceed to the remaining
+ * node initialization.Others, do nothing.
+ *
+ * @param port port
+ */
+ public void portRemoved(Port port) {
+ CordVtnNode node = nodeByBridgeId((DeviceId) port.element().id());
+ String portName = portName(port);
+
+ if (node == null) {
+ return;
+ }
+
+ log.info("Port {} is removed from {}", portName, node.hostname());
+
+ if (portName.startsWith(VPORT_PREFIX)) {
+ if (isNodeStateComplete(node)) {
+ instanceManager.removeInstance(connectPoint(port));
+ } else {
+ log.debug("VM is vanished from incomplete node, ignore it.", portName);
+ }
+ } else if (portName.contains(DEFAULT_TUNNEL) || portName.equals(node.dpIntf())) {
+ setNodeState(node, NodeState.INCOMPLETE);
+ }
+ }
+ }
+
+ private class InternalDeviceListener implements DeviceListener {
+
+ @Override
+ public void event(DeviceEvent event) {
+
+ NodeId leaderNodeId = leadershipService.getLeader(appId.name());
+ if (!Objects.equals(localNodeId, leaderNodeId)) {
+ // do not allow to proceed without leadership
+ return;
+ }
+
+ Device device = event.subject();
+ ConnectionHandler<Device> handler =
+ (device.type().equals(SWITCH) ? bridgeHandler : ovsdbHandler);
+
+ switch (event.type()) {
+ case PORT_ADDED:
+ eventExecutor.execute(() -> bridgeHandler.portAdded(event.port()));
+ break;
+ case PORT_UPDATED:
+ if (!event.port().isEnabled()) {
+ eventExecutor.execute(() -> bridgeHandler.portRemoved(event.port()));
+ }
+ break;
+ case DEVICE_ADDED:
+ case DEVICE_AVAILABILITY_CHANGED:
+ if (deviceService.isAvailable(device.id())) {
+ eventExecutor.execute(() -> handler.connected(device));
+ } else {
+ eventExecutor.execute(() -> handler.disconnected(device));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * Reads cordvtn nodes from config file.
+ */
+ private void readConfiguration() {
+ CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
+ if (config == null) {
+ log.debug("No configuration found");
+ return;
+ }
+ config.cordVtnNodes().forEach(this::addOrUpdateNode);
+ }
+
+ private class InternalConfigListener implements NetworkConfigListener {
+
+ @Override
+ public void event(NetworkConfigEvent event) {
+ NodeId leaderNodeId = leadershipService.getLeader(appId.name());
+ if (!Objects.equals(localNodeId, leaderNodeId)) {
+ // do not allow to proceed without leadership
+ return;
+ }
+
+ if (!event.configClass().equals(CordVtnConfig.class)) {
+ return;
+ }
+
+ switch (event.type()) {
+ case CONFIG_ADDED:
+ case CONFIG_UPDATED:
+ eventExecutor.execute(CordVtnNodeManager.this::readConfiguration);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private class InternalMapListener implements MapEventListener<String, CordVtnNode> {
+
+ @Override
+ public void event(MapEvent<String, CordVtnNode> event) {
+ NodeId leaderNodeId = leadershipService.getLeader(appId.name());
+ if (!Objects.equals(localNodeId, leaderNodeId)) {
+ // do not allow to proceed without leadership
+ return;
+ }
+
+ CordVtnNode oldNode;
+ CordVtnNode newNode;
+
+ switch (event.type()) {
+ case UPDATE:
+ oldNode = event.oldValue().value();
+ newNode = event.newValue().value();
+
+ log.info("Reloaded {}", newNode.hostname());
+ if (!newNode.equals(oldNode)) {
+ log.debug("New node: {}", newNode);
+ }
+ // performs init procedure even if the node is not changed
+ // for robustness since it's no harm to run init procedure
+ // multiple times
+ eventExecutor.execute(() -> initNode(newNode));
+ break;
+ case INSERT:
+ newNode = event.newValue().value();
+ log.info("Added {}", newNode.hostname());
+ eventExecutor.execute(() -> initNode(newNode));
+ break;
+ case REMOVE:
+ oldNode = event.oldValue().value();
+ log.info("Removed {}", oldNode.hostname());
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/impl/CordVtnPipeline.java b/src/main/java/org/onosproject/cordvtn/impl/CordVtnPipeline.java
new file mode 100644
index 0000000..4abbcf0
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/impl/CordVtnPipeline.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.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.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onlab.util.ItemNotFoundException;
+import org.onosproject.cordvtn.api.CordVtnNode;
+import org.onosproject.cordvtn.api.CordVtnService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.behaviour.ExtensionTreatmentResolver;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.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.net.flow.instructions.ExtensionPropertyException;
+import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.slf4j.Logger;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Provides CORD VTN pipeline.
+ */
+@Component(immediate = true)
+@Service(value = CordVtnPipeline.class)
+public final class CordVtnPipeline {
+
+ protected final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected FlowRuleService flowRuleService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+ // tables
+ public static final int TABLE_ZERO = 0;
+ public static final int TABLE_IN_PORT = 1;
+ public static final int TABLE_ACCESS_TYPE = 2;
+ public static final int TABLE_IN_SERVICE = 3;
+ public static final int TABLE_DST_IP = 4;
+ public static final int TABLE_TUNNEL_IN = 5;
+ public static final int TABLE_VLAN = 6;
+
+ // priorities
+ public static final int PRIORITY_MANAGEMENT = 55000;
+ public static final int PRIORITY_HIGH = 50000;
+ public static final int PRIORITY_DEFAULT = 5000;
+ public static final int PRIORITY_LOW = 4000;
+ public static final int PRIORITY_ZERO = 0;
+
+ public static final int VXLAN_UDP_PORT = 4789;
+ public static final VlanId VLAN_WAN = VlanId.vlanId((short) 500);
+ public static final String DEFAULT_TUNNEL = "vxlan";
+ private static final String PORT_NAME = "portName";
+
+ private ApplicationId appId;
+
+ @Activate
+ protected void activate() {
+ appId = coreService.registerApplication(CordVtnService.CORDVTN_APP_ID);
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ log.info("Stopped");
+ }
+
+ /**
+ * Flush flows installed by this application.
+ */
+ public void flushRules() {
+ flowRuleService.getFlowRulesById(appId).forEach(flowRule -> processFlowRule(false, flowRule));
+ }
+
+ /**
+ * Installs table miss rule to a give device.
+ *
+ * @param node cordvtn node
+ * @param dpPort data plane port number
+ * @param tunnelPort tunnel port number
+ */
+ public void initPipeline(CordVtnNode node, PortNumber dpPort, PortNumber tunnelPort) {
+ checkNotNull(node);
+
+ processTableZero(node.intBrId(), dpPort, node.dpIp().ip());
+ processInPortTable(node.intBrId(), tunnelPort, dpPort);
+ processAccessTypeTable(node.intBrId(), dpPort);
+ processVlanTable(node.intBrId(), dpPort);
+ }
+
+ private void processTableZero(DeviceId deviceId, PortNumber dpPort, IpAddress dpIp) {
+ // take vxlan packet out onto the physical port
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchInPort(PortNumber.LOCAL)
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(dpPort)
+ .build();
+
+ FlowRule flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_HIGH)
+ .forDevice(deviceId)
+ .forTable(TABLE_ZERO)
+ .makePermanent()
+ .build();
+
+ processFlowRule(true, flowRule);
+
+ // take a vxlan encap'd packet through the Linux stack
+ selector = DefaultTrafficSelector.builder()
+ .matchInPort(dpPort)
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPProtocol(IPv4.PROTOCOL_UDP)
+ .matchUdpDst(TpPort.tpPort(VXLAN_UDP_PORT))
+ .build();
+
+ treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.LOCAL)
+ .build();
+
+ flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_HIGH)
+ .forDevice(deviceId)
+ .forTable(TABLE_ZERO)
+ .makePermanent()
+ .build();
+
+ processFlowRule(true, flowRule);
+
+ // take a packet to the data plane ip through Linux stack
+ selector = DefaultTrafficSelector.builder()
+ .matchInPort(dpPort)
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(dpIp.toIpPrefix())
+ .build();
+
+ treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.LOCAL)
+ .build();
+
+ flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_HIGH)
+ .forDevice(deviceId)
+ .forTable(TABLE_ZERO)
+ .makePermanent()
+ .build();
+
+ processFlowRule(true, flowRule);
+
+ // take an arp packet from physical through Linux stack
+ selector = DefaultTrafficSelector.builder()
+ .matchInPort(dpPort)
+ .matchEthType(Ethernet.TYPE_ARP)
+ .matchArpTpa(dpIp.getIp4Address())
+ .build();
+
+ treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.LOCAL)
+ .build();
+
+ flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_HIGH)
+ .forDevice(deviceId)
+ .forTable(TABLE_ZERO)
+ .makePermanent()
+ .build();
+
+ processFlowRule(true, flowRule);
+
+ // take all else to the next table
+ selector = DefaultTrafficSelector.builder()
+ .build();
+
+ treatment = DefaultTrafficTreatment.builder()
+ .transition(TABLE_IN_PORT)
+ .build();
+
+ flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_ZERO)
+ .forDevice(deviceId)
+ .forTable(TABLE_ZERO)
+ .makePermanent()
+ .build();
+
+ processFlowRule(true, flowRule);
+
+ // take all vlan tagged packet to the VLAN table
+ selector = DefaultTrafficSelector.builder()
+ .matchVlanId(VlanId.ANY)
+ .build();
+
+ treatment = DefaultTrafficTreatment.builder()
+ .transition(TABLE_VLAN)
+ .build();
+
+ flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_MANAGEMENT)
+ .forDevice(deviceId)
+ .forTable(TABLE_ZERO)
+ .makePermanent()
+ .build();
+
+ processFlowRule(true, flowRule);
+ }
+
+ private void processInPortTable(DeviceId deviceId, PortNumber tunnelPort, PortNumber dpPort) {
+ checkNotNull(tunnelPort);
+
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchInPort(tunnelPort)
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .transition(TABLE_TUNNEL_IN)
+ .build();
+
+ FlowRule flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_DEFAULT)
+ .forDevice(deviceId)
+ .forTable(TABLE_IN_PORT)
+ .makePermanent()
+ .build();
+
+ processFlowRule(true, flowRule);
+
+ selector = DefaultTrafficSelector.builder()
+ .matchInPort(dpPort)
+ .build();
+
+ treatment = DefaultTrafficTreatment.builder()
+ .transition(TABLE_DST_IP)
+ .build();
+
+ flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_DEFAULT)
+ .forDevice(deviceId)
+ .forTable(TABLE_IN_PORT)
+ .makePermanent()
+ .build();
+
+ processFlowRule(true, flowRule);
+ }
+
+ private void processAccessTypeTable(DeviceId deviceId, PortNumber dpPort) {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(dpPort)
+ .build();
+
+ FlowRule flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_ZERO)
+ .forDevice(deviceId)
+ .forTable(TABLE_ACCESS_TYPE)
+ .makePermanent()
+ .build();
+
+ processFlowRule(true, flowRule);
+ }
+
+ private void processVlanTable(DeviceId deviceId, PortNumber dpPort) {
+ // for traffic going out to WAN, strip vid 500 and take through data plane interface
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchVlanId(VLAN_WAN)
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .popVlan()
+ .setOutput(dpPort)
+ .build();
+
+ FlowRule flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_DEFAULT)
+ .forDevice(deviceId)
+ .forTable(TABLE_VLAN)
+ .makePermanent()
+ .build();
+
+ processFlowRule(true, flowRule);
+
+ selector = DefaultTrafficSelector.builder()
+ .matchVlanId(VLAN_WAN)
+ .matchEthType(Ethernet.TYPE_ARP)
+ .build();
+
+ treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.CONTROLLER)
+ .build();
+
+ flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_HIGH)
+ .forDevice(deviceId)
+ .forTable(TABLE_VLAN)
+ .makePermanent()
+ .build();
+
+ processFlowRule(true, flowRule);
+ }
+
+ public void processFlowRule(boolean install, FlowRule rule) {
+ FlowRuleOperations.Builder oBuilder = FlowRuleOperations.builder();
+ oBuilder = install ? oBuilder.add(rule) : oBuilder.remove(rule);
+
+ flowRuleService.apply(oBuilder.build(new FlowRuleOperationsContext() {
+ @Override
+ public void onError(FlowRuleOperations ops) {
+ log.error(String.format("Failed %s, %s", ops.toString(), rule.toString()));
+ }
+ }));
+ }
+
+ public ExtensionTreatment tunnelDstTreatment(DeviceId deviceId, Ip4Address remoteIp) {
+ try {
+ Device device = deviceService.getDevice(deviceId);
+
+ if (device.is(ExtensionTreatmentResolver.class)) {
+ ExtensionTreatmentResolver resolver = device.as(ExtensionTreatmentResolver.class);
+ ExtensionTreatment treatment =
+ resolver.getExtensionInstruction(NICIRA_SET_TUNNEL_DST.type());
+ treatment.setPropertyValue("tunnelDst", remoteIp);
+ return treatment;
+ } else {
+ log.warn("The extension treatment resolving behaviour is not supported in device {}",
+ device.id().toString());
+ return null;
+ }
+ } catch (ItemNotFoundException | UnsupportedOperationException |
+ ExtensionPropertyException e) {
+ log.error("Failed to get extension instruction {}", deviceId);
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/impl/RemoteIpCommandUtil.java b/src/main/java/org/onosproject/cordvtn/impl/RemoteIpCommandUtil.java
new file mode 100644
index 0000000..c09e3a0
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/impl/RemoteIpCommandUtil.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.impl;
+
+import com.google.common.collect.Sets;
+import com.google.common.io.CharStreams;
+import com.jcraft.jsch.Channel;
+import com.jcraft.jsch.ChannelExec;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cordvtn.api.NetworkAddress;
+import org.onosproject.cordvtn.api.SshAccessInfo;
+import org.slf4j.Logger;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * {@code RemoteIpCommandUtil} provides methods to help execute Linux IP commands to a remote server.
+ * It opens individual exec channels for each command. User can create a session with {@code connect}
+ * method and then execute a series commands. After done with all commands, the session must be closed
+ * explicitly by calling {@code disconnect}.
+ */
+public final class RemoteIpCommandUtil {
+
+ protected static final Logger log = getLogger(RemoteIpCommandUtil.class);
+
+ private static final String STRICT_HOST_CHECKING = "StrictHostKeyChecking";
+ private static final String DEFAULT_STRICT_HOST_CHECKING = "no";
+ private static final int DEFAULT_SESSION_TIMEOUT = 60000; // milliseconds
+
+ private static final String IP_PATTERN = "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
+ "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
+ "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
+ "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$";
+
+ private static final String IP_ADDR_SHOW = "sudo ip addr show %s";
+ private static final String IP_ADDR_FLUSH = "sudo ip addr flush %s";
+ private static final String IP_ADDR_ADD = "sudo ip addr add %s dev %s";
+ private static final String IP_ADDR_DELETE = "sudo ip addr delete %s dev %s";
+ private static final String IP_LINK_SHOW = "sudo ip link show %s";
+ private static final String IP_LINK_UP = "sudo ip link set %s up";
+
+ /**
+ * Default constructor.
+ */
+ private RemoteIpCommandUtil() {
+ }
+
+ /**
+ * Adds a given IP address to a given device.
+ *
+ * @param session ssh connection
+ * @param ip network address
+ * @param device device name to assign the ip address
+ * @return true if the command succeeds, or false
+ */
+ public static boolean addIp(Session session, NetworkAddress ip, String device) {
+ if (session == null || !session.isConnected()) {
+ return false;
+ }
+
+ executeCommand(session, String.format(IP_ADDR_ADD, ip.cidr(), device));
+ Set<IpAddress> result = getCurrentIps(session, device);
+ return result.contains(ip.ip());
+ }
+
+ /**
+ * Removes the IP address from a given device.
+ *
+ * @param session ssh connection
+ * @param ip ip address
+ * @param device device name
+ * @return true if the command succeeds, or false
+ */
+ public static boolean deleteIp(Session session, IpAddress ip, String device) {
+ if (session == null || !session.isConnected()) {
+ return false;
+ }
+
+ executeCommand(session, String.format(IP_ADDR_DELETE, ip, device));
+ Set<IpAddress> result = getCurrentIps(session, device);
+ return !result.contains(ip);
+ }
+
+ /**
+ * Removes all IP address on a given device.
+ *
+ * @param session ssh connection
+ * @param device device name
+ * @return true if the command succeeds, or false
+ */
+ public static boolean flushIp(Session session, String device) {
+ if (session == null || !session.isConnected()) {
+ return false;
+ }
+
+ executeCommand(session, String.format(IP_ADDR_FLUSH, device));
+ return getCurrentIps(session, device).isEmpty();
+ }
+
+ /**
+ * Returns a set of IP address that a given device has.
+ *
+ * @param session ssh connection
+ * @param device device name
+ * @return set of IP prefix or empty set
+ */
+ public static Set<IpAddress> getCurrentIps(Session session, String device) {
+ if (session == null || !session.isConnected()) {
+ return Sets.newHashSet();
+ }
+
+ String output = executeCommand(session, String.format(IP_ADDR_SHOW, device));
+ Set<IpAddress> result = Pattern.compile(" |/")
+ .splitAsStream(output)
+ .filter(s -> s.matches(IP_PATTERN))
+ .map(IpAddress::valueOf)
+ .collect(Collectors.toSet());
+
+ return result;
+ }
+
+ /**
+ * Sets link state up for a given device.
+ *
+ * @param session ssh connection
+ * @param device device name
+ * @return true if the command succeeds, or false
+ */
+ public static boolean setInterfaceUp(Session session, String device) {
+ if (session == null || !session.isConnected()) {
+ return false;
+ }
+
+ executeCommand(session, String.format(IP_LINK_UP, device));
+ return isInterfaceUp(session, device);
+ }
+
+ /**
+ * Checks if a given interface is up or not.
+ *
+ * @param session ssh connection
+ * @param device device name
+ * @return true if the interface is up, or false
+ */
+ public static boolean isInterfaceUp(Session session, String device) {
+ if (session == null || !session.isConnected()) {
+ return false;
+ }
+
+ String output = executeCommand(session, String.format(IP_LINK_SHOW, device));
+ return output != null && output.contains("UP");
+ }
+
+ /**
+ * Creates a new session with a given access information.
+ *
+ * @param sshInfo information to ssh to the remove server
+ * @return ssh session, or null
+ */
+ public static Session connect(SshAccessInfo sshInfo) {
+ try {
+ JSch jsch = new JSch();
+ jsch.addIdentity(sshInfo.privateKey());
+
+ Session session = jsch.getSession(sshInfo.user(),
+ sshInfo.remoteIp().toString(),
+ sshInfo.port().toInt());
+ session.setConfig(STRICT_HOST_CHECKING, DEFAULT_STRICT_HOST_CHECKING);
+ session.connect(DEFAULT_SESSION_TIMEOUT);
+
+ return session;
+ } catch (JSchException e) {
+ log.debug("Failed to connect to {} due to {}", sshInfo.toString(), e.toString());
+ return null;
+ }
+ }
+
+ /**
+ * Closes a connection.
+ *
+ * @param session session
+ */
+ public static void disconnect(Session session) {
+ if (session.isConnected()) {
+ session.disconnect();
+ }
+ }
+
+ /**
+ * Executes a given command. It opens exec channel for the command and closes
+ * the channel when it's done.
+ *
+ * @param session ssh connection to a remote server
+ * @param command command to execute
+ * @return command output string if the command succeeds, or null
+ */
+ private static String executeCommand(Session session, String command) {
+ if (session == null || !session.isConnected()) {
+ return null;
+ }
+
+ log.trace("Execute command {} to {}", command, session.getHost());
+
+ try {
+ Channel channel = session.openChannel("exec");
+ ((ChannelExec) channel).setCommand(command);
+ channel.setInputStream(null);
+ InputStream output = channel.getInputStream();
+
+ channel.connect();
+ String result = CharStreams.toString(new InputStreamReader(output));
+ channel.disconnect();
+
+ return result;
+ } catch (JSchException | IOException e) {
+ log.debug("Failed to execute command {} due to {}", command, e.toString());
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/impl/package-info.java b/src/main/java/org/onosproject/cordvtn/impl/package-info.java
new file mode 100644
index 0000000..fcc52ff
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Implementation for CORD VTN application.
+ */
+package org.onosproject.cordvtn.impl;
\ No newline at end of file
diff --git a/src/main/java/org/onosproject/cordvtn/impl/service/DummyInstanceHandler.java b/src/main/java/org/onosproject/cordvtn/impl/service/DummyInstanceHandler.java
new file mode 100644
index 0000000..ee78218
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/impl/service/DummyInstanceHandler.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.impl.service;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+
+import org.apache.felix.scr.annotations.Deactivate;
+import org.onosproject.cordvtn.api.Instance;
+import org.onosproject.cordvtn.api.InstanceHandler;
+import org.onosproject.cordvtn.impl.CordVtnInstanceHandler;
+import org.onosproject.xosclient.api.VtnService;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+
+/**
+ * Provides network connectivity for dummy service instances.
+ */
+@Component(immediate = true)
+public class DummyInstanceHandler extends CordVtnInstanceHandler implements InstanceHandler {
+
+ @Activate
+ protected void activate() {
+ serviceType = VtnService.ServiceType.DUMMY;
+ eventExecutor = newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn-dummy", "event-handler"));
+ super.activate();
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ super.deactivate();
+ }
+
+ @Override
+ public void instanceDetected(Instance instance) {
+ super.instanceDetected(instance);
+ }
+
+ @Override
+ public void instanceRemoved(Instance instance) {
+ super.instanceRemoved(instance);
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/impl/service/OltAgentInstanceHandler.java b/src/main/java/org/onosproject/cordvtn/impl/service/OltAgentInstanceHandler.java
new file mode 100644
index 0000000..eaec689
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/impl/service/OltAgentInstanceHandler.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.impl.service;
+
+import com.google.common.collect.Maps;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.cordconfig.access.AccessAgentConfig;
+import org.onosproject.cordconfig.access.AccessAgentData;
+import org.onosproject.cordvtn.api.CordVtnConfig;
+import org.onosproject.cordvtn.api.Instance;
+import org.onosproject.cordvtn.api.InstanceHandler;
+import org.onosproject.cordvtn.impl.CordVtnInstanceHandler;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.basics.SubjectFactories;
+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.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.xosclient.api.VtnService;
+
+import java.util.Map;
+import java.util.Set;
+
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.cordvtn.impl.CordVtnPipeline.PRIORITY_MANAGEMENT;
+import static org.onosproject.cordvtn.impl.CordVtnPipeline.TABLE_ACCESS_TYPE;
+
+/**
+ * Provides network connectivity for OLT agent instances.
+ */
+@Component(immediate = true)
+public class OltAgentInstanceHandler extends CordVtnInstanceHandler implements InstanceHandler {
+
+ private static final Class<AccessAgentConfig> CONFIG_CLASS = AccessAgentConfig.class;
+ private ConfigFactory<DeviceId, AccessAgentConfig> configFactory =
+ new ConfigFactory<DeviceId, AccessAgentConfig>(
+ SubjectFactories.DEVICE_SUBJECT_FACTORY, CONFIG_CLASS, "accessAgent") {
+ @Override
+ public AccessAgentConfig createConfig() {
+ return new AccessAgentConfig();
+ }
+ };
+
+ private Map<DeviceId, AccessAgentData> oltAgentData = Maps.newConcurrentMap();
+ private IpPrefix mgmtIpRange = null;
+
+ @Activate
+ protected void activate() {
+ eventExecutor = newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn-olt", "event-handler"));
+ serviceType = VtnService.ServiceType.OLT_AGENT;
+
+ configRegistry.registerConfigFactory(configFactory);
+ configListener = new InternalConfigListener();
+
+ super.activate();
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ super.deactivate();
+ }
+
+ @Override
+ public void instanceDetected(Instance instance) {
+ log.info("OLT agent instance detected {}", instance);
+
+ managementAccessRule(instance.deviceId(), true);
+ // TODO implement
+ }
+
+ @Override
+ public void instanceRemoved(Instance instance) {
+ log.info("OLT agent instance removed {}", instance);
+
+ if (getInstances(instance.serviceId()).isEmpty()) {
+ nodeManager.completeNodes().stream().forEach(node ->
+ managementAccessRule(node.intBrId(), false));
+ }
+
+ // TODO implement
+ }
+
+ private void managementAccessRule(DeviceId deviceId, boolean install) {
+ // TODO remove this rule after long term management network is done
+ if (mgmtIpRange != null) {
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(mgmtIpRange)
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.LOCAL)
+ .build();
+
+ FlowRule flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_MANAGEMENT)
+ .forDevice(deviceId)
+ .forTable(TABLE_ACCESS_TYPE)
+ .makePermanent()
+ .build();
+
+ pipeline.processFlowRule(install, flowRule);
+ }
+ }
+
+ private void readAccessAgentConfig() {
+
+ Set<DeviceId> deviceSubjects = configRegistry.getSubjects(DeviceId.class, CONFIG_CLASS);
+ deviceSubjects.stream().forEach(subject -> {
+ AccessAgentConfig config = configRegistry.getConfig(subject, CONFIG_CLASS);
+ if (config != null) {
+ oltAgentData.put(subject, config.getAgent());
+ }
+ });
+ }
+
+ @Override
+ protected void readConfiguration() {
+ CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
+ if (config == null) {
+ log.debug("No configuration found");
+ return;
+ }
+
+ osAccess = config.openstackAccess();
+ xosAccess = config.xosAccess();
+ mgmtIpRange = config.managementIpRange();
+ }
+
+ public class InternalConfigListener implements NetworkConfigListener {
+
+ @Override
+ public void event(NetworkConfigEvent event) {
+
+ switch (event.type()) {
+ case CONFIG_UPDATED:
+ case CONFIG_ADDED:
+ if (event.configClass().equals(CordVtnConfig.class)) {
+ readConfiguration();
+ } else if (event.configClass().equals(CONFIG_CLASS)) {
+ readAccessAgentConfig();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/impl/service/VsgInstanceHandler.java b/src/main/java/org/onosproject/cordvtn/impl/service/VsgInstanceHandler.java
new file mode 100644
index 0000000..f60f72b
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/impl/service/VsgInstanceHandler.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.impl.service;
+
+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.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.cordvtn.api.Instance;
+import org.onosproject.cordvtn.api.InstanceHandler;
+import org.onosproject.cordvtn.impl.CordVtnInstanceHandler;
+import org.onosproject.cordvtn.impl.CordVtnInstanceManager;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.HostId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.IPCriterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.flow.instructions.L2ModificationInstruction;
+import org.onosproject.net.host.DefaultHostDescription;
+import org.onosproject.net.host.HostDescription;
+import org.onosproject.xosclient.api.VtnPort;
+import org.onosproject.xosclient.api.VtnPortApi;
+import org.onosproject.xosclient.api.VtnPortId;
+import org.onosproject.xosclient.api.VtnService;
+
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.cordvtn.api.Instance.*;
+import static org.onosproject.cordvtn.impl.CordVtnPipeline.*;
+import static org.onosproject.net.flow.criteria.Criterion.Type.IPV4_DST;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_PUSH;
+
+/**
+ * Provides network connectivity for vSG instances.
+ */
+@Component(immediate = true)
+@Service(value = VsgInstanceHandler.class)
+public final class VsgInstanceHandler extends CordVtnInstanceHandler implements InstanceHandler {
+
+ private static final String STAG = "stag";
+ private static final String VSG_VM = "vsgVm";
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected FlowRuleService flowRuleService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CordVtnInstanceManager instanceManager;
+
+ @Activate
+ protected void activate() {
+ serviceType = VtnService.ServiceType.VSG;
+ eventExecutor = newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn-vsg", "event-handler"));
+ super.activate();
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ super.deactivate();
+ }
+
+ @Override
+ public void instanceDetected(Instance instance) {
+ if (isVsgContainer(instance)) {
+ log.info("vSG container detected {}", instance);
+
+ // find vsg vm for this vsg container
+ String vsgVmId = instance.getAnnotation(VSG_VM);
+ if (Strings.isNullOrEmpty(vsgVmId)) {
+ log.warn("Failed to find VSG VM for {}", instance);
+ return;
+ }
+
+ Instance vsgVm = Instance.of(hostService.getHost(HostId.hostId(vsgVmId)));
+ VtnPort vtnPort = getVtnPort(vsgVm);
+ if (vtnPort == null || getStag(vtnPort) == null) {
+ return;
+ }
+
+ populateVsgRules(vsgVm, getStag(vtnPort),
+ nodeManager.dpPort(vsgVm.deviceId()),
+ vtnPort.addressPairs().keySet(),
+ true);
+
+ } else {
+ VtnPort vtnPort = getVtnPort(instance);
+ if (vtnPort == null || getStag(vtnPort) == null) {
+ return;
+ }
+
+ vtnPort.addressPairs().entrySet().stream()
+ .forEach(pair -> addVsgContainer(
+ instance,
+ pair.getKey(),
+ pair.getValue(),
+ getStag(vtnPort).toString()
+ ));
+ super.instanceDetected(instance);
+ }
+ }
+
+ @Override
+ public void instanceRemoved(Instance instance) {
+ if (isVsgContainer(instance)) {
+ log.info("vSG container vanished {}", instance);
+
+ // find vsg vm for this vsg container
+ String vsgVmId = instance.getAnnotation(VSG_VM);
+ if (Strings.isNullOrEmpty(vsgVmId)) {
+ log.warn("Failed to find VSG VM for {}", instance);
+ return;
+ }
+
+ Instance vsgVm = Instance.of(hostService.getHost(HostId.hostId(vsgVmId)));
+ VtnPort vtnPort = getVtnPort(vsgVm);
+ if (vtnPort == null || getStag(vtnPort) == null) {
+ return;
+ }
+
+ populateVsgRules(vsgVm, getStag(vtnPort),
+ nodeManager.dpPort(vsgVm.deviceId()),
+ vtnPort.addressPairs().keySet(),
+ false);
+
+ } else {
+ // TODO remove vsg vm related rules
+ super.instanceRemoved(instance);
+ }
+ }
+
+ /**
+ * Updates set of vSGs in a given vSG VM.
+ *
+ * @param vsgVmId vsg vm host id
+ * @param stag stag
+ * @param vsgInstances full set of vsg wan ip and mac address pairs in this vsg vm
+ */
+ public void updateVsgInstances(HostId vsgVmId, String stag, Map<IpAddress, MacAddress> vsgInstances) {
+ if (hostService.getHost(vsgVmId) == null) {
+ log.debug("vSG VM {} is not added yet, ignore this update", vsgVmId);
+ return;
+ }
+
+ Instance vsgVm = Instance.of(hostService.getHost(vsgVmId));
+ if (vsgVm == null) {
+ log.warn("Failed to find existing vSG VM for STAG: {}", stag);
+ return;
+ }
+
+ log.info("Updates vSGs in {} with STAG: {}", vsgVm, stag);
+
+ // adds vSGs in the address pair
+ vsgInstances.entrySet().stream()
+ .filter(addr -> hostService.getHostsByMac(addr.getValue()).isEmpty())
+ .forEach(addr -> addVsgContainer(
+ vsgVm,
+ addr.getKey(),
+ addr.getValue(),
+ stag));
+
+ // removes vSGs not listed in the address pair
+ hostService.getConnectedHosts(vsgVm.host().location()).stream()
+ .filter(host -> !host.mac().equals(vsgVm.mac()))
+ .filter(host -> !vsgInstances.values().contains(host.mac()))
+ .forEach(host -> {
+ log.info("Removed vSG {}", host.toString());
+ instanceManager.removeInstance(host.id());
+ });
+ }
+
+ private boolean isVsgContainer(Instance instance) {
+ return !Strings.isNullOrEmpty(instance.host().annotations().value(STAG));
+ }
+
+ private void addVsgContainer(Instance vsgVm, IpAddress vsgWanIp, MacAddress vsgMac,
+ String stag) {
+ HostId hostId = HostId.hostId(vsgMac);
+ DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
+ .set(SERVICE_TYPE, vsgVm.serviceType().toString())
+ .set(SERVICE_ID, vsgVm.serviceId().id())
+ .set(PORT_ID, vsgVm.portId().id())
+ .set(NESTED_INSTANCE, TRUE)
+ .set(STAG, stag)
+ .set(VSG_VM, vsgVm.host().id().toString())
+ .set(CREATE_TIME, String.valueOf(System.currentTimeMillis()));
+
+ HostDescription hostDesc = new DefaultHostDescription(
+ vsgMac,
+ VlanId.NONE,
+ vsgVm.host().location(),
+ Sets.newHashSet(vsgWanIp),
+ annotations.build());
+
+ instanceManager.addInstance(hostId, hostDesc);
+ }
+
+ private void populateVsgRules(Instance vsgVm, VlanId stag, PortNumber dpPort,
+ Set<IpAddress> vsgWanIps, boolean install) {
+ // for traffics with s-tag, strip the tag and take through the vSG VM
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .matchInPort(dpPort)
+ .matchVlanId(stag)
+ .build();
+
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .setOutput(vsgVm.portNumber())
+ .build();
+
+ FlowRule flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_DEFAULT)
+ .forDevice(vsgVm.deviceId())
+ .forTable(TABLE_VLAN)
+ .makePermanent()
+ .build();
+
+ pipeline.processFlowRule(install, flowRule);
+
+ // for traffics with customer vlan, tag with the service vlan based on input port with
+ // lower priority to avoid conflict with WAN tag
+ selector = DefaultTrafficSelector.builder()
+ .matchInPort(vsgVm.portNumber())
+ .matchVlanId(stag)
+ .build();
+
+ treatment = DefaultTrafficTreatment.builder()
+ .setOutput(dpPort)
+ .build();
+
+ flowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(selector)
+ .withTreatment(treatment)
+ .withPriority(PRIORITY_DEFAULT)
+ .forDevice(vsgVm.deviceId())
+ .forTable(TABLE_VLAN)
+ .makePermanent()
+ .build();
+
+ pipeline.processFlowRule(install, flowRule);
+
+ // for traffic coming from WAN, tag 500 and take through the vSG VM
+ // based on destination ip
+ vsgWanIps.stream().forEach(ip -> {
+ TrafficSelector downstream = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPDst(ip.toIpPrefix())
+ .build();
+
+ TrafficTreatment downstreamTreatment = DefaultTrafficTreatment.builder()
+ .pushVlan()
+ .setVlanId(VLAN_WAN)
+ .setEthDst(vsgVm.mac())
+ .setOutput(vsgVm.portNumber())
+ .build();
+
+ FlowRule downstreamFlowRule = DefaultFlowRule.builder()
+ .fromApp(appId)
+ .withSelector(downstream)
+ .withTreatment(downstreamTreatment)
+ .withPriority(PRIORITY_DEFAULT)
+ .forDevice(vsgVm.deviceId())
+ .forTable(TABLE_DST_IP)
+ .makePermanent()
+ .build();
+
+ pipeline.processFlowRule(install, downstreamFlowRule);
+ });
+
+ // remove downstream flow rules for the vSG not shown in vsgWanIps
+ for (FlowRule rule : flowRuleService.getFlowRulesById(appId)) {
+ if (!rule.deviceId().equals(vsgVm.deviceId())) {
+ continue;
+ }
+ PortNumber output = getOutputFromTreatment(rule);
+ if (output == null || !output.equals(vsgVm.portNumber()) ||
+ !isVlanPushFromTreatment(rule)) {
+ continue;
+ }
+
+ IpPrefix dstIp = getDstIpFromSelector(rule);
+ if (dstIp != null && !vsgWanIps.contains(dstIp.address())) {
+ pipeline.processFlowRule(false, rule);
+ }
+ }
+ }
+
+ private VtnPort getVtnPort(Instance instance) {
+ checkNotNull(osAccess, OPENSTACK_ACCESS_ERROR);
+ checkNotNull(xosAccess, XOS_ACCESS_ERROR);
+
+ VtnPortId vtnPortId = instance.portId();
+ VtnPortApi portApi = xosClient.getClient(xosAccess).vtnPort();
+ VtnPort vtnPort = portApi.vtnPort(vtnPortId, osAccess);
+ if (vtnPort == null) {
+ log.warn("Failed to get port information of {}", instance);
+ return null;
+ }
+ return vtnPort;
+ }
+
+ // TODO get stag from XOS when XOS provides it, extract if from port name for now
+ private VlanId getStag(VtnPort vtnPort) {
+ checkNotNull(vtnPort);
+
+ String portName = vtnPort.name();
+ if (portName != null && portName.startsWith(STAG)) {
+ return VlanId.vlanId(portName.split("-")[1]);
+ } else {
+ return null;
+ }
+ }
+
+ private PortNumber getOutputFromTreatment(FlowRule flowRule) {
+ Instruction instruction = flowRule.treatment().allInstructions().stream()
+ .filter(inst -> inst instanceof Instructions.OutputInstruction)
+ .findFirst()
+ .orElse(null);
+ if (instruction == null) {
+ return null;
+ }
+ return ((Instructions.OutputInstruction) instruction).port();
+ }
+
+ private IpPrefix getDstIpFromSelector(FlowRule flowRule) {
+ Criterion criterion = flowRule.selector().getCriterion(IPV4_DST);
+ if (criterion != null && criterion instanceof IPCriterion) {
+ IPCriterion ip = (IPCriterion) criterion;
+ return ip.ip();
+ } else {
+ return null;
+ }
+ }
+
+ private boolean isVlanPushFromTreatment(FlowRule flowRule) {
+ Instruction instruction = flowRule.treatment().allInstructions().stream()
+ .filter(inst -> inst instanceof L2ModificationInstruction)
+ .filter(inst -> ((L2ModificationInstruction) inst).subtype().equals(VLAN_PUSH))
+ .findAny()
+ .orElse(null);
+ return instruction != null;
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/impl/service/package-info.java b/src/main/java/org/onosproject/cordvtn/impl/service/package-info.java
new file mode 100644
index 0000000..035b012
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/impl/service/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Implementation of instance handlers for various network services.
+ */
+package org.onosproject.cordvtn.impl.service;
\ No newline at end of file
diff --git a/src/main/java/org/onosproject/cordvtn/rest/CordVtnWebApplication.java b/src/main/java/org/onosproject/cordvtn/rest/CordVtnWebApplication.java
new file mode 100644
index 0000000..e1193df
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/rest/CordVtnWebApplication.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2015-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.cordvtn.rest;
+
+import org.onlab.rest.AbstractWebApplication;
+
+import java.util.Set;
+
+/**
+ * CORD VTN Web application.
+ */
+public class CordVtnWebApplication extends AbstractWebApplication {
+ @Override
+ public Set<Class<?>> getClasses() {
+ return getClasses(ServiceDependencyWebResource.class,
+ NeutronMl2NetworksWebResource.class,
+ NeutronMl2SubnetsWebResource.class,
+ NeutronMl2PortsWebResource.class);
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/rest/NeutronMl2NetworksWebResource.java b/src/main/java/org/onosproject/cordvtn/rest/NeutronMl2NetworksWebResource.java
new file mode 100644
index 0000000..ebb3be4
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/rest/NeutronMl2NetworksWebResource.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.rest;
+
+import org.onosproject.rest.AbstractWebResource;
+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.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.InputStream;
+
+/**
+ * Dummy Neutron ML2 mechanism driver.
+ * It just returns OK for networks resource requests.
+ */
+@Path("networks")
+public class NeutronMl2NetworksWebResource extends AbstractWebResource {
+ protected final Logger log = LoggerFactory.getLogger(getClass());
+ private static final String NETWORKS_MESSAGE = "Received networks %s";
+
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response createNetwork(InputStream input) {
+ log.trace(String.format(NETWORKS_MESSAGE, "create"));
+ return Response.status(Response.Status.OK).build();
+ }
+
+ @PUT
+ @Path("{id}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response updateNetwork(@PathParam("id") String id, InputStream input) {
+ log.trace(String.format(NETWORKS_MESSAGE, "update"));
+ return Response.status(Response.Status.OK).build();
+ }
+
+ @DELETE
+ @Path("{id}")
+ public Response deleteNetwork(@PathParam("id") String id) {
+ log.trace(String.format(NETWORKS_MESSAGE, "delete"));
+ return Response.noContent().build();
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/rest/NeutronMl2PortsWebResource.java b/src/main/java/org/onosproject/cordvtn/rest/NeutronMl2PortsWebResource.java
new file mode 100644
index 0000000..bf8d79d
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/rest/NeutronMl2PortsWebResource.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.rest;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.Maps;
+import org.onlab.osgi.DefaultServiceDirectory;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onosproject.cordvtn.impl.service.VsgInstanceHandler;
+import org.onosproject.net.HostId;
+import org.onosproject.rest.AbstractWebResource;
+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.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.InputStream;
+import java.util.Map;
+
+
+/**
+ * Dummy Neutron ML2 mechanism driver.
+ * It just returns OK for ports resource requests except for the port update.
+ */
+@Path("ports")
+public class NeutronMl2PortsWebResource extends AbstractWebResource {
+ protected final Logger log = LoggerFactory.getLogger(getClass());
+ private static final String PORTS_MESSAGE = "Received ports %s";
+
+ private static final String PORT = "port";
+ private static final String DEVICE_ID = "device_id";
+ private static final String NAME = "name";
+ private static final String MAC_ADDRESS = "mac_address";
+ private static final String ADDRESS_PAIRS = "allowed_address_pairs";
+ private static final String IP_ADDERSS = "ip_address";
+ private static final String STAG_PREFIX = "stag-";
+ private static final int STAG_BEGIN_INDEX = 5;
+
+ private final VsgInstanceHandler service = DefaultServiceDirectory.getService(VsgInstanceHandler.class);
+
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response createPorts(InputStream input) {
+ log.trace(String.format(PORTS_MESSAGE, "create"));
+ return Response.status(Response.Status.OK).build();
+ }
+
+ @PUT
+ @Path("{id}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response updatePorts(@PathParam("id") String id, InputStream input) {
+ log.debug(String.format(PORTS_MESSAGE, "update"));
+
+ // TODO get vSG updates from XOS to CORD VTN service directly
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode jsonNode = mapper.readTree(input).get(PORT);
+ log.trace("{}", jsonNode.toString());
+
+ String deviceId = jsonNode.path(DEVICE_ID).asText();
+ String name = jsonNode.path(NAME).asText();
+ if (deviceId.isEmpty() || name.isEmpty() || !name.startsWith(STAG_PREFIX)) {
+ // ignore all updates other than allowed address pairs
+ return Response.status(Response.Status.OK).build();
+ }
+
+ // this is allowed address pairs updates
+ MacAddress mac = MacAddress.valueOf(jsonNode.path(MAC_ADDRESS).asText());
+ Map<IpAddress, MacAddress> vsgInstances = Maps.newHashMap();
+ jsonNode.path(ADDRESS_PAIRS).forEach(addrPair -> {
+ IpAddress pairIp = IpAddress.valueOf(addrPair.path(IP_ADDERSS).asText());
+ MacAddress pairMac = MacAddress.valueOf(addrPair.path(MAC_ADDRESS).asText());
+ vsgInstances.put(pairIp, pairMac);
+ });
+
+ service.updateVsgInstances(HostId.hostId(mac),
+ name.substring(STAG_BEGIN_INDEX),
+ vsgInstances);
+ } catch (Exception e) {
+ return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+ }
+
+ return Response.status(Response.Status.OK).build();
+ }
+
+ @Path("{id}")
+ @DELETE
+ public Response deletePorts(@PathParam("id") String id) {
+ log.trace(String.format(PORTS_MESSAGE, "delete"));
+ return Response.noContent().build();
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/rest/NeutronMl2SubnetsWebResource.java b/src/main/java/org/onosproject/cordvtn/rest/NeutronMl2SubnetsWebResource.java
new file mode 100644
index 0000000..c6b9544
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/rest/NeutronMl2SubnetsWebResource.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.rest;
+
+import org.onosproject.rest.AbstractWebResource;
+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.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.InputStream;
+
+/**
+ * Dummy Neutron ML2 mechanism driver.
+ * It just returns OK for subnets resource requests.
+ */
+@Path("subnets")
+public class NeutronMl2SubnetsWebResource extends AbstractWebResource {
+ protected final Logger log = LoggerFactory.getLogger(getClass());
+ private static final String SUBNETS_MESSAGE = "Received subnets %s";
+
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response createSubnet(InputStream input) {
+ log.trace(String.format(SUBNETS_MESSAGE, "create"));
+ return Response.status(Response.Status.OK).build();
+ }
+
+
+ @PUT
+ @Path("{id}")
+ @Produces(MediaType.APPLICATION_JSON)
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response updateSubnet(@PathParam("id") String id, InputStream input) {
+ log.trace(String.format(SUBNETS_MESSAGE, "update"));
+ return Response.status(Response.Status.OK).build();
+
+ }
+
+ @DELETE
+ @Path("{id}")
+ public Response deleteSubnet(@PathParam("id") String id) {
+ log.trace(String.format(SUBNETS_MESSAGE, "delete"));
+ return Response.noContent().build();
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/rest/ServiceDependencyWebResource.java b/src/main/java/org/onosproject/cordvtn/rest/ServiceDependencyWebResource.java
new file mode 100644
index 0000000..f7ddf96
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/rest/ServiceDependencyWebResource.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2015-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cordvtn.rest;
+
+import org.onosproject.cordvtn.api.CordVtnService;
+import org.onosproject.rest.AbstractWebResource;
+import org.onosproject.xosclient.api.VtnServiceId;
+
+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.MediaType;
+import javax.ws.rs.core.Response;
+
+/**
+ * Manages service dependency.
+ */
+@Path("service-dependency")
+public class ServiceDependencyWebResource extends AbstractWebResource {
+
+ private final CordVtnService service = get(CordVtnService.class);
+ private static final String BIDIRECTION = "b";
+
+ /**
+ * Creates service dependencies with unidirectional access between the services.
+ *
+ * @param tServiceId tenant service id
+ * @param pServiceId provider service id
+ * @return 200 OK
+ */
+ @POST
+ @Path("{tenantServiceId}/{providerServiceId}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response createServiceDependency(@PathParam("tenantServiceId") String tServiceId,
+ @PathParam("providerServiceId") String pServiceId) {
+ service.createServiceDependency(VtnServiceId.of(tServiceId),
+ VtnServiceId.of(pServiceId),
+ false);
+ return Response.status(Response.Status.OK).build();
+ }
+
+ /**
+ * Creates service dependencies with an access type extension between the services.
+ *
+ * @param tServiceId tenant service id
+ * @param pServiceId provider service id
+ * @param direction b for bidirectional access, otherwise unidirectional access
+ * @return 200 OK
+ */
+ @POST
+ @Path("{tenantServiceId}/{providerServiceId}/{direction}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response createServiceDependency(@PathParam("tenantServiceId") String tServiceId,
+ @PathParam("providerServiceId") String pServiceId,
+ @PathParam("direction") String direction) {
+ service.createServiceDependency(VtnServiceId.of(tServiceId),
+ VtnServiceId.of(pServiceId),
+ direction.equals(BIDIRECTION));
+ return Response.status(Response.Status.OK).build();
+ }
+
+ /**
+ * Removes service dependencies.
+ *
+ * @param tServiceId tenant service id
+ * @param pServiceId provider service id
+ * @return 204 NO CONTENT
+ */
+ @DELETE
+ @Path("{tenantServiceId}/{providerServiceId}")
+ public Response removeServiceDependency(@PathParam("tenantServiceId") String tServiceId,
+ @PathParam("providerServiceId") String pServiceId) {
+ service.removeServiceDependency(VtnServiceId.of(tServiceId), VtnServiceId.of(pServiceId));
+ return Response.noContent().build();
+ }
+}
diff --git a/src/main/java/org/onosproject/cordvtn/rest/package-info.java b/src/main/java/org/onosproject/cordvtn/rest/package-info.java
new file mode 100644
index 0000000..0b7e606
--- /dev/null
+++ b/src/main/java/org/onosproject/cordvtn/rest/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * REST APIs for CORD VTN.
+ */
+package org.onosproject.cordvtn.rest;
\ No newline at end of file
diff --git a/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..07704ce
--- /dev/null
+++ b/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,35 @@
+<!--
+ ~ Copyright 2015-present Open Networking Laboratory
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<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.cordvtn.cli.CordVtnNodeListCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.cordvtn.cli.CordVtnNodeDeleteCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.cordvtn.cli.CordVtnNodeInitCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.cordvtn.cli.CordVtnNodeCheckCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.cordvtn.cli.CordVtnFlushRules"/>
+ </command>
+ </command-bundle>
+</blueprint>
diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..37aea15
--- /dev/null
+++ b/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2015-present Open Networking Laboratory
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<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>CORD VTN 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.cordvtn.rest.CordVtnWebApplication</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>