mirror of
https://github.com/opennetworkinglab/onos.git
synced 2026-05-04 11:51:43 +02:00
[ONOS-3688] Implement application for Openstack Node bootstrap
- Using OVSDB connnection, it makes bridge and vxlan tunnel for Openstack Nodes. - Reading nodes information from network config supported Change-Id: I1c0483b146ace19cd77ac91141182ee9200a9432
This commit is contained in:
parent
3a06c522d8
commit
a7d6e9fada
34
apps/openstacknode/network-cfg.json
Normal file
34
apps/openstacknode/network-cfg.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"apps" : {
|
||||
"org.onosproject.openstacknode" : {
|
||||
"openstacknode" : {
|
||||
"nodes" : [
|
||||
{
|
||||
"hostname" : "compute-01",
|
||||
"ovsdbIp" : "192.168.56.112",
|
||||
"ovsdbPort" : "6640",
|
||||
"bridgeId" : "of:0000000000000001",
|
||||
"openstackNodeType" : "COMPUTENODE"
|
||||
},
|
||||
{
|
||||
"hostname" : "compute-02",
|
||||
"ovsdbIp" : "192.168.56.106",
|
||||
"ovsdbPort" : "6640",
|
||||
"bridgeId" : "of:0000000000000002",
|
||||
"openstackNodeType" : "COMPUTENODE"
|
||||
},
|
||||
{
|
||||
"hostname" : "network",
|
||||
"ovsdbIp" : "192.168.56.108",
|
||||
"ovsdbPort" : "6640",
|
||||
"bridgeId" : "of:0000000000000003",
|
||||
"openstackNodeType" : "GATEWAYNODE",
|
||||
"gatewayExternalInterfaceName" : "eth1",
|
||||
"gatewayExternalInterfaceMac" : "00:00:00:00:00:10"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
73
apps/openstacknode/pom.xml
Normal file
73
apps/openstacknode/pom.xml
Normal file
@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2016 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.5.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>onos-app-openstacknode</artifactId>
|
||||
<packaging>bundle</packaging>
|
||||
|
||||
<description>SONA Openstack Node Bootstrap Application</description>
|
||||
|
||||
|
||||
<properties>
|
||||
<onos.app.name>org.onosproject.openstacknode</onos.app.name>
|
||||
<onos.app.category>default</onos.app.category>
|
||||
<onos.app.url>http://onosproject.org</onos.app.url>
|
||||
<onos.app.readme>SONA Openstack Node Bootstrap Application</onos.app.readme>
|
||||
<onos.app.requires>
|
||||
org.onosproject.ovsdb
|
||||
</onos.app.requires>
|
||||
</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>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2016 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.openstacknode;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright 2016 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.openstacknode;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import org.onlab.packet.IpAddress;
|
||||
import org.onlab.packet.MacAddress;
|
||||
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/gateway node for OpenstackSwitching/Routing service.
|
||||
*/
|
||||
public final class OpenstackNode {
|
||||
|
||||
private final String hostName;
|
||||
private final IpAddress ovsdbIp;
|
||||
private final TpPort ovsdbPort;
|
||||
private final DeviceId bridgeId;
|
||||
private final OpenstackNodeService.OpenstackNodeType openstackNodeType;
|
||||
private final String gatewayExternalInterfaceName;
|
||||
private final MacAddress gatewayExternalInterfaceMac;
|
||||
private static final String OVSDB = "ovsdb:";
|
||||
|
||||
|
||||
public static final Comparator<OpenstackNode> OPENSTACK_NODE_COMPARATOR =
|
||||
(node1, node2) -> node1.hostName().compareTo(node2.hostName());
|
||||
|
||||
/**
|
||||
* Creates a new node.
|
||||
*
|
||||
* @param hostName hostName
|
||||
* @param ovsdbIp OVSDB server IP address
|
||||
* @param ovsdbPort OVSDB server port number
|
||||
* @param bridgeId integration bridge identifier
|
||||
* @param openstackNodeType openstack node type
|
||||
* @param gatewayExternalInterfaceName gatewayExternalInterfaceName
|
||||
* @param gatewayExternalInterfaceMac gatewayExternalInterfaceMac
|
||||
*/
|
||||
public OpenstackNode(String hostName, IpAddress ovsdbIp, TpPort ovsdbPort, DeviceId bridgeId,
|
||||
OpenstackNodeService.OpenstackNodeType openstackNodeType,
|
||||
String gatewayExternalInterfaceName,
|
||||
MacAddress gatewayExternalInterfaceMac) {
|
||||
this.hostName = checkNotNull(hostName, "hostName cannot be null");
|
||||
this.ovsdbIp = checkNotNull(ovsdbIp, "ovsdbIp cannot be null");
|
||||
this.ovsdbPort = checkNotNull(ovsdbPort, "ovsdbPort cannot be null");
|
||||
this.bridgeId = checkNotNull(bridgeId, "bridgeId cannot be null");
|
||||
this.openstackNodeType = checkNotNull(openstackNodeType, "openstackNodeType cannot be null");
|
||||
this.gatewayExternalInterfaceName = gatewayExternalInterfaceName;
|
||||
this.gatewayExternalInterfaceMac = gatewayExternalInterfaceMac;
|
||||
|
||||
if (openstackNodeType == OpenstackNodeService.OpenstackNodeType.GATEWAYNODE) {
|
||||
checkNotNull(gatewayExternalInterfaceName, "gatewayExternalInterfaceName cannot be null");
|
||||
checkNotNull(gatewayExternalInterfaceMac, "gatewayExternalInterfaceMac cannot be null");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OVSDB server IP address.
|
||||
*
|
||||
* @return ip address
|
||||
*/
|
||||
public IpAddress ovsdbIp() {
|
||||
return this.ovsdbIp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OVSDB server port number.
|
||||
*
|
||||
* @return port number
|
||||
*/
|
||||
public TpPort ovsdbPort() {
|
||||
return this.ovsdbPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hostName.
|
||||
*
|
||||
* @return hostName
|
||||
*/
|
||||
public String hostName() {
|
||||
return this.hostName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.concat(this.ovsdbIp.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the openstack node type.
|
||||
*
|
||||
* @return openstack node type
|
||||
*/
|
||||
public OpenstackNodeService.OpenstackNodeType openstackNodeType() {
|
||||
return this.openstackNodeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the gatewayExternalInterfaceName.
|
||||
*
|
||||
* @return gatewayExternalInterfaceName
|
||||
*/
|
||||
public String gatewayExternalInterfaceName() {
|
||||
return this.gatewayExternalInterfaceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the gatewayExternalInterfaceMac.
|
||||
*
|
||||
* @return gatewayExternalInterfaceMac
|
||||
*/
|
||||
public MacAddress gatewayExternalInterfaceMac() {
|
||||
return this.gatewayExternalInterfaceMac;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj instanceof OpenstackNode) {
|
||||
OpenstackNode that = (OpenstackNode) obj;
|
||||
|
||||
if (Objects.equals(hostName, that.hostName) &&
|
||||
Objects.equals(ovsdbIp, that.ovsdbIp) &&
|
||||
Objects.equals(ovsdbPort, that.ovsdbPort) &&
|
||||
Objects.equals(bridgeId, that.bridgeId) &&
|
||||
Objects.equals(openstackNodeType, that.openstackNodeType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(hostName, ovsdbIp, ovsdbPort, bridgeId, openstackNodeType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (openstackNodeType == OpenstackNodeService.OpenstackNodeType.COMPUTENODE) {
|
||||
return MoreObjects.toStringHelper(getClass())
|
||||
.add("host", hostName)
|
||||
.add("ip", ovsdbIp)
|
||||
.add("port", ovsdbPort)
|
||||
.add("bridgeId", bridgeId)
|
||||
.add("openstacknodetype", openstackNodeType)
|
||||
.toString();
|
||||
} else {
|
||||
return MoreObjects.toStringHelper(getClass())
|
||||
.add("host", hostName)
|
||||
.add("ip", ovsdbIp)
|
||||
.add("port", ovsdbPort)
|
||||
.add("bridgeId", bridgeId)
|
||||
.add("openstacknodetype", openstackNodeType)
|
||||
.add("gatewayExternalInterfaceName", gatewayExternalInterfaceName)
|
||||
.add("gatewayExternalInterfaceMac", gatewayExternalInterfaceMac)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2016 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.openstacknode;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.onlab.packet.Ip4Address;
|
||||
import org.onlab.packet.MacAddress;
|
||||
import org.onlab.packet.TpPort;
|
||||
import org.onosproject.core.ApplicationId;
|
||||
import org.onosproject.net.DeviceId;
|
||||
import org.slf4j.Logger;
|
||||
import java.util.Set;
|
||||
import org.onosproject.net.config.Config;
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
/**
|
||||
* Configuration object for OpensatckNode service.
|
||||
*/
|
||||
public class OpenstackNodeConfig extends Config<ApplicationId> {
|
||||
|
||||
protected final Logger log = getLogger(getClass());
|
||||
|
||||
|
||||
public static final String NODES = "nodes";
|
||||
public static final String HOST_NAME = "hostname";
|
||||
public static final String OVSDB_IP = "ovsdbIp";
|
||||
public static final String OVSDB_PORT = "ovsdbPort";
|
||||
public static final String BRIDGE_ID = "bridgeId";
|
||||
public static final String NODE_TYPE = "openstackNodeType";
|
||||
public static final String GATEWAY_EXTERNAL_INTERFACE_NAME = "gatewayExternalInterfaceName";
|
||||
public static final String GATEWAY_EXTERNAL_INTERFACE_MAC = "gatewayExternalInterfaceMac";
|
||||
|
||||
/**
|
||||
* Returns the set of nodes read from network config.
|
||||
*
|
||||
* @return set of OpensatckNodeConfig or null
|
||||
*/
|
||||
public Set<OpenstackNode> openstackNodes() {
|
||||
|
||||
Set<OpenstackNode> nodes = Sets.newHashSet();
|
||||
|
||||
JsonNode jsonNodes = object.get(NODES);
|
||||
if (jsonNodes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
jsonNodes.forEach(jsonNode -> {
|
||||
try {
|
||||
if (OpenstackNodeService.OpenstackNodeType.valueOf(jsonNode.path(NODE_TYPE).asText()) ==
|
||||
OpenstackNodeService.OpenstackNodeType.COMPUTENODE) {
|
||||
nodes.add(new OpenstackNode(
|
||||
jsonNode.path(HOST_NAME).asText(),
|
||||
Ip4Address.valueOf(jsonNode.path(OVSDB_IP).asText()),
|
||||
TpPort.tpPort(jsonNode.path(OVSDB_PORT).asInt()),
|
||||
DeviceId.deviceId(jsonNode.path(BRIDGE_ID).asText()),
|
||||
OpenstackNodeService.OpenstackNodeType.valueOf(jsonNode.path(NODE_TYPE).asText()),
|
||||
null, MacAddress.NONE));
|
||||
} else {
|
||||
nodes.add(new OpenstackNode(
|
||||
jsonNode.path(HOST_NAME).asText(),
|
||||
Ip4Address.valueOf(jsonNode.path(OVSDB_IP).asText()),
|
||||
TpPort.tpPort(jsonNode.path(OVSDB_PORT).asInt()),
|
||||
DeviceId.deviceId(jsonNode.path(BRIDGE_ID).asText()),
|
||||
OpenstackNodeService.OpenstackNodeType.valueOf(jsonNode.path(NODE_TYPE).asText()),
|
||||
jsonNode.path(GATEWAY_EXTERNAL_INTERFACE_NAME).asText(),
|
||||
MacAddress.valueOf(jsonNode.path(GATEWAY_EXTERNAL_INTERFACE_MAC).asText())));
|
||||
}
|
||||
} catch (IllegalArgumentException | NullPointerException e) {
|
||||
log.error("Failed to read {}", e.toString());
|
||||
}
|
||||
});
|
||||
return nodes;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,630 @@
|
||||
/*
|
||||
* Copyright 2016 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.openstacknode;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
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.util.ItemNotFoundException;
|
||||
import org.onlab.util.KryoNamespace;
|
||||
import org.onosproject.cluster.ClusterService;
|
||||
import org.onosproject.core.ApplicationId;
|
||||
import org.onosproject.core.CoreService;
|
||||
import org.onosproject.net.DefaultAnnotations;
|
||||
import org.onosproject.net.Device;
|
||||
import org.onosproject.net.DeviceId;
|
||||
import org.onosproject.net.Port;
|
||||
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.ConfigFactory;
|
||||
import org.onosproject.net.config.NetworkConfigEvent;
|
||||
import org.onosproject.net.config.NetworkConfigListener;
|
||||
import org.onosproject.net.config.NetworkConfigRegistry;
|
||||
import org.onosproject.net.config.NetworkConfigService;
|
||||
import org.onosproject.net.config.basics.SubjectFactories;
|
||||
import org.onosproject.net.device.DeviceAdminService;
|
||||
import org.onosproject.net.device.DeviceEvent;
|
||||
import org.onosproject.net.device.DeviceListener;
|
||||
import org.onosproject.net.device.DeviceService;
|
||||
import org.onosproject.net.driver.DriverHandler;
|
||||
import org.onosproject.net.driver.DriverService;
|
||||
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.Serializer;
|
||||
import org.onosproject.store.service.StorageService;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import static org.onlab.util.Tools.groupedThreads;
|
||||
import static org.onosproject.net.Device.Type.SWITCH;
|
||||
import static org.onosproject.net.behaviour.TunnelDescription.Type.VXLAN;
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
|
||||
/**
|
||||
* Initializes devices in compute/gateway nodes according to there type.
|
||||
*/
|
||||
@Component(immediate = true)
|
||||
@Service
|
||||
public class OpenstackNodeManager implements OpenstackNodeService {
|
||||
protected final Logger log = getLogger(getClass());
|
||||
private static final int NUM_THREADS = 1;
|
||||
private static final KryoNamespace.Builder NODE_SERIALIZER = KryoNamespace.newBuilder()
|
||||
.register(KryoNamespaces.API)
|
||||
.register(OpenstackNode.class)
|
||||
.register(OpenstackNodeType.class)
|
||||
.register(NodeState.class);
|
||||
private static final String DEFAULT_BRIDGE = "br-int";
|
||||
private static final String DEFAULT_TUNNEL = "vxlan";
|
||||
private static final String PORT_NAME = "portName";
|
||||
private static final String OPENSTACK_NODESTORE = "openstacknode-nodestore";
|
||||
private static final String OPENSTACK_NODEMANAGER_ID = "org.onosproject.openstacknode";
|
||||
|
||||
private static final Map<String, String> DEFAULT_TUNNEL_OPTIONS
|
||||
= ImmutableMap.of("key", "flow", "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 DeviceService deviceService;
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
|
||||
protected OvsdbController controller;
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
|
||||
protected ClusterService clusterService;
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
|
||||
protected DriverService driverService;
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
|
||||
protected DeviceAdminService adminService;
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
|
||||
protected StorageService storageService;
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
|
||||
protected NetworkConfigService configService;
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
|
||||
protected NetworkConfigRegistry configRegistry;
|
||||
|
||||
private final OvsdbHandler ovsdbHandler = new OvsdbHandler();
|
||||
private final BridgeHandler bridgeHandler = new BridgeHandler();
|
||||
private final NetworkConfigListener configListener = new InternalConfigListener();
|
||||
private final ConfigFactory configFactory =
|
||||
new ConfigFactory(SubjectFactories.APP_SUBJECT_FACTORY, OpenstackNodeConfig.class, "openstacknode") {
|
||||
@Override
|
||||
public OpenstackNodeConfig createConfig() {
|
||||
return new OpenstackNodeConfig();
|
||||
}
|
||||
};
|
||||
|
||||
private final ExecutorService eventExecutor = Executors
|
||||
.newFixedThreadPool(NUM_THREADS, groupedThreads("onos/openstacknode", "event-handler"));
|
||||
|
||||
private final DeviceListener deviceListener = new InternalDeviceListener();
|
||||
|
||||
private ApplicationId appId;
|
||||
private ConsistentMap<OpenstackNode, NodeState> nodeStore;
|
||||
|
||||
private enum NodeState {
|
||||
|
||||
INIT {
|
||||
@Override
|
||||
public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) {
|
||||
openstackNodeManager.connect(node);
|
||||
}
|
||||
},
|
||||
OVSDB_CONNECTED {
|
||||
@Override
|
||||
public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) {
|
||||
if (!openstackNodeManager.getOvsdbConnectionState(node)) {
|
||||
openstackNodeManager.connect(node);
|
||||
} else {
|
||||
openstackNodeManager.createIntegrationBridge(node);
|
||||
}
|
||||
}
|
||||
},
|
||||
BRIDGE_CREATED {
|
||||
@Override
|
||||
public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) {
|
||||
if (!openstackNodeManager.getOvsdbConnectionState(node)) {
|
||||
openstackNodeManager.connect(node);
|
||||
} else {
|
||||
openstackNodeManager.createTunnelInterface(node);
|
||||
}
|
||||
}
|
||||
},
|
||||
COMPLETE {
|
||||
@Override
|
||||
public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) {
|
||||
openstackNodeManager.postInit(node);
|
||||
}
|
||||
},
|
||||
INCOMPLETE {
|
||||
@Override
|
||||
public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) {
|
||||
}
|
||||
};
|
||||
|
||||
public abstract void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node);
|
||||
}
|
||||
|
||||
@Activate
|
||||
protected void activate() {
|
||||
appId = coreService.registerApplication(OPENSTACK_NODEMANAGER_ID);
|
||||
nodeStore = storageService.<OpenstackNode, NodeState>consistentMapBuilder()
|
||||
.withSerializer(Serializer.using(NODE_SERIALIZER.build()))
|
||||
.withName(OPENSTACK_NODESTORE)
|
||||
.withApplicationId(appId)
|
||||
.build();
|
||||
|
||||
deviceService.addListener(deviceListener);
|
||||
configRegistry.registerConfigFactory(configFactory);
|
||||
configService.addListener(configListener);
|
||||
readConfiguration();
|
||||
|
||||
log.info("Started");
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
protected void deactivate() {
|
||||
deviceService.removeListener(deviceListener);
|
||||
eventExecutor.shutdown();
|
||||
nodeStore.clear();
|
||||
|
||||
configRegistry.unregisterConfigFactory(configFactory);
|
||||
configService.removeListener(configListener);
|
||||
|
||||
log.info("Stopped");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addNode(OpenstackNode node) {
|
||||
checkNotNull(node, "Node cannot be null");
|
||||
|
||||
nodeStore.putIfAbsent(node, checkNodeState(node));
|
||||
|
||||
NodeState state = checkNodeState(node);
|
||||
state.process(this, node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteNode(OpenstackNode node) {
|
||||
checkNotNull(node, "Node cannot be null");
|
||||
|
||||
if (getOvsdbConnectionState(node)) {
|
||||
disconnect(node);
|
||||
}
|
||||
|
||||
nodeStore.remove(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OpenstackNode> getNodes(OpenstackNodeType openstackNodeType) {
|
||||
List<OpenstackNode> nodes = new ArrayList<>();
|
||||
nodes.addAll(nodeStore.keySet().stream().filter(node -> node.openstackNodeType()
|
||||
.equals(openstackNodeType)).collect(Collectors.toList()));
|
||||
return nodes;
|
||||
}
|
||||
|
||||
private List<OpenstackNode> getNodesAll() {
|
||||
List<OpenstackNode> nodes = new ArrayList<>();
|
||||
nodes.addAll(nodeStore.keySet());
|
||||
return nodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isComplete(OpenstackNode node) {
|
||||
checkNotNull(node, "Node cannot be null");
|
||||
|
||||
if (!nodeStore.containsKey(node)) {
|
||||
log.warn("Node {} does not exist", node.hostName());
|
||||
return false;
|
||||
} else if (nodeStore.get(node).equals(NodeState.COMPLETE)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks current state of a given openstack node and returns it.
|
||||
*
|
||||
* @param node openstack node
|
||||
* @return node state
|
||||
*/
|
||||
private NodeState checkNodeState(OpenstackNode node) {
|
||||
checkNotNull(node, "Node cannot be null");
|
||||
|
||||
if (checkIntegrationBridge(node) && checkTunnelInterface(node)) {
|
||||
return NodeState.COMPLETE;
|
||||
} else if (checkIntegrationBridge(node)) {
|
||||
return NodeState.BRIDGE_CREATED;
|
||||
} else if (getOvsdbConnectionState(node)) {
|
||||
return NodeState.OVSDB_CONNECTED;
|
||||
} else {
|
||||
return NodeState.INIT;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if integration bridge exists and available.
|
||||
*
|
||||
* @param node openstack node
|
||||
* @return true if the bridge is available, false otherwise
|
||||
*/
|
||||
private boolean checkIntegrationBridge(OpenstackNode node) {
|
||||
return (deviceService.getDevice(node.intBrId()) != null
|
||||
&& deviceService.isAvailable(node.intBrId()));
|
||||
}
|
||||
/**
|
||||
* Checks if tunnel interface exists.
|
||||
*
|
||||
* @param node openstack node
|
||||
* @return true if the interface exists, false otherwise
|
||||
*/
|
||||
private boolean checkTunnelInterface(OpenstackNode node) {
|
||||
checkNotNull(node, "Node cannot be null");
|
||||
return deviceService.getPorts(node.intBrId())
|
||||
.stream()
|
||||
.filter(p -> p.annotations().value(PORT_NAME).contains(DEFAULT_TUNNEL) && p.isEnabled())
|
||||
.findAny().isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns connection state of OVSDB server for a given node.
|
||||
*
|
||||
* @param node openstack node
|
||||
* @return true if it is connected, false otherwise
|
||||
*/
|
||||
private boolean getOvsdbConnectionState(OpenstackNode node) {
|
||||
checkNotNull(node, "Node cannot be null");
|
||||
|
||||
OvsdbClientService ovsdbClient = getOvsdbClient(node);
|
||||
return deviceService.isAvailable(node.ovsdbId()) &&
|
||||
ovsdbClient != null && ovsdbClient.isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns OVSDB client for a given node.
|
||||
*
|
||||
* @param node openstack node
|
||||
* @return OVSDB client, or null if it fails to get OVSDB client
|
||||
*/
|
||||
private OvsdbClientService getOvsdbClient(OpenstackNode node) {
|
||||
checkNotNull(node, "Node cannot be null");
|
||||
|
||||
OvsdbClientService ovsdbClient = controller.getOvsdbClient(
|
||||
new OvsdbNodeId(node.ovsdbIp(), node.ovsdbPort().toInt()));
|
||||
if (ovsdbClient == null) {
|
||||
log.debug("Couldn't find OVSDB client for {}", node.hostName());
|
||||
}
|
||||
return ovsdbClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to OVSDB server for a given node.
|
||||
*
|
||||
* @param node openstack node
|
||||
*/
|
||||
private void connect(OpenstackNode node) {
|
||||
checkNotNull(node, "Node cannot be null");
|
||||
|
||||
if (!nodeStore.containsKey(node)) {
|
||||
log.warn("Node {} does not exist", node.hostName());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!getOvsdbConnectionState(node)) {
|
||||
controller.connect(node.ovsdbIp(), node.ovsdbPort());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an integration bridge for a given node.
|
||||
*
|
||||
* @param node openstack node
|
||||
*/
|
||||
private void createIntegrationBridge(OpenstackNode node) {
|
||||
if (checkIntegrationBridge(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 {
|
||||
DriverHandler handler = driverService.createHandler(node.ovsdbId());
|
||||
BridgeConfig bridgeConfig = handler.behaviour(BridgeConfig.class);
|
||||
bridgeConfig.addBridge(BridgeName.bridgeName(DEFAULT_BRIDGE), dpid, controllers);
|
||||
} catch (ItemNotFoundException e) {
|
||||
log.warn("Failed to create integration bridge on {}", node.ovsdbId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates tunnel interface to the integration bridge for a given node.
|
||||
*
|
||||
* @param node openstack node
|
||||
*/
|
||||
private void createTunnelInterface(OpenstackNode node) {
|
||||
if (checkTunnelInterface(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 {
|
||||
DriverHandler handler = driverService.createHandler(node.ovsdbId());
|
||||
TunnelConfig tunnelConfig = handler.behaviour(TunnelConfig.class);
|
||||
tunnelConfig.createTunnelInterface(BridgeName.bridgeName(DEFAULT_BRIDGE), description);
|
||||
} catch (ItemNotFoundException e) {
|
||||
log.warn("Failed to create tunnel interface on {}", node.ovsdbId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs tasks after node initialization.
|
||||
* First disconnect unnecessary OVSDB connection and then installs flow rules
|
||||
* for existing VMs if there are any.
|
||||
*
|
||||
* @param node openstack node
|
||||
*/
|
||||
private void postInit(OpenstackNode node) {
|
||||
disconnect(node);
|
||||
log.info("Finished initializing {}", node.hostName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new state for a given openstack node.
|
||||
*
|
||||
* @param node openstack node
|
||||
* @param newState new node state
|
||||
*/
|
||||
private void setNodeState(OpenstackNode node, NodeState newState) {
|
||||
checkNotNull(node, "Node cannot be null");
|
||||
|
||||
log.debug("Changed {} state: {}", node.hostName(), newState.toString());
|
||||
|
||||
nodeStore.put(node, newState);
|
||||
newState.process(this, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns openstack node associated with a given OVSDB device.
|
||||
*
|
||||
* @param ovsdbId OVSDB device id
|
||||
* @return openstack node, null if it fails to find the node
|
||||
*/
|
||||
private OpenstackNode getNodeByOvsdbId(DeviceId ovsdbId) {
|
||||
|
||||
return getNodesAll().stream()
|
||||
.filter(node -> node.ovsdbId().equals(ovsdbId))
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns openstack node associated with a given integration bridge.
|
||||
*
|
||||
* @param bridgeId device id of integration bridge
|
||||
* @return openstack node, null if it fails to find the node
|
||||
*/
|
||||
private OpenstackNode getNodeByBridgeId(DeviceId bridgeId) {
|
||||
return getNodesAll().stream()
|
||||
.filter(node -> node.intBrId().equals(bridgeId))
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
/**
|
||||
* Disconnects OVSDB server for a given node.
|
||||
*
|
||||
* @param node openstack node
|
||||
*/
|
||||
private void disconnect(OpenstackNode node) {
|
||||
checkNotNull(node, "Node cannot be null");
|
||||
|
||||
if (!nodeStore.containsKey(node)) {
|
||||
log.warn("Node {} does not exist", node.hostName());
|
||||
return;
|
||||
}
|
||||
|
||||
if (getOvsdbConnectionState(node)) {
|
||||
OvsdbClientService ovsdbClient = getOvsdbClient(node);
|
||||
ovsdbClient.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private class InternalDeviceListener implements DeviceListener {
|
||||
|
||||
@Override
|
||||
public void event(DeviceEvent event) {
|
||||
|
||||
Device device = event.subject();
|
||||
ConnectionHandler<Device> handler =
|
||||
(device.type().equals(SWITCH) ? bridgeHandler : ovsdbHandler);
|
||||
|
||||
switch (event.type()) {
|
||||
case PORT_ADDED:
|
||||
eventExecutor.submit(() -> bridgeHandler.portAdded(event.port()));
|
||||
break;
|
||||
case PORT_UPDATED:
|
||||
if (!event.port().isEnabled()) {
|
||||
eventExecutor.submit(() -> bridgeHandler.portRemoved(event.port()));
|
||||
}
|
||||
break;
|
||||
case DEVICE_ADDED:
|
||||
case DEVICE_AVAILABILITY_CHANGED:
|
||||
if (deviceService.isAvailable(device.id())) {
|
||||
eventExecutor.submit(() -> handler.connected(device));
|
||||
} else {
|
||||
eventExecutor.submit(() -> handler.disconnected(device));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
log.debug("Unsupported event type {}", event.type().toString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class OvsdbHandler implements ConnectionHandler<Device> {
|
||||
|
||||
@Override
|
||||
public void connected(Device device) {
|
||||
OpenstackNode node = getNodeByOvsdbId(device.id());
|
||||
if (node != null) {
|
||||
setNodeState(node, checkNodeState(node));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected(Device device) {
|
||||
if (!deviceService.isAvailable(device.id())) {
|
||||
adminService.removeDevice(device.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class BridgeHandler implements ConnectionHandler<Device> {
|
||||
|
||||
@Override
|
||||
public void connected(Device device) {
|
||||
OpenstackNode node = getNodeByBridgeId(device.id());
|
||||
if (node != null) {
|
||||
setNodeState(node, checkNodeState(node));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected(Device device) {
|
||||
OpenstackNode node = getNodeByBridgeId(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 port, proceed remaining node initialization.
|
||||
* Otherwise, do nothing.
|
||||
*
|
||||
* @param port port
|
||||
*/
|
||||
public void portAdded(Port port) {
|
||||
if (!port.annotations().value(PORT_NAME).contains(DEFAULT_TUNNEL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
OpenstackNode node = getNodeByBridgeId((DeviceId) port.element().id());
|
||||
if (node != null) {
|
||||
setNodeState(node, checkNodeState(node));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles port removed situation.
|
||||
* If the removed port is tunnel port, proceed remaining node initialization.
|
||||
* Others, do nothing.
|
||||
*
|
||||
* @param port port
|
||||
*/
|
||||
public void portRemoved(Port port) {
|
||||
if (!port.annotations().value(PORT_NAME).contains(DEFAULT_TUNNEL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
OpenstackNode node = getNodeByBridgeId((DeviceId) port.element().id());
|
||||
if (node != null) {
|
||||
log.info("Tunnel interface is removed from {}", node.hostName());
|
||||
setNodeState(node, NodeState.INCOMPLETE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void readConfiguration() {
|
||||
OpenstackNodeConfig config =
|
||||
configService.getConfig(appId, OpenstackNodeConfig.class);
|
||||
|
||||
if (config == null) {
|
||||
log.error("No configuration found");
|
||||
return;
|
||||
}
|
||||
|
||||
config.openstackNodes().stream().forEach(node -> addNode(node));
|
||||
log.info("Node configured");
|
||||
}
|
||||
|
||||
private class InternalConfigListener implements NetworkConfigListener {
|
||||
|
||||
@Override
|
||||
public void event(NetworkConfigEvent event) {
|
||||
if (!event.configClass().equals(OpenstackNodeConfig.class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.type()) {
|
||||
case CONFIG_ADDED:
|
||||
case CONFIG_UPDATED:
|
||||
eventExecutor.execute(OpenstackNodeManager.this::readConfiguration);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2016 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.openstacknode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Handles the bootstrap request for compute/gateway node.
|
||||
*/
|
||||
public interface OpenstackNodeService {
|
||||
|
||||
public enum OpenstackNodeType {
|
||||
/**
|
||||
* Compute or Gateway Node.
|
||||
*/
|
||||
COMPUTENODE,
|
||||
GATEWAYNODE
|
||||
}
|
||||
/**
|
||||
* Adds a new node to the service.
|
||||
*
|
||||
* @param node openstack node
|
||||
*/
|
||||
void addNode(OpenstackNode node);
|
||||
|
||||
/**
|
||||
* Deletes a node from the service.
|
||||
*
|
||||
* @param node openstack node
|
||||
*/
|
||||
void deleteNode(OpenstackNode node);
|
||||
|
||||
/**
|
||||
* Returns nodes known to the service for designated openstacktype.
|
||||
*
|
||||
* @param openstackNodeType openstack node type
|
||||
* @return list of nodes
|
||||
*/
|
||||
List<OpenstackNode> getNodes(OpenstackNodeType openstackNodeType);
|
||||
|
||||
/**
|
||||
* Returns the NodeState for a given node.
|
||||
*
|
||||
* @param node openstack node
|
||||
* @return true if the NodeState for a given node is COMPLETE, false otherwise
|
||||
*/
|
||||
boolean isComplete(OpenstackNode node);
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2016 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Application for bootstrapping Compute/Gateway Node in OpenStack.
|
||||
*/
|
||||
package org.onosproject.openstacknode;
|
||||
@ -68,6 +68,7 @@
|
||||
<module>openstackrouting</module>
|
||||
<module>cordmcast</module>
|
||||
<module>vpls</module>
|
||||
<module>openstacknode</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user