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' ],
)
......
......@@ -5,26 +5,33 @@
"nodes" : [
{
"hostname" : "compute-01",
"ovsdbIp" : "192.168.56.112",
"ovsdbPort" : "6640",
"bridgeId" : "of:0000000000000001",
"openstackNodeType" : "COMPUTENODE"
"type" : "COMPUTE",
"managementIp" : "10.203.25.244",
"dataIp" : "10.134.34.222",
"integrationBridge" : "of:00000000000000a1"
},
{
"hostname" : "compute-02",
"ovsdbIp" : "192.168.56.106",
"ovsdbPort" : "6640",
"bridgeId" : "of:0000000000000002",
"openstackNodeType" : "COMPUTENODE"
"type" : "COMPUTE",
"managementIp" : "10.203.229.42",
"dataIp" : "10.134.34.223",
"integrationBridge" : "of:00000000000000a2"
},
{
"hostname" : "network",
"ovsdbIp" : "192.168.56.108",
"ovsdbPort" : "6640",
"bridgeId" : "of:0000000000000003",
"openstackNodeType" : "GATEWAYNODE",
"gatewayExternalInterfaceName" : "eth1",
"gatewayExternalInterfaceMac" : "00:00:00:00:00:10"
"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,30 +172,143 @@ 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)
.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())));
if (type.equals(GATEWAY)) {
nodeBuilder.routerBridge(DeviceId.deviceId(get(node, ROUTER_BRIDGE)));
}
} catch (IllegalArgumentException | NullPointerException e) {
log.error("Failed to read {}", e.toString());
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);
public void process(OpenstackNodeManager nodeManager, OpenstackNode node) {
// make sure there is OVSDB connection
if (!nodeManager.isOvsdbConnected(node)) {
nodeManager.connectOvsdb(node);
return;
}
},
OVSDB_CONNECTED {
@Override
public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) {
if (!openstackNodeManager.getOvsdbConnectionState(node)) {
openstackNodeManager.connect(node);
} else {
openstackNodeManager.createIntegrationBridge(node);
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);
//TODO: Fix any node can engage this operation.
if (!localNodeId.equals(leaderNodeId)) {
log.debug("Only the leaderNode can perform addNode operation");
return;
log.info("Modified");
}
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());
}
@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 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<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();
}
/**
* 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();
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");
private void postInit(OpenstackNode node) {
if (isOvsdbConnected(node)) {
OvsdbNodeId ovsdb = new OvsdbNodeId(node.managementIp(), ovsdbPort);
controller.getOvsdbClient(ovsdb).disconnect();
}
OvsdbClientService ovsdbClient = controller.getOvsdbClient(
new OvsdbNodeId(node.ovsdbIp(), node.ovsdbPort().toInt()));
if (ovsdbClient == null) {
log.debug("Couldn't find OVSDB client for {}", node.hostName());
// TODO add gateway node to scalable gateway pool
log.info("Finished init {}", node.hostname());
}
return ovsdbClient;
private void setNodeState(OpenstackNode node, NodeState newState) {
log.debug("Changed {} state: {}", node.hostname(), newState);
nodeStore.put(node.hostname(), OpenstackNode.getUpdatedNode(node, newState));
}
/**
* Connects to OVSDB server for a given node.
*
* @param node openstack node
*/
private void connect(OpenstackNode node) {
checkNotNull(node, "Node cannot be null");
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 (!nodeStore.containsKey(node)) {
log.warn("Node {} does not exist", node.hostName());
return;
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;
}
if (!getOvsdbConnectionState(node)) {
controller.connect(node.ovsdbIp(), node.ovsdbPort());
return NodeState.COMPLETE;
}
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();
}
/**
* Creates an integration bridge for a given node.
*
* @param node openstack node
*/
private void createIntegrationBridge(OpenstackNode node) {
if (checkIntegrationBridge(node)) {
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 bridgeConfig = device.as(BridgeConfig.class);
bridgeConfig.addBridge(bridgeDesc);
} 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)) {
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");
log.debug("Changed {} state: {}", node.hostName(), newState.toString());
nodeStore.put(node, newState);
newState.process(this, node);
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;
}
/**
* 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) {
PatchDescription patchIntg = DefaultPatchDescription.builder()
.deviceId(INTEGRATION_BRIDGE)
.ifaceName(PATCH_INTG_BRIDGE)
.peer(PATCH_ROUT_BRIDGE)
.build();
return getNodesAll().stream()
.filter(node -> node.ovsdbId().equals(ovsdbId))
.findFirst().orElse(null);
}
PatchDescription patchRout = DefaultPatchDescription.builder()
.deviceId(ROUTER_BRIDGE)
.ifaceName(PATCH_ROUT_BRIDGE)
.peer(PATCH_INTG_BRIDGE)
.build();
/**
* 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);
InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class);
ifaceConfig.addPatchMode(PATCH_INTG_BRIDGE, patchIntg);
ifaceConfig.addPatchMode(PATCH_ROUT_BRIDGE, patchRout);
}
/**
* 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;
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();
}
if (getOvsdbConnectionState(node)) {
OvsdbClientService ovsdbClient = getOvsdbClient(node);
ovsdbClient.disconnect();
}
private void connectOvsdb(OpenstackNode node) {
controller.connect(node.managementIp(), TpPort.tpPort(ovsdbPort));
}
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 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;
}
Device device = event.subject();
ConnectionHandler<Device> handler =
(device.type().equals(SWITCH) ? bridgeHandler : ovsdbHandler);
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));
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) {
if (config == null) {
log.error("No configuration found");
NodeId leaderNodeId = leadershipService.getLeader(appId.name());
if (!Objects.equals(localNodeId, leaderNodeId)) {
// do not allow to proceed without leadership
return;
}
config.openstackNodes().stream().forEach(node -> addNode(node));
log.info("Node configured");
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.debug("No configuration found");
return;
}
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>