Hyunsun Moon
Committed by Jonathan Hart

Support GATEWAY type node bootstrapping

- Create router bridge and pactch port to integration bridge for gateway node
- Refactored to listen map event for node add/update
- Added CLIs

Change-Id: Id653f2a2c01d94036f77e6ce1b1230111f3dbbb1
COMPILE_DEPS = [
'//lib:CORE_DEPS',
'//lib:org.apache.karaf.shell.console',
'//cli:onos-cli',
'//protocols/ovsdb/api:onos-protocols-ovsdb-api',
'//protocols/ovsdb/rfc:onos-protocols-ovsdb-rfc',
'//core/store/serializers:onos-core-serializers',
......@@ -14,5 +16,5 @@ onos_app (
category = 'Utility',
url = 'http://onosproject.org',
description = 'SONA Openstack Node Bootstrap Application.',
required_app = [ 'org.onosproject.ovsdb' ],
required_app = [ 'org.onosproject.ovsdb-base' ],
)
......
......@@ -3,29 +3,36 @@
"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"
}
{
"hostname" : "compute-01",
"type" : "COMPUTE",
"managementIp" : "10.203.25.244",
"dataIp" : "10.134.34.222",
"integrationBridge" : "of:00000000000000a1"
},
{
"hostname" : "compute-02",
"type" : "COMPUTE",
"managementIp" : "10.203.229.42",
"dataIp" : "10.134.34.223",
"integrationBridge" : "of:00000000000000a2"
},
{
"hostname" : "gateway-01",
"type" : "GATEWAY",
"managementIp" : "10.203.198.125",
"dataIp" : "10.134.33.208",
"integrationBridge" : "of:00000000000000a3",
"routerBridge" : "of:00000000000000b3"
},
{
"hostname" : "gateway-02",
"type" : "GATEWAY",
"managementIp" : "10.203.198.131",
"dataIp" : "10.134.33.209",
"integrationBridge" : "of:00000000000000a4",
"routerBridge" : "of:00000000000000b4"
}
]
}
}
......
......@@ -38,7 +38,7 @@
<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
org.onosproject.ovsdb-base
</onos.app.requires>
</properties>
......@@ -58,6 +58,11 @@
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-cli</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-ovsdb-api</artifactId>
<version>${project.version}</version>
</dependency>
......@@ -69,5 +74,10 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.apache.karaf.shell</groupId>
<artifactId>org.apache.karaf.shell.console</artifactId>
<version>3.0.5</version>
</dependency>
</dependencies>
</project>
......
/*
* 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.openstacknode;
/**
* Provides constants used in OpenStack node services.
*/
public final class Constants {
private Constants() {
}
public static final String INTEGRATION_BRIDGE = "br-int";
public static final String ROUTER_BRIDGE = "br-router";
public static final String DEFAULT_TUNNEL = "vxlan";
public static final String PATCH_INTG_BRIDGE = "patch-intg";
public static final String PATCH_ROUT_BRIDGE = "patch-rout";
}
\ No newline at end of file
......@@ -16,14 +16,16 @@
package org.onosproject.openstacknode;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onlab.packet.TpPort;
import org.onosproject.net.DeviceId;
import org.onosproject.openstacknode.OpenstackNodeService.NodeType;
import java.util.Comparator;
import java.util.Objects;
import java.util.Optional;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
/**
......@@ -31,118 +33,121 @@ import static com.google.common.base.Preconditions.checkNotNull;
*/
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:";
private final String hostname;
private final NodeType type;
private final IpAddress managementIp;
private final IpAddress dataIp;
private final DeviceId integrationBridge;
private final Optional<DeviceId> routerBridge;
private final OpenstackNodeState state;
public static final Comparator<OpenstackNode> OPENSTACK_NODE_COMPARATOR =
(node1, node2) -> node1.hostName().compareTo(node2.hostName());
(node1, node2) -> node1.hostname().compareTo(node2.hostname());
private OpenstackNode(String hostname,
NodeType type,
IpAddress managementIp,
IpAddress dataIp,
DeviceId integrationBridge,
Optional<DeviceId> routerBridge,
OpenstackNodeState state) {
this.hostname = hostname;
this.type = type;
this.managementIp = managementIp;
this.dataIp = dataIp;
this.integrationBridge = integrationBridge;
this.routerBridge = routerBridge;
this.state = state;
}
/**
* Creates a new node.
* Returns OpenStack node with new state.
*
* @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
* @param node openstack node
* @param state openstack node init state
* @return openstack node
*/
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");
}
public static OpenstackNode getUpdatedNode(OpenstackNode node, OpenstackNodeState state) {
return new OpenstackNode(node.hostname,
node.type,
node.managementIp,
node.dataIp,
node.integrationBridge,
node.routerBridge,
state);
}
/**
* Returns the OVSDB server IP address.
* Returns hostname of the node.
*
* @return ip address
* @return hostname
*/
public IpAddress ovsdbIp() {
return this.ovsdbIp;
public String hostname() {
return hostname;
}
/**
* Returns the OVSDB server port number.
* Returns the type of the node.
*
* @return port number
* @return node type
*/
public TpPort ovsdbPort() {
return this.ovsdbPort;
public NodeType type() {
return type;
}
/**
* Returns the hostName.
* Returns the management network IP address of the node.
*
* @return hostName
* @return management network ip address
*/
public String hostName() {
return this.hostName;
public IpAddress managementIp() {
return managementIp;
}
/**
* Returns the identifier of the integration bridge.
* Returns the data network IP address of the node.
*
* @return device id
* @return data network ip address
*/
public DeviceId intBrId() {
return this.bridgeId;
public IpAddress dataIp() {
return dataIp;
}
/**
* Returns the identifier of the OVSDB device.
* Returns the integration bridge device ID.
*
* @return device id
*/
public DeviceId ovsdbId() {
return DeviceId.deviceId(OVSDB.concat(this.ovsdbIp.toString()));
public DeviceId intBridge() {
return integrationBridge;
}
/**
* Returns the openstack node type.
* Returns the router bridge device ID.
* It returns valid value only if the node type is GATEWAY.
*
* @return openstack node type
* @return device id; or empty device id
*/
public OpenstackNodeService.OpenstackNodeType openstackNodeType() {
return this.openstackNodeType;
public Optional<DeviceId> routerBridge() {
return routerBridge;
}
/**
* Returns the gatewayExternalInterfaceName.
* Returns the init state of the node.
*
* @return gatewayExternalInterfaceName
* @return init state
*/
public String gatewayExternalInterfaceName() {
return this.gatewayExternalInterfaceName;
public OpenstackNodeState state() {
return state;
}
/**
* Returns the gatewayExternalInterfaceMac.
* Returns the device ID of the OVSDB session of the node.
*
* @return gatewayExternalInterfaceMac
* @return device id
*/
public MacAddress gatewayExternalInterfaceMac() {
return this.gatewayExternalInterfaceMac;
public DeviceId ovsdbId() {
return DeviceId.deviceId("ovsdb:" + managementIp.toString());
}
@Override
......@@ -153,12 +158,12 @@ public final class OpenstackNode {
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)) {
if (Objects.equals(hostname, that.hostname) &&
Objects.equals(type, that.type) &&
Objects.equals(managementIp, that.managementIp) &&
Objects.equals(dataIp, that.dataIp) &&
Objects.equals(integrationBridge, that.integrationBridge) &&
Objects.equals(routerBridge, that.routerBridge)) {
return true;
}
}
......@@ -167,29 +172,142 @@ public final class OpenstackNode {
@Override
public int hashCode() {
return Objects.hash(hostName, ovsdbIp, ovsdbPort, bridgeId, openstackNodeType);
return Objects.hash(hostname,
type,
managementIp,
dataIp,
integrationBridge,
routerBridge);
}
@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();
return MoreObjects.toStringHelper(getClass())
.add("hostname", hostname)
.add("type", type)
.add("managementIp", managementIp)
.add("dataIp", dataIp)
.add("integrationBridge", integrationBridge)
.add("routerBridge", routerBridge)
.add("state", state)
.toString();
}
/**
* Returns a new builder instance.
*
* @return openstack node builder
*/
public static Builder builder() {
return new Builder();
}
/**
* Builder of OpenStack node entities.
*/
public static final class Builder {
private String hostname;
private NodeType type;
private IpAddress managementIp;
private IpAddress dataIp;
private DeviceId integrationBridge;
private Optional<DeviceId> routerBridge = Optional.empty();
private OpenstackNodeState state = OpenstackNodeState.noState();
private Builder() {
}
public OpenstackNode build() {
checkArgument(!Strings.isNullOrEmpty(hostname));
checkNotNull(type);
checkNotNull(managementIp);
checkNotNull(dataIp);
checkNotNull(integrationBridge);
checkNotNull(routerBridge);
return new OpenstackNode(hostname,
type,
managementIp,
dataIp,
integrationBridge,
routerBridge,
state);
}
/**
* Returns node builder with the hostname.
*
* @param hostname hostname
* @return openstack node builder
*/
public Builder hostname(String hostname) {
this.hostname = hostname;
return this;
}
/**
* Returns node builder with the node type.
*
* @param type openstack node type
* @return openstack node builder
*/
public Builder type(NodeType type) {
this.type = type;
return this;
}
/**
* Returns node builder with the management network IP address.
*
* @param managementIp management ip address
* @return openstack node builder
*/
public Builder managementIp(IpAddress managementIp) {
this.managementIp = managementIp;
return this;
}
/**
* Returns node builder with the data network IP address.
*
* @param dataIp data network ip address
* @return openstack node builder
*/
public Builder dataIp(IpAddress dataIp) {
this.dataIp = dataIp;
return this;
}
/**
* Returns node builder with the integration bridge ID.
*
* @param integrationBridge integration bridge device id
* @return openstack node builder
*/
public Builder integrationBridge(DeviceId integrationBridge) {
this.integrationBridge = integrationBridge;
return this;
}
/**
* Returns node builder with the router bridge ID.
*
* @param routerBridge router bridge device ID
* @return openstack node builder
*/
public Builder routerBridge(DeviceId routerBridge) {
this.routerBridge = Optional.ofNullable(routerBridge);
return this;
}
/**
* Returns node builder with the init state.
*
* @param state node init state
* @return openstack node builder
*/
public Builder state(OpenstackNodeState state) {
this.state = state;
return this;
}
}
}
......
......@@ -17,73 +17,94 @@ package org.onosproject.openstacknode;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Sets;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.MacAddress;
import org.onlab.packet.TpPort;
import org.onlab.packet.IpAddress;
import org.onosproject.core.ApplicationId;
import org.onosproject.net.DeviceId;
import org.slf4j.Logger;
import org.onosproject.openstacknode.OpenstackNodeService.NodeType;
import java.util.Set;
import org.onosproject.net.config.Config;
import static org.slf4j.LoggerFactory.getLogger;
import static org.onosproject.net.config.Config.FieldPresence.MANDATORY;
import static org.onosproject.openstacknode.OpenstackNodeService.NodeType.GATEWAY;
/**
* Configuration object for OpensatckNode service.
*/
public class OpenstackNodeConfig extends Config<ApplicationId> {
public final class OpenstackNodeConfig extends Config<ApplicationId> {
private static final String NODES = "nodes";
private static final String HOST_NAME = "hostname";
private static final String TYPE = "type";
private static final String MANAGEMENT_IP = "managementIp";
private static final String DATA_IP = "dataIp";
private static final String INTEGRATION_BRIDGE = "integrationBridge";
private static final String ROUTER_BRIDGE = "routerBridge";
protected final Logger log = getLogger(getClass());
@Override
public boolean isValid() {
boolean result = hasOnlyFields(NODES);
if (object.get(NODES) == null || object.get(NODES).size() < 1) {
final String msg = "No node is present";
throw new IllegalArgumentException(msg);
}
for (JsonNode node : object.get(NODES)) {
ObjectNode osNode = (ObjectNode) node;
result &= hasOnlyFields(osNode,
HOST_NAME,
TYPE,
MANAGEMENT_IP,
DATA_IP,
INTEGRATION_BRIDGE,
ROUTER_BRIDGE
);
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";
result &= isString(osNode, HOST_NAME, MANDATORY);
result &= isString(osNode, TYPE, MANDATORY);
result &= isIpAddress(osNode, MANAGEMENT_IP, MANDATORY);
result &= result && isIpAddress(osNode, DATA_IP, MANDATORY);
result &= isString(osNode, INTEGRATION_BRIDGE, MANDATORY);
DeviceId.deviceId(osNode.get(INTEGRATION_BRIDGE).asText());
NodeType.valueOf(osNode.get(TYPE).asText());
if (osNode.get(TYPE).asText().equals(GATEWAY.name())) {
result &= isString(osNode, ROUTER_BRIDGE, MANDATORY);
DeviceId.deviceId(osNode.get(ROUTER_BRIDGE).asText());
}
}
return result;
}
/**
* Returns the set of nodes read from network config.
*
* @return set of OpensatckNodeConfig or null
* @return set of openstack nodes
*/
public Set<OpenstackNode> openstackNodes() {
Set<OpenstackNode> nodes = Sets.newHashSet();
JsonNode jsonNodes = object.get(NODES);
if (jsonNodes == null) {
return null;
}
for (JsonNode node : object.get(NODES)) {
NodeType type = NodeType.valueOf(get(node, TYPE));
OpenstackNode.Builder nodeBuilder = OpenstackNode.builder()
.integrationBridge(DeviceId.deviceId(get(node, INTEGRATION_BRIDGE)))
.dataIp(IpAddress.valueOf(get(node, DATA_IP)))
.managementIp(IpAddress.valueOf(get(node, MANAGEMENT_IP)))
.type(type)
.hostname(get(node, HOST_NAME));
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());
if (type.equals(GATEWAY)) {
nodeBuilder.routerBridge(DeviceId.deviceId(get(node, ROUTER_BRIDGE)));
}
});
nodes.add(nodeBuilder.build());
}
return nodes;
}
private String get(JsonNode jsonNode, String path) {
return jsonNode.get(path).asText();
}
}
......
......@@ -15,14 +15,20 @@
*/
package org.onosproject.openstacknode;
import com.google.common.collect.Sets;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Modified;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onlab.util.ItemNotFoundException;
import org.onlab.packet.IpAddress;
import org.onlab.packet.TpPort;
import org.onlab.util.KryoNamespace;
import org.onlab.util.Tools;
import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.cluster.ClusterService;
import org.onosproject.cluster.LeadershipService;
import org.onosproject.cluster.NodeId;
......@@ -31,12 +37,15 @@ import org.onosproject.core.CoreService;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Port;
import org.onosproject.net.PortNumber;
import org.onosproject.net.behaviour.BridgeConfig;
import org.onosproject.net.behaviour.BridgeDescription;
import org.onosproject.net.behaviour.ControllerInfo;
import org.onosproject.net.behaviour.DefaultBridgeDescription;
import org.onosproject.net.behaviour.DefaultPatchDescription;
import org.onosproject.net.behaviour.DefaultTunnelDescription;
import org.onosproject.net.behaviour.InterfaceConfig;
import org.onosproject.net.behaviour.PatchDescription;
import org.onosproject.net.behaviour.TunnelDescription;
import org.onosproject.net.behaviour.TunnelEndPoints;
import org.onosproject.net.behaviour.TunnelKeys;
......@@ -44,57 +53,61 @@ 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.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.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import static org.onlab.util.Tools.groupedThreads;
import static org.onosproject.net.AnnotationKeys.PORT_NAME;
import static org.onosproject.net.Device.Type.SWITCH;
import static org.onosproject.net.behaviour.TunnelDescription.Type.VXLAN;
import static org.onosproject.openstacknode.Constants.*;
import static org.slf4j.LoggerFactory.getLogger;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Initializes devices in compute/gateway nodes according to there type.
*/
@Component(immediate = true)
@Service
public class OpenstackNodeManager implements OpenstackNodeService {
public final class OpenstackNodeManager implements OpenstackNodeService {
protected final Logger log = getLogger(getClass());
private static final KryoNamespace.Builder NODE_SERIALIZER = KryoNamespace.newBuilder()
.register(KryoNamespaces.API)
.register(OpenstackNode.class)
.register(OpenstackNodeType.class)
.register(NodeType.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 String OVSDB_PORT = "ovsdbPort";
private static final int DEFAULT_OVSDB_PORT = 6640;
private static final int DEFAULT_OFPORT = 6653;
private static final int DPID_BEGIN = 3;
private static final int OFPORT = 6653;
private static final String APP_ID = "org.onosproject.openstacknode";
private static final Class<OpenstackNodeConfig> CONFIG_CLASS = OpenstackNodeConfig.class;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
......@@ -109,16 +122,10 @@ public class OpenstackNodeManager implements OpenstackNodeService {
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;
protected ComponentConfigService componentConfigService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigRegistry configRegistry;
......@@ -126,436 +133,375 @@ public class OpenstackNodeManager implements OpenstackNodeService {
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected LeadershipService leadershipService;
private final OvsdbHandler ovsdbHandler = new OvsdbHandler();
private final BridgeHandler bridgeHandler = new BridgeHandler();
private final NetworkConfigListener configListener = new InternalConfigListener();
@Property(name = OVSDB_PORT, intValue = DEFAULT_OVSDB_PORT,
label = "OVSDB server listen port")
private int ovsdbPort = DEFAULT_OVSDB_PORT;
private final ExecutorService eventExecutor =
newSingleThreadScheduledExecutor(groupedThreads("onos/openstack-node", "event-handler"));
private final ConfigFactory configFactory =
new ConfigFactory(SubjectFactories.APP_SUBJECT_FACTORY, OpenstackNodeConfig.class, "openstacknode") {
new ConfigFactory<ApplicationId, OpenstackNodeConfig>(
SubjectFactories.APP_SUBJECT_FACTORY, CONFIG_CLASS, "openstacknode") {
@Override
public OpenstackNodeConfig createConfig() {
return new OpenstackNodeConfig();
}
};
private final ExecutorService eventExecutor =
newSingleThreadScheduledExecutor(groupedThreads("onos/openstacknode", "event-handler", log));
private final NetworkConfigListener configListener = new InternalConfigListener();
private final DeviceListener deviceListener = new InternalDeviceListener();
private final MapEventListener<String, OpenstackNode> nodeStoreListener = new InternalMapListener();
private final OvsdbHandler ovsdbHandler = new OvsdbHandler();
private final BridgeHandler bridgeHandler = new BridgeHandler();
private ConsistentMap<String, OpenstackNode> nodeStore;
private ApplicationId appId;
private ConsistentMap<OpenstackNode, NodeState> nodeStore;
private NodeId localNodeId;
private enum NodeState {
private enum NodeState implements OpenstackNodeState {
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);
public void process(OpenstackNodeManager nodeManager, OpenstackNode node) {
// make sure there is OVSDB connection
if (!nodeManager.isOvsdbConnected(node)) {
nodeManager.connectOvsdb(node);
return;
}
nodeManager.createBridge(node,
INTEGRATION_BRIDGE,
node.intBridge().toString().substring(DPID_BEGIN));
// creates additional router bridge if the node type is GATEWAY
if (node.type().equals(NodeType.GATEWAY)) {
nodeManager.createBridge(node,
ROUTER_BRIDGE,
node.routerBridge().get().toString().substring(DPID_BEGIN));
}
}
},
BRIDGE_CREATED {
@Override
public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) {
if (!openstackNodeManager.getOvsdbConnectionState(node)) {
openstackNodeManager.connect(node);
} else {
openstackNodeManager.createTunnelInterface(node);
public void process(OpenstackNodeManager nodeManager, OpenstackNode node) {
// make sure there is OVSDB connection
if (!nodeManager.isOvsdbConnected(node)) {
nodeManager.connectOvsdb(node);
return;
}
nodeManager.createTunnelInterface(node);
// creates additional patch ports connecting integration bridge and
// router bridge if the node type is GATEWAY
if (node.type().equals(NodeType.GATEWAY)) {
nodeManager.createPatchInterface(node);
}
}
},
COMPLETE {
@Override
public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) {
openstackNodeManager.postInit(node);
public void process(OpenstackNodeManager nodeManager, OpenstackNode node) {
nodeManager.postInit(node);
}
},
INCOMPLETE {
@Override
public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) {
public void process(OpenstackNodeManager nodeManager, OpenstackNode node) {
}
};
public abstract void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node);
public abstract void process(OpenstackNodeManager nodeManager, OpenstackNode node);
}
@Activate
protected void activate() {
appId = coreService.registerApplication(OPENSTACK_NODEMANAGER_ID);
appId = coreService.getAppId(APP_ID);
localNodeId = clusterService.getLocalNode().id();
leadershipService.runForLeadership(appId.name());
nodeStore = storageService.<OpenstackNode, NodeState>consistentMapBuilder()
nodeStore = storageService.<String, OpenstackNode>consistentMapBuilder()
.withSerializer(Serializer.using(NODE_SERIALIZER.build()))
.withName(OPENSTACK_NODESTORE)
.withName("openstack-nodestore")
.withApplicationId(appId)
.build();
nodeStore.addListener(nodeStoreListener);
deviceService.addListener(deviceListener);
configRegistry.registerConfigFactory(configFactory);
configService.addListener(configListener);
readConfiguration();
configRegistry.addListener(configListener);
componentConfigService.registerProperties(getClass());
log.info("Started");
}
@Deactivate
protected void deactivate() {
configRegistry.removeListener(configListener);
deviceService.removeListener(deviceListener);
eventExecutor.shutdown();
nodeStore.clear();
nodeStore.removeListener(nodeStoreListener);
componentConfigService.unregisterProperties(getClass(), true);
configRegistry.unregisterConfigFactory(configFactory);
configService.removeListener(configListener);
leadershipService.withdraw(appId.name());
eventExecutor.shutdown();
log.info("Stopped");
}
@Modified
protected void modified(ComponentContext context) {
Dictionary<?, ?> properties = context.getProperties();
int updatedOvsdbPort = Tools.getIntegerProperty(properties, OVSDB_PORT);
if (!Objects.equals(updatedOvsdbPort, ovsdbPort)) {
ovsdbPort = updatedOvsdbPort;
}
@Override
public void addNode(OpenstackNode node) {
checkNotNull(node, "Node cannot be null");
NodeId leaderNodeId = leadershipService.getLeader(appId.name());
log.debug("Node init requested, localNodeId: {}, leaderNodeId: {}", localNodeId, leaderNodeId);
log.info("Modified");
}
//TODO: Fix any node can engage this operation.
if (!localNodeId.equals(leaderNodeId)) {
log.debug("Only the leaderNode can perform addNode operation");
return;
}
nodeStore.putIfAbsent(node, checkNodeState(node));
NodeState state = checkNodeState(node);
state.process(this, node);
@Override
public void addOrUpdateNode(OpenstackNode node) {
nodeStore.put(node.hostname(),
OpenstackNode.getUpdatedNode(node, nodeState(node)));
}
@Override
public void deleteNode(OpenstackNode node) {
checkNotNull(node, "Node cannot be null");
if (getOvsdbConnectionState(node)) {
disconnect(node);
if (isOvsdbConnected(node)) {
OvsdbNodeId ovsdb = new OvsdbNodeId(node.managementIp(), ovsdbPort);
controller.getOvsdbClient(ovsdb).disconnect();
}
nodeStore.remove(node);
nodeStore.remove(node.hostname());
}
@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;
public List<OpenstackNode> nodes() {
return nodeStore.values().stream().map(Versioned::value).collect(Collectors.toList());
}
private List<OpenstackNode> getNodesAll() {
List<OpenstackNode> nodes = new ArrayList<>();
nodes.addAll(nodeStore.keySet());
return nodes;
@Override
public Set<OpenstackNode> completeNodes() {
return nodeStore.values().stream().map(Versioned::value)
.filter(node -> node.state().equals(NodeState.COMPLETE))
.collect(Collectors.toSet());
}
@Override
public boolean isComplete(OpenstackNode node) {
checkNotNull(node, "Node cannot be null");
if (!nodeStore.containsKey(node)) {
log.warn("Node {} does not exist", node.hostName());
public boolean isComplete(String hostname) {
Versioned<OpenstackNode> versionedNode = nodeStore.get(hostname);
if (versionedNode == null) {
log.warn("Node {} does not exist", hostname);
return false;
} else if (nodeStore.get(node).equals(NodeState.COMPLETE)) {
return true;
}
return false;
OpenstackNodeState state = versionedNode.value().state();
return state != null && state.equals(NodeState.COMPLETE);
}
/**
* 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;
@Override
public Optional<IpAddress> dataIp(DeviceId deviceId) {
OpenstackNode node = nodeByDeviceId(deviceId);
if (node == null) {
log.warn("Failed to get node for {}", deviceId);
return Optional.empty();
}
return Optional.of(node.dataIp());
}
/**
* 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()));
@Override
public Optional<PortNumber> tunnelPort(DeviceId deviceId) {
return deviceService.getPorts(deviceId).stream()
.filter(p -> p.annotations().value(PORT_NAME).equals(DEFAULT_TUNNEL) &&
p.isEnabled())
.map(Port::number).findFirst();
}
/**
* 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();
@Override
public Optional<DeviceId> routerBridge(DeviceId intBridgeId) {
OpenstackNode node = nodeByDeviceId(intBridgeId);
if (node == null || node.type().equals(NodeType.COMPUTE)) {
log.warn("Failed to find router bridge connected to {}", intBridgeId);
return Optional.empty();
}
return node.routerBridge();
}
/**
* 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");
@Override
public Optional<PortNumber> externalPort(DeviceId intBridgeId) {
return deviceService.getPorts(intBridgeId).stream()
.filter(p -> p.annotations().value(PORT_NAME).equals(PATCH_INTG_BRIDGE) &&
p.isEnabled())
.map(Port::number).findFirst();
}
OvsdbClientService ovsdbClient = getOvsdbClient(node);
return deviceService.isAvailable(node.ovsdbId()) &&
ovsdbClient != null && ovsdbClient.isConnected();
private void initNode(OpenstackNode node) {
NodeState state = (NodeState) node.state();
state.process(this, node);
log.debug("Processing node: {} state: {}", node.hostname(), state);
}
/**
* 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());
private void postInit(OpenstackNode node) {
if (isOvsdbConnected(node)) {
OvsdbNodeId ovsdb = new OvsdbNodeId(node.managementIp(), ovsdbPort);
controller.getOvsdbClient(ovsdb).disconnect();
}
return ovsdbClient;
// TODO add gateway node to scalable gateway pool
log.info("Finished init {}", node.hostname());
}
/**
* Connects to OVSDB server for a given node.
*
* @param node openstack node
*/
private void connect(OpenstackNode node) {
checkNotNull(node, "Node cannot be null");
private void setNodeState(OpenstackNode node, NodeState newState) {
log.debug("Changed {} state: {}", node.hostname(), newState);
nodeStore.put(node.hostname(), OpenstackNode.getUpdatedNode(node, newState));
}
if (!nodeStore.containsKey(node)) {
log.warn("Node {} does not exist", node.hostName());
return;
private NodeState nodeState(OpenstackNode node) {
if (!deviceService.isAvailable(node.intBridge())) {
return NodeState.INIT;
}
if (node.type().equals(NodeType.GATEWAY) &&
!deviceService.isAvailable(node.routerBridge().get())) {
return NodeState.INIT;
}
if (!getOvsdbConnectionState(node)) {
controller.connect(node.ovsdbIp(), node.ovsdbPort());
if (!isIfaceCreated(node.intBridge(), DEFAULT_TUNNEL)) {
return NodeState.BRIDGE_CREATED;
}
if (node.type().equals(NodeType.GATEWAY) && (
!isIfaceCreated(node.routerBridge().get(), PATCH_ROUT_BRIDGE) ||
!isIfaceCreated(node.intBridge(), PATCH_INTG_BRIDGE))) {
return NodeState.BRIDGE_CREATED;
}
return NodeState.COMPLETE;
}
/**
* Creates an integration bridge for a given node.
*
* @param node openstack node
*/
private void createIntegrationBridge(OpenstackNode node) {
if (checkIntegrationBridge(node)) {
private boolean isIfaceCreated(DeviceId deviceId, String ifaceName) {
return deviceService.getPorts(deviceId).stream()
.filter(p -> p.annotations().value(PORT_NAME).contains(ifaceName) &&
p.isEnabled())
.findAny()
.isPresent();
}
private void createBridge(OpenstackNode node, String bridgeName, String dpid) {
Device device = deviceService.getDevice(node.ovsdbId());
if (device == null || !device.is(BridgeConfig.class)) {
log.error("Failed to create integration bridge on {}", node.ovsdbId());
return;
}
List<ControllerInfo> controllers = clusterService.getNodes().stream()
.map(controller -> new ControllerInfo(controller.ip(), OFPORT, "tcp"))
.map(controller -> new ControllerInfo(controller.ip(), DEFAULT_OFPORT, "tcp"))
.collect(Collectors.toList());
String dpid = node.intBrId().toString().substring(DPID_BEGIN);
BridgeDescription bridgeDesc = DefaultBridgeDescription.builder()
.name(DEFAULT_BRIDGE)
.name(bridgeName)
.failMode(BridgeDescription.FailMode.SECURE)
.datapathId(dpid)
.disableInBand()
.controllers(controllers)
.build();
try {
DriverHandler handler = driverService.createHandler(node.ovsdbId());
BridgeConfig bridgeConfig = handler.behaviour(BridgeConfig.class);
bridgeConfig.addBridge(bridgeDesc);
} catch (ItemNotFoundException e) {
log.warn("Failed to create integration bridge on {}", node.ovsdbId());
}
BridgeConfig bridgeConfig = device.as(BridgeConfig.class);
bridgeConfig.addBridge(bridgeDesc);
}
/**
* Creates tunnel interface to the integration bridge for a given node.
*
* @param node openstack node
*/
private void createTunnelInterface(OpenstackNode node) {
if (checkTunnelInterface(node)) {
Device device = deviceService.getDevice(node.ovsdbId());
if (device == null || !device.is(InterfaceConfig.class)) {
log.error("Failed to create tunnel interface on {}", node.ovsdbId());
return;
}
TunnelDescription description = DefaultTunnelDescription.builder()
.deviceId(DEFAULT_BRIDGE)
TunnelDescription tunnelDesc = DefaultTunnelDescription.builder()
.deviceId(INTEGRATION_BRIDGE)
.ifaceName(DEFAULT_TUNNEL)
.type(VXLAN)
.remote(TunnelEndPoints.flowTunnelEndpoint())
.key(TunnelKeys.flowTunnelKey())
.build();
try {
DriverHandler handler = driverService.createHandler(node.ovsdbId());
InterfaceConfig ifaceConfig = handler.behaviour(InterfaceConfig.class);
ifaceConfig.addTunnelMode(DEFAULT_TUNNEL, 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());
InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
ifaceConfig.addTunnelMode(DEFAULT_TUNNEL, tunnelDesc);
}
/**
* 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");
private void createPatchInterface(OpenstackNode node) {
Device device = deviceService.getDevice(node.ovsdbId());
if (device == null || !device.is(InterfaceConfig.class)) {
log.error("Failed to create patch interfaces on {}", node.hostname());
return;
}
log.debug("Changed {} state: {}", node.hostName(), newState.toString());
PatchDescription patchIntg = DefaultPatchDescription.builder()
.deviceId(INTEGRATION_BRIDGE)
.ifaceName(PATCH_INTG_BRIDGE)
.peer(PATCH_ROUT_BRIDGE)
.build();
nodeStore.put(node, newState);
newState.process(this, node);
PatchDescription patchRout = DefaultPatchDescription.builder()
.deviceId(ROUTER_BRIDGE)
.ifaceName(PATCH_ROUT_BRIDGE)
.peer(PATCH_INTG_BRIDGE)
.build();
InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
ifaceConfig.addPatchMode(PATCH_INTG_BRIDGE, patchIntg);
ifaceConfig.addPatchMode(PATCH_ROUT_BRIDGE, patchRout);
}
/**
* 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);
private boolean isOvsdbConnected(OpenstackNode node) {
OvsdbNodeId ovsdb = new OvsdbNodeId(node.managementIp(), ovsdbPort);
OvsdbClientService client = controller.getOvsdbClient(ovsdb);
return deviceService.isAvailable(node.ovsdbId()) &&
client != null &&
client.isConnected();
}
/**
* 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);
private void connectOvsdb(OpenstackNode node) {
controller.connect(node.managementIp(), TpPort.tpPort(ovsdbPort));
}
/**
* 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 Set<String> systemIfaces(OpenstackNode node) {
Set<String> ifaces = Sets.newHashSet(DEFAULT_TUNNEL);
if (node.type().equals(NodeType.GATEWAY)) {
ifaces.add(PATCH_INTG_BRIDGE);
ifaces.add(PATCH_ROUT_BRIDGE);
}
return ifaces;
}
private class InternalDeviceListener implements DeviceListener {
@Override
public void event(DeviceEvent event) {
NodeId leaderNodeId = leadershipService.getLeader(appId.name());
//TODO: Fix any node can engage this operation.
if (!localNodeId.equals(leaderNodeId)) {
log.debug("Only the leaderNode can process events");
return;
}
private OpenstackNode nodeByDeviceId(DeviceId deviceId) {
OpenstackNode node = nodes().stream()
.filter(n -> n.intBridge().equals(deviceId))
.findFirst().orElseGet(() -> nodes().stream()
.filter(n -> n.routerBridge().isPresent())
.filter(n -> n.routerBridge().get().equals(deviceId))
.findFirst().orElse(null));
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;
}
}
return node;
}
private class OvsdbHandler implements ConnectionHandler<Device> {
@Override
public void connected(Device device) {
OpenstackNode node = getNodeByOvsdbId(device.id());
OpenstackNode node = nodes().stream()
.filter(n -> n.ovsdbId().equals(device.id()))
.findFirst()
.orElse(null);
if (node != null) {
setNodeState(node, checkNodeState(node));
setNodeState(node, nodeState(node));
} else {
log.debug("{} is detected on unregistered node, ignore it.", device.id());
}
}
@Override
public void disconnected(Device device) {
if (!deviceService.isAvailable(device.id())) {
adminService.removeDevice(device.id());
}
log.debug("Device {} is disconnected", device.id());
}
}
......@@ -563,78 +509,124 @@ public class OpenstackNodeManager implements OpenstackNodeService {
@Override
public void connected(Device device) {
OpenstackNode node = getNodeByBridgeId(device.id());
OpenstackNode node = nodeByDeviceId(device.id());
if (node != null) {
setNodeState(node, checkNodeState(node));
setNodeState(node, nodeState(node));
} else {
log.debug("{} is detected on unregistered node, ignore it.", device.id());
}
}
@Override
public void disconnected(Device device) {
OpenstackNode node = getNodeByBridgeId(device.id());
OpenstackNode node = nodeByDeviceId(device.id());
if (node != null) {
log.debug("Integration Bridge is disconnected from {}", node.hostName());
log.warn("Device {} is disconnected", device.id());
setNodeState(node, NodeState.INCOMPLETE);
}
}
/**
* Handles port added situation.
* If the added port is tunnel port, proceed remaining node initialization.
* Otherwise, do nothing.
* 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) {
if (!port.annotations().value(PORT_NAME).contains(DEFAULT_TUNNEL)) {
OpenstackNode node = nodeByDeviceId((DeviceId) port.element().id());
String portName = port.annotations().value(PORT_NAME);
if (node == null) {
log.debug("{} is added to unregistered node, ignore it.", portName);
return;
}
OpenstackNode node = getNodeByBridgeId((DeviceId) port.element().id());
if (node != null) {
setNodeState(node, checkNodeState(node));
log.info("Port {} is added to {}", portName, node.hostname());
if (systemIfaces(node).contains(portName)) {
setNodeState(node, nodeState(node));
}
}
/**
* Handles port removed situation.
* If the removed port is tunnel port, proceed remaining node initialization.
* Others, do nothing.
* 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) {
if (!port.annotations().value(PORT_NAME).contains(DEFAULT_TUNNEL)) {
OpenstackNode node = nodeByDeviceId((DeviceId) port.element().id());
String portName = port.annotations().value(PORT_NAME);
if (node == null) {
return;
}
OpenstackNode node = getNodeByBridgeId((DeviceId) port.element().id());
if (node != null) {
log.info("Tunnel interface is removed from {}", node.hostName());
log.info("Port {} is removed from {}", portName, node.hostname());
if (systemIfaces(node).contains(portName)) {
setNodeState(node, NodeState.INCOMPLETE);
}
}
}
private class InternalDeviceListener implements DeviceListener {
private void readConfiguration() {
OpenstackNodeConfig config =
configService.getConfig(appId, OpenstackNodeConfig.class);
@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;
}
}
}
private void readConfiguration() {
OpenstackNodeConfig config = configRegistry.getConfig(appId, CONFIG_CLASS);
if (config == null) {
log.error("No configuration found");
log.debug("No configuration found");
return;
}
config.openstackNodes().stream().forEach(node -> addNode(node));
log.info("Node configured");
config.openstackNodes().forEach(this::addOrUpdateNode);
}
private class InternalConfigListener implements NetworkConfigListener {
@Override
public void event(NetworkConfigEvent event) {
if (!event.configClass().equals(OpenstackNodeConfig.class)) {
NodeId leaderNodeId = leadershipService.getLeader(appId.name());
if (!Objects.equals(localNodeId, leaderNodeId)) {
// do not allow to proceed without leadership
return;
}
if (!event.configClass().equals(CONFIG_CLASS)) {
return;
}
......@@ -649,6 +641,46 @@ public class OpenstackNodeManager implements OpenstackNodeService {
}
}
private class InternalMapListener implements MapEventListener<String, OpenstackNode> {
@Override
public void event(MapEvent<String, OpenstackNode> event) {
NodeId leaderNodeId = leadershipService.getLeader(appId.name());
if (!Objects.equals(localNodeId, leaderNodeId)) {
// do not allow to proceed without leadership
return;
}
OpenstackNode oldNode;
OpenstackNode newNode;
switch (event.type()) {
case UPDATE:
oldNode = event.oldValue().value();
newNode = event.newValue().value();
log.debug("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;
}
}
}
}
......
......@@ -15,26 +15,33 @@
*/
package org.onosproject.openstacknode;
import org.onlab.packet.IpAddress;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import java.util.List;
import java.util.Optional;
import java.util.Set;
/**
* Handles the bootstrap request for compute/gateway node.
*/
public interface OpenstackNodeService {
public enum OpenstackNodeType {
enum NodeType {
/**
* Compute or Gateway Node.
*/
COMPUTENODE,
GATEWAYNODE
COMPUTE,
GATEWAY
}
/**
* Adds a new node to the service.
* Adds or updates a new node to the service.
*
* @param node openstack node
*/
void addNode(OpenstackNode node);
void addOrUpdateNode(OpenstackNode node);
/**
* Deletes a node from the service.
......@@ -44,18 +51,58 @@ public interface OpenstackNodeService {
void deleteNode(OpenstackNode node);
/**
* Returns nodes known to the service for designated openstacktype.
* Returns all nodes known to the service.
*
* @param openstackNodeType openstack node type
* @return list of nodes
*/
List<OpenstackNode> getNodes(OpenstackNodeType openstackNodeType);
List<OpenstackNode> nodes();
/**
* Returns the NodeState for a given node.
* Returns all nodes in complete state.
*
* @param node openstack node
* @return true if the NodeState for a given node is COMPLETE, false otherwise
* @return set of nodes
*/
Set<OpenstackNode> completeNodes();
/**
* Returns node initialization state is complete or not.
*
* @param hostname hostname of the node
* @return true if initial node setup is completed, otherwise false
*/
boolean isComplete(String hostname);
/**
* Returns data network IP address of a given integration bridge device.
*
* @param intBridgeId integration bridge device id
* @return ip address; empty value otherwise
*/
Optional<IpAddress> dataIp(DeviceId intBridgeId);
/**
* Returns tunnel port number of a given integration bridge device.
*
* @param intBridgeId integration bridge device id
* @return port number; or empty value
*/
Optional<PortNumber> tunnelPort(DeviceId intBridgeId);
/**
* Returns router bridge device ID connected to a given integration bridge.
* It returns valid value only if the node type is GATEWAY.
*
* @param intBridgeId device id of the integration bridge
* @return device id of a router bridge; or empty value
*/
Optional<DeviceId> routerBridge(DeviceId intBridgeId);
/**
* Returns port number connected to the router bridge.
* It returns valid value only if the node type is GATEWAY.
*
* @param intBridgeId integration bridge device id
* @return port number; or empty value
*/
boolean isComplete(OpenstackNode node);
Optional<PortNumber> externalPort(DeviceId intBridgeId);
}
......
/*
* 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.openstacknode;
/**
* Entity that defines possible init state of the OpenStack node.
*/
public interface OpenstackNodeState {
/**
* Returns null for no state.
*
* @return null
*/
static OpenstackNodeState noState() {
return null;
}
}
\ No newline at end of file
/*
* 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.openstacknode.cli;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Port;
import org.onosproject.net.Device;
import org.onosproject.net.device.DeviceService;
import org.onosproject.openstacknode.OpenstackNode;
import org.onosproject.openstacknode.OpenstackNodeService;
import static org.onosproject.net.AnnotationKeys.PORT_NAME;
import static org.onosproject.openstacknode.Constants.*;
import static org.onosproject.openstacknode.OpenstackNodeService.NodeType.GATEWAY;
/**
* Checks detailed node init state.
*/
@Command(scope = "onos", name = "openstack-node-check",
description = "Shows detailed node init state")
public class OpenstackNodeCheckCommand extends AbstractShellCommand {
@Argument(index = 0, name = "hostname", description = "Hostname",
required = true, multiValued = false)
private String hostname = null;
private static final String MSG_OK = "OK";
private static final String MSG_NO = "NO";
@Override
protected void execute() {
OpenstackNodeService nodeService = AbstractShellCommand.get(OpenstackNodeService.class);
DeviceService deviceService = AbstractShellCommand.get(DeviceService.class);
OpenstackNode node = nodeService.nodes()
.stream()
.filter(n -> n.hostname().equals(hostname))
.findFirst()
.orElse(null);
if (node == null) {
print("Cannot find %s from registered nodes", hostname);
return;
}
print("%n[Integration Bridge Status]");
Device device = deviceService.getDevice(node.intBridge());
if (device != null) {
print("%s %s=%s available=%s %s",
deviceService.isAvailable(device.id()) ? MSG_OK : MSG_NO,
INTEGRATION_BRIDGE,
device.id(),
deviceService.isAvailable(device.id()),
device.annotations());
print(getPortState(deviceService, node.intBridge(), DEFAULT_TUNNEL));
} else {
print("%s %s=%s is not available",
MSG_NO,
INTEGRATION_BRIDGE,
node.intBridge());
}
if (node.type().equals(GATEWAY)) {
print("%n[Router Bridge Status]");
device = deviceService.getDevice(node.routerBridge().get());
if (device != null) {
print("%s %s=%s available=%s %s",
deviceService.isAvailable(device.id()) ? MSG_OK : MSG_NO,
ROUTER_BRIDGE,
device.id(),
deviceService.isAvailable(device.id()),
device.annotations());
print(getPortState(deviceService, node.routerBridge().get(), PATCH_ROUT_BRIDGE));
print(getPortState(deviceService, node.intBridge(), PATCH_INTG_BRIDGE));
} else {
print("%s %s=%s is not available",
MSG_NO,
ROUTER_BRIDGE,
node.intBridge());
}
}
}
private String getPortState(DeviceService deviceService, DeviceId deviceId, String portName) {
Port port = deviceService.getPorts(deviceId).stream()
.filter(p -> p.annotations().value(PORT_NAME).equals(portName) &&
p.isEnabled())
.findAny().orElse(null);
if (port != null) {
return String.format("%s %s portNum=%s enabled=%s %s",
port.isEnabled() ? MSG_OK : MSG_NO,
portName,
port.number(),
port.isEnabled() ? Boolean.TRUE : Boolean.FALSE,
port.annotations());
} else {
return String.format("%s %s does not exist", MSG_NO, portName);
}
}
}
/*
* 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.openstacknode.cli;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.openstacknode.OpenstackNode;
import org.onosproject.openstacknode.OpenstackNodeService;
import java.util.NoSuchElementException;
/**
* Initializes nodes for OpenStack node service.
*/
@Command(scope = "onos", name = "openstack-node-init",
description = "Initializes nodes for OpenStack node service")
public class OpenstackNodeInitCommand extends AbstractShellCommand {
@Argument(index = 0, name = "hostnames", description = "Hostname(s)",
required = true, multiValued = true)
private String[] hostnames = null;
@Override
protected void execute() {
OpenstackNodeService nodeService = AbstractShellCommand.get(OpenstackNodeService.class);
for (String hostname : hostnames) {
OpenstackNode node;
try {
node = nodeService.nodes()
.stream()
.filter(n -> n.hostname().equals(hostname))
.findFirst().get();
} catch (NoSuchElementException e) {
print("Unable to find %s", hostname);
continue;
}
nodeService.addOrUpdateNode(node);
}
}
}
/*
* 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.openstacknode.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.openstacknode.OpenstackNode;
import org.onosproject.openstacknode.OpenstackNodeService;
import java.util.Collections;
import java.util.List;
/**
* Lists all nodes registered to the service.
*/
@Command(scope = "onos", name = "openstack-nodes",
description = "Lists all nodes registered in OpenStack node service")
public class OpenstackNodeListCommand extends AbstractShellCommand {
private static final String COMPLETE = "COMPLETE";
private static final String INCOMPLETE = "INCOMPLETE";
@Override
protected void execute() {
OpenstackNodeService nodeService = AbstractShellCommand.get(OpenstackNodeService.class);
List<OpenstackNode> nodes = nodeService.nodes();
Collections.sort(nodes, OpenstackNode.OPENSTACK_NODE_COMPARATOR);
if (outputJson()) {
print("%s", json(nodeService, nodes));
} else {
for (OpenstackNode node : nodes) {
print("hostname=%s, type=%s, managementIp=%s, dataIp=%s, intBridge=%s, routerBridge=%s init=%s",
node.hostname(),
node.type(),
node.managementIp(),
node.dataIp(),
node.intBridge(),
node.routerBridge(),
getState(nodeService, node));
}
print("Total %s nodes", nodeService.nodes().size());
}
}
private JsonNode json(OpenstackNodeService nodeService, List<OpenstackNode> nodes) {
ObjectMapper mapper = new ObjectMapper();
ArrayNode result = mapper.createArrayNode();
for (OpenstackNode node : nodes) {
result.add(mapper.createObjectNode()
.put("hostname", node.hostname())
.put("type", node.type().name())
.put("managementIp", node.managementIp().toString())
.put("dataIp", node.dataIp().toString())
.put("intBridge", node.intBridge().toString())
.put("routerBridge", node.routerBridge().toString())
.put("state", getState(nodeService, node)));
}
return result;
}
private String getState(OpenstackNodeService nodeService, OpenstackNode node) {
return nodeService.isComplete(node.hostname()) ? COMPLETE : INCOMPLETE;
}
}
\ No newline at end of file
/*
* 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.
*/
/**
* Console commands to manage OpenStack nodes.
*/
package org.onosproject.openstacknode.cli;
\ No newline at end of file
<!--
~ 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.
-->
<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.openstacknode.cli.OpenstackNodeListCommand"/>
</command>
<command>
<action class="org.onosproject.openstacknode.cli.OpenstackNodeCheckCommand"/>
</command>
<command>
<action class="org.onosproject.openstacknode.cli.OpenstackNodeInitCommand"/>
</command>
</command-bundle>
</blueprint>