From a7d6e9fada117b5ba2ec1dc6325fa4e088fd800f Mon Sep 17 00:00:00 2001 From: Daniel Park Date: Mon, 18 Jan 2016 17:54:14 +0900 Subject: [PATCH] [ONOS-3688] Implement application for Openstack Node bootstrap - Using OVSDB connnection, it makes bridge and vxlan tunnel for Openstack Nodes. - Reading nodes information from network config supported Change-Id: I1c0483b146ace19cd77ac91141182ee9200a9432 --- apps/openstacknode/network-cfg.json | 34 + apps/openstacknode/pom.xml | 73 ++ .../openstacknode/ConnectionHandler.java | 35 + .../openstacknode/OpenstackNode.java | 196 ++++++ .../openstacknode/OpenstackNodeConfig.java | 89 +++ .../openstacknode/OpenstackNodeManager.java | 630 ++++++++++++++++++ .../openstacknode/OpenstackNodeService.java | 61 ++ .../openstacknode/package-info.java | 20 + apps/pom.xml | 1 + 9 files changed, 1139 insertions(+) create mode 100644 apps/openstacknode/network-cfg.json create mode 100644 apps/openstacknode/pom.xml create mode 100644 apps/openstacknode/src/main/java/org/onosproject/openstacknode/ConnectionHandler.java create mode 100644 apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNode.java create mode 100644 apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeConfig.java create mode 100644 apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeManager.java create mode 100644 apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeService.java create mode 100644 apps/openstacknode/src/main/java/org/onosproject/openstacknode/package-info.java diff --git a/apps/openstacknode/network-cfg.json b/apps/openstacknode/network-cfg.json new file mode 100644 index 0000000000..959b4b34a3 --- /dev/null +++ b/apps/openstacknode/network-cfg.json @@ -0,0 +1,34 @@ +{ + "apps" : { + "org.onosproject.openstacknode" : { + "openstacknode" : { + "nodes" : [ + { + "hostname" : "compute-01", + "ovsdbIp" : "192.168.56.112", + "ovsdbPort" : "6640", + "bridgeId" : "of:0000000000000001", + "openstackNodeType" : "COMPUTENODE" + }, + { + "hostname" : "compute-02", + "ovsdbIp" : "192.168.56.106", + "ovsdbPort" : "6640", + "bridgeId" : "of:0000000000000002", + "openstackNodeType" : "COMPUTENODE" + }, + { + "hostname" : "network", + "ovsdbIp" : "192.168.56.108", + "ovsdbPort" : "6640", + "bridgeId" : "of:0000000000000003", + "openstackNodeType" : "GATEWAYNODE", + "gatewayExternalInterfaceName" : "eth1", + "gatewayExternalInterfaceMac" : "00:00:00:00:00:10" + } + ] + } + } + } +} + diff --git a/apps/openstacknode/pom.xml b/apps/openstacknode/pom.xml new file mode 100644 index 0000000000..f97f890d09 --- /dev/null +++ b/apps/openstacknode/pom.xml @@ -0,0 +1,73 @@ + + + + 4.0.0 + + + org.onosproject + onos-apps + 1.5.0-SNAPSHOT + ../pom.xml + + + onos-app-openstacknode + bundle + + SONA Openstack Node Bootstrap Application + + + + org.onosproject.openstacknode + default + http://onosproject.org + SONA Openstack Node Bootstrap Application + + org.onosproject.ovsdb + + + + + + org.osgi + org.osgi.compendium + + + org.onosproject + onos-api + + + org.onosproject + onos-core-serializers + ${project.version} + + + org.onosproject + onos-ovsdb-api + ${project.version} + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.core + jackson-annotations + + + diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/ConnectionHandler.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/ConnectionHandler.java new file mode 100644 index 0000000000..95a90a17e0 --- /dev/null +++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/ConnectionHandler.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.openstacknode; + +/** + * Entity capable of handling a subject connected and disconnected situation. + */ +public interface ConnectionHandler { + /** + * Processes the connected subject. + * + * @param subject subject + */ + void connected(T subject); + + /** + * Processes the disconnected subject. + * + * @param subject subject. + */ + void disconnected(T subject); +} diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNode.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNode.java new file mode 100644 index 0000000000..a96b5ac7b4 --- /dev/null +++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNode.java @@ -0,0 +1,196 @@ +/* + * Copyright 2016 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.openstacknode; + +import com.google.common.base.MoreObjects; +import org.onlab.packet.IpAddress; +import org.onlab.packet.MacAddress; +import org.onlab.packet.TpPort; +import org.onosproject.net.DeviceId; + +import java.util.Comparator; +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Representation of a compute/gateway node for OpenstackSwitching/Routing service. + */ +public final class OpenstackNode { + + private final String hostName; + private final IpAddress ovsdbIp; + private final TpPort ovsdbPort; + private final DeviceId bridgeId; + private final OpenstackNodeService.OpenstackNodeType openstackNodeType; + private final String gatewayExternalInterfaceName; + private final MacAddress gatewayExternalInterfaceMac; + private static final String OVSDB = "ovsdb:"; + + + public static final Comparator OPENSTACK_NODE_COMPARATOR = + (node1, node2) -> node1.hostName().compareTo(node2.hostName()); + + /** + * Creates a new node. + * + * @param hostName hostName + * @param ovsdbIp OVSDB server IP address + * @param ovsdbPort OVSDB server port number + * @param bridgeId integration bridge identifier + * @param openstackNodeType openstack node type + * @param gatewayExternalInterfaceName gatewayExternalInterfaceName + * @param gatewayExternalInterfaceMac gatewayExternalInterfaceMac + */ + public OpenstackNode(String hostName, IpAddress ovsdbIp, TpPort ovsdbPort, DeviceId bridgeId, + OpenstackNodeService.OpenstackNodeType openstackNodeType, + String gatewayExternalInterfaceName, + MacAddress gatewayExternalInterfaceMac) { + this.hostName = checkNotNull(hostName, "hostName cannot be null"); + this.ovsdbIp = checkNotNull(ovsdbIp, "ovsdbIp cannot be null"); + this.ovsdbPort = checkNotNull(ovsdbPort, "ovsdbPort cannot be null"); + this.bridgeId = checkNotNull(bridgeId, "bridgeId cannot be null"); + this.openstackNodeType = checkNotNull(openstackNodeType, "openstackNodeType cannot be null"); + this.gatewayExternalInterfaceName = gatewayExternalInterfaceName; + this.gatewayExternalInterfaceMac = gatewayExternalInterfaceMac; + + if (openstackNodeType == OpenstackNodeService.OpenstackNodeType.GATEWAYNODE) { + checkNotNull(gatewayExternalInterfaceName, "gatewayExternalInterfaceName cannot be null"); + checkNotNull(gatewayExternalInterfaceMac, "gatewayExternalInterfaceMac cannot be null"); + } + } + + /** + * Returns the OVSDB server IP address. + * + * @return ip address + */ + public IpAddress ovsdbIp() { + return this.ovsdbIp; + } + + /** + * Returns the OVSDB server port number. + * + * @return port number + */ + public TpPort ovsdbPort() { + return this.ovsdbPort; + } + + /** + * Returns the hostName. + * + * @return hostName + */ + public String hostName() { + return this.hostName; + } + + /** + * Returns the identifier of the integration bridge. + * + * @return device id + */ + public DeviceId intBrId() { + return this.bridgeId; + } + + /** + * Returns the identifier of the OVSDB device. + * + * @return device id + */ + public DeviceId ovsdbId() { + return DeviceId.deviceId(OVSDB.concat(this.ovsdbIp.toString())); + } + + /** + * Returns the openstack node type. + * + * @return openstack node type + */ + public OpenstackNodeService.OpenstackNodeType openstackNodeType() { + return this.openstackNodeType; + } + + /** + * Returns the gatewayExternalInterfaceName. + * + * @return gatewayExternalInterfaceName + */ + public String gatewayExternalInterfaceName() { + return this.gatewayExternalInterfaceName; + } + + /** + * Returns the gatewayExternalInterfaceMac. + * + * @return gatewayExternalInterfaceMac + */ + public MacAddress gatewayExternalInterfaceMac() { + return this.gatewayExternalInterfaceMac; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof OpenstackNode) { + OpenstackNode that = (OpenstackNode) obj; + + if (Objects.equals(hostName, that.hostName) && + Objects.equals(ovsdbIp, that.ovsdbIp) && + Objects.equals(ovsdbPort, that.ovsdbPort) && + Objects.equals(bridgeId, that.bridgeId) && + Objects.equals(openstackNodeType, that.openstackNodeType)) { + return true; + } + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(hostName, ovsdbIp, ovsdbPort, bridgeId, openstackNodeType); + } + + @Override + public String toString() { + if (openstackNodeType == OpenstackNodeService.OpenstackNodeType.COMPUTENODE) { + return MoreObjects.toStringHelper(getClass()) + .add("host", hostName) + .add("ip", ovsdbIp) + .add("port", ovsdbPort) + .add("bridgeId", bridgeId) + .add("openstacknodetype", openstackNodeType) + .toString(); + } else { + return MoreObjects.toStringHelper(getClass()) + .add("host", hostName) + .add("ip", ovsdbIp) + .add("port", ovsdbPort) + .add("bridgeId", bridgeId) + .add("openstacknodetype", openstackNodeType) + .add("gatewayExternalInterfaceName", gatewayExternalInterfaceName) + .add("gatewayExternalInterfaceMac", gatewayExternalInterfaceMac) + .toString(); + } + } +} + diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeConfig.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeConfig.java new file mode 100644 index 0000000000..ffd45ca83c --- /dev/null +++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeConfig.java @@ -0,0 +1,89 @@ +/* + * Copyright 2016 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.openstacknode; + + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.Sets; +import org.onlab.packet.Ip4Address; +import org.onlab.packet.MacAddress; +import org.onlab.packet.TpPort; +import org.onosproject.core.ApplicationId; +import org.onosproject.net.DeviceId; +import org.slf4j.Logger; +import java.util.Set; +import org.onosproject.net.config.Config; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Configuration object for OpensatckNode service. + */ +public class OpenstackNodeConfig extends Config { + + protected final Logger log = getLogger(getClass()); + + + public static final String NODES = "nodes"; + public static final String HOST_NAME = "hostname"; + public static final String OVSDB_IP = "ovsdbIp"; + public static final String OVSDB_PORT = "ovsdbPort"; + public static final String BRIDGE_ID = "bridgeId"; + public static final String NODE_TYPE = "openstackNodeType"; + public static final String GATEWAY_EXTERNAL_INTERFACE_NAME = "gatewayExternalInterfaceName"; + public static final String GATEWAY_EXTERNAL_INTERFACE_MAC = "gatewayExternalInterfaceMac"; + + /** + * Returns the set of nodes read from network config. + * + * @return set of OpensatckNodeConfig or null + */ + public Set openstackNodes() { + + Set nodes = Sets.newHashSet(); + + JsonNode jsonNodes = object.get(NODES); + if (jsonNodes == null) { + return null; + } + + jsonNodes.forEach(jsonNode -> { + try { + if (OpenstackNodeService.OpenstackNodeType.valueOf(jsonNode.path(NODE_TYPE).asText()) == + OpenstackNodeService.OpenstackNodeType.COMPUTENODE) { + nodes.add(new OpenstackNode( + jsonNode.path(HOST_NAME).asText(), + Ip4Address.valueOf(jsonNode.path(OVSDB_IP).asText()), + TpPort.tpPort(jsonNode.path(OVSDB_PORT).asInt()), + DeviceId.deviceId(jsonNode.path(BRIDGE_ID).asText()), + OpenstackNodeService.OpenstackNodeType.valueOf(jsonNode.path(NODE_TYPE).asText()), + null, MacAddress.NONE)); + } else { + nodes.add(new OpenstackNode( + jsonNode.path(HOST_NAME).asText(), + Ip4Address.valueOf(jsonNode.path(OVSDB_IP).asText()), + TpPort.tpPort(jsonNode.path(OVSDB_PORT).asInt()), + DeviceId.deviceId(jsonNode.path(BRIDGE_ID).asText()), + OpenstackNodeService.OpenstackNodeType.valueOf(jsonNode.path(NODE_TYPE).asText()), + jsonNode.path(GATEWAY_EXTERNAL_INTERFACE_NAME).asText(), + MacAddress.valueOf(jsonNode.path(GATEWAY_EXTERNAL_INTERFACE_MAC).asText()))); + } + } catch (IllegalArgumentException | NullPointerException e) { + log.error("Failed to read {}", e.toString()); + } + }); + return nodes; + } +} diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeManager.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeManager.java new file mode 100644 index 0000000000..28b41cfce7 --- /dev/null +++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeManager.java @@ -0,0 +1,630 @@ +/* + * Copyright 2016 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.openstacknode; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onlab.util.ItemNotFoundException; +import org.onlab.util.KryoNamespace; +import org.onosproject.cluster.ClusterService; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Port; +import org.onosproject.net.behaviour.BridgeConfig; +import org.onosproject.net.behaviour.BridgeName; +import org.onosproject.net.behaviour.ControllerInfo; +import org.onosproject.net.behaviour.DefaultTunnelDescription; +import org.onosproject.net.behaviour.TunnelConfig; +import org.onosproject.net.behaviour.TunnelDescription; +import org.onosproject.net.behaviour.TunnelName; +import org.onosproject.net.config.ConfigFactory; +import org.onosproject.net.config.NetworkConfigEvent; +import org.onosproject.net.config.NetworkConfigListener; +import org.onosproject.net.config.NetworkConfigRegistry; +import org.onosproject.net.config.NetworkConfigService; +import org.onosproject.net.config.basics.SubjectFactories; +import org.onosproject.net.device.DeviceAdminService; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.driver.DriverHandler; +import org.onosproject.net.driver.DriverService; +import org.onosproject.ovsdb.controller.OvsdbClientService; +import org.onosproject.ovsdb.controller.OvsdbController; +import org.onosproject.ovsdb.controller.OvsdbNodeId; +import org.onosproject.store.serializers.KryoNamespaces; +import org.onosproject.store.service.ConsistentMap; +import org.onosproject.store.service.Serializer; +import org.onosproject.store.service.StorageService; +import org.slf4j.Logger; + +import static org.onlab.util.Tools.groupedThreads; +import static org.onosproject.net.Device.Type.SWITCH; +import static org.onosproject.net.behaviour.TunnelDescription.Type.VXLAN; +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +import static com.google.common.base.Preconditions.checkNotNull; + + +/** + * Initializes devices in compute/gateway nodes according to there type. + */ +@Component(immediate = true) +@Service +public class OpenstackNodeManager implements OpenstackNodeService { + protected final Logger log = getLogger(getClass()); + private static final int NUM_THREADS = 1; + private static final KryoNamespace.Builder NODE_SERIALIZER = KryoNamespace.newBuilder() + .register(KryoNamespaces.API) + .register(OpenstackNode.class) + .register(OpenstackNodeType.class) + .register(NodeState.class); + private static final String DEFAULT_BRIDGE = "br-int"; + private static final String DEFAULT_TUNNEL = "vxlan"; + private static final String PORT_NAME = "portName"; + private static final String OPENSTACK_NODESTORE = "openstacknode-nodestore"; + private static final String OPENSTACK_NODEMANAGER_ID = "org.onosproject.openstacknode"; + + private static final Map DEFAULT_TUNNEL_OPTIONS + = ImmutableMap.of("key", "flow", "remote_ip", "flow"); + + private static final int DPID_BEGIN = 3; + private static final int OFPORT = 6653; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected OvsdbController controller; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ClusterService clusterService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DriverService driverService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceAdminService adminService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected StorageService storageService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected NetworkConfigService configService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected NetworkConfigRegistry configRegistry; + + private final OvsdbHandler ovsdbHandler = new OvsdbHandler(); + private final BridgeHandler bridgeHandler = new BridgeHandler(); + private final NetworkConfigListener configListener = new InternalConfigListener(); + private final ConfigFactory configFactory = + new ConfigFactory(SubjectFactories.APP_SUBJECT_FACTORY, OpenstackNodeConfig.class, "openstacknode") { + @Override + public OpenstackNodeConfig createConfig() { + return new OpenstackNodeConfig(); + } + }; + + private final ExecutorService eventExecutor = Executors + .newFixedThreadPool(NUM_THREADS, groupedThreads("onos/openstacknode", "event-handler")); + + private final DeviceListener deviceListener = new InternalDeviceListener(); + + private ApplicationId appId; + private ConsistentMap nodeStore; + + private enum NodeState { + + INIT { + @Override + public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) { + openstackNodeManager.connect(node); + } + }, + OVSDB_CONNECTED { + @Override + public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) { + if (!openstackNodeManager.getOvsdbConnectionState(node)) { + openstackNodeManager.connect(node); + } else { + openstackNodeManager.createIntegrationBridge(node); + } + } + }, + BRIDGE_CREATED { + @Override + public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) { + if (!openstackNodeManager.getOvsdbConnectionState(node)) { + openstackNodeManager.connect(node); + } else { + openstackNodeManager.createTunnelInterface(node); + } + } + }, + COMPLETE { + @Override + public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) { + openstackNodeManager.postInit(node); + } + }, + INCOMPLETE { + @Override + public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) { + } + }; + + public abstract void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node); + } + + @Activate + protected void activate() { + appId = coreService.registerApplication(OPENSTACK_NODEMANAGER_ID); + nodeStore = storageService.consistentMapBuilder() + .withSerializer(Serializer.using(NODE_SERIALIZER.build())) + .withName(OPENSTACK_NODESTORE) + .withApplicationId(appId) + .build(); + + deviceService.addListener(deviceListener); + configRegistry.registerConfigFactory(configFactory); + configService.addListener(configListener); + readConfiguration(); + + log.info("Started"); + } + + @Deactivate + protected void deactivate() { + deviceService.removeListener(deviceListener); + eventExecutor.shutdown(); + nodeStore.clear(); + + configRegistry.unregisterConfigFactory(configFactory); + configService.removeListener(configListener); + + log.info("Stopped"); + } + + + @Override + public void addNode(OpenstackNode node) { + checkNotNull(node, "Node cannot be null"); + + nodeStore.putIfAbsent(node, checkNodeState(node)); + + NodeState state = checkNodeState(node); + state.process(this, node); + } + + @Override + public void deleteNode(OpenstackNode node) { + checkNotNull(node, "Node cannot be null"); + + if (getOvsdbConnectionState(node)) { + disconnect(node); + } + + nodeStore.remove(node); + } + + @Override + public List getNodes(OpenstackNodeType openstackNodeType) { + List nodes = new ArrayList<>(); + nodes.addAll(nodeStore.keySet().stream().filter(node -> node.openstackNodeType() + .equals(openstackNodeType)).collect(Collectors.toList())); + return nodes; + } + + private List getNodesAll() { + List nodes = new ArrayList<>(); + nodes.addAll(nodeStore.keySet()); + return nodes; + } + + @Override + public boolean isComplete(OpenstackNode node) { + checkNotNull(node, "Node cannot be null"); + + if (!nodeStore.containsKey(node)) { + log.warn("Node {} does not exist", node.hostName()); + return false; + } else if (nodeStore.get(node).equals(NodeState.COMPLETE)) { + return true; + } + return false; + } + + /** + * Checks current state of a given openstack node and returns it. + * + * @param node openstack node + * @return node state + */ + private NodeState checkNodeState(OpenstackNode node) { + checkNotNull(node, "Node cannot be null"); + + if (checkIntegrationBridge(node) && checkTunnelInterface(node)) { + return NodeState.COMPLETE; + } else if (checkIntegrationBridge(node)) { + return NodeState.BRIDGE_CREATED; + } else if (getOvsdbConnectionState(node)) { + return NodeState.OVSDB_CONNECTED; + } else { + return NodeState.INIT; + } + } + + + /** + * Checks if integration bridge exists and available. + * + * @param node openstack node + * @return true if the bridge is available, false otherwise + */ + private boolean checkIntegrationBridge(OpenstackNode node) { + return (deviceService.getDevice(node.intBrId()) != null + && deviceService.isAvailable(node.intBrId())); + } + /** + * Checks if tunnel interface exists. + * + * @param node openstack node + * @return true if the interface exists, false otherwise + */ + private boolean checkTunnelInterface(OpenstackNode node) { + checkNotNull(node, "Node cannot be null"); + return deviceService.getPorts(node.intBrId()) + .stream() + .filter(p -> p.annotations().value(PORT_NAME).contains(DEFAULT_TUNNEL) && p.isEnabled()) + .findAny().isPresent(); + } + + /** + * Returns connection state of OVSDB server for a given node. + * + * @param node openstack node + * @return true if it is connected, false otherwise + */ + private boolean getOvsdbConnectionState(OpenstackNode node) { + checkNotNull(node, "Node cannot be null"); + + OvsdbClientService ovsdbClient = getOvsdbClient(node); + return deviceService.isAvailable(node.ovsdbId()) && + ovsdbClient != null && ovsdbClient.isConnected(); + } + + /** + * Returns OVSDB client for a given node. + * + * @param node openstack node + * @return OVSDB client, or null if it fails to get OVSDB client + */ + private OvsdbClientService getOvsdbClient(OpenstackNode node) { + checkNotNull(node, "Node cannot be null"); + + OvsdbClientService ovsdbClient = controller.getOvsdbClient( + new OvsdbNodeId(node.ovsdbIp(), node.ovsdbPort().toInt())); + if (ovsdbClient == null) { + log.debug("Couldn't find OVSDB client for {}", node.hostName()); + } + return ovsdbClient; + } + + /** + * Connects to OVSDB server for a given node. + * + * @param node openstack node + */ + private void connect(OpenstackNode node) { + checkNotNull(node, "Node cannot be null"); + + if (!nodeStore.containsKey(node)) { + log.warn("Node {} does not exist", node.hostName()); + return; + } + + if (!getOvsdbConnectionState(node)) { + controller.connect(node.ovsdbIp(), node.ovsdbPort()); + } + } + + /** + * Creates an integration bridge for a given node. + * + * @param node openstack node + */ + private void createIntegrationBridge(OpenstackNode node) { + if (checkIntegrationBridge(node)) { + return; + } + + List controllers = new ArrayList<>(); + Sets.newHashSet(clusterService.getNodes()).stream() + .forEach(controller -> { + ControllerInfo ctrlInfo = new ControllerInfo(controller.ip(), OFPORT, "tcp"); + controllers.add(ctrlInfo); + }); + String dpid = node.intBrId().toString().substring(DPID_BEGIN); + + try { + DriverHandler handler = driverService.createHandler(node.ovsdbId()); + BridgeConfig bridgeConfig = handler.behaviour(BridgeConfig.class); + bridgeConfig.addBridge(BridgeName.bridgeName(DEFAULT_BRIDGE), dpid, controllers); + } catch (ItemNotFoundException e) { + log.warn("Failed to create integration bridge on {}", node.ovsdbId()); + } + } + + /** + * Creates tunnel interface to the integration bridge for a given node. + * + * @param node openstack node + */ + private void createTunnelInterface(OpenstackNode node) { + if (checkTunnelInterface(node)) { + return; + } + + DefaultAnnotations.Builder optionBuilder = DefaultAnnotations.builder(); + for (String key : DEFAULT_TUNNEL_OPTIONS.keySet()) { + optionBuilder.set(key, DEFAULT_TUNNEL_OPTIONS.get(key)); + } + TunnelDescription description = + new DefaultTunnelDescription(null, null, VXLAN, TunnelName.tunnelName(DEFAULT_TUNNEL), + optionBuilder.build()); + try { + DriverHandler handler = driverService.createHandler(node.ovsdbId()); + TunnelConfig tunnelConfig = handler.behaviour(TunnelConfig.class); + tunnelConfig.createTunnelInterface(BridgeName.bridgeName(DEFAULT_BRIDGE), description); + } catch (ItemNotFoundException e) { + log.warn("Failed to create tunnel interface on {}", node.ovsdbId()); + } + } + + /** + * Performs tasks after node initialization. + * First disconnect unnecessary OVSDB connection and then installs flow rules + * for existing VMs if there are any. + * + * @param node openstack node + */ + private void postInit(OpenstackNode node) { + disconnect(node); + log.info("Finished initializing {}", node.hostName()); + } + + /** + * Sets a new state for a given openstack node. + * + * @param node openstack node + * @param newState new node state + */ + private void setNodeState(OpenstackNode node, NodeState newState) { + checkNotNull(node, "Node cannot be null"); + + log.debug("Changed {} state: {}", node.hostName(), newState.toString()); + + nodeStore.put(node, newState); + newState.process(this, node); + } + + /** + * Returns openstack node associated with a given OVSDB device. + * + * @param ovsdbId OVSDB device id + * @return openstack node, null if it fails to find the node + */ + private OpenstackNode getNodeByOvsdbId(DeviceId ovsdbId) { + + return getNodesAll().stream() + .filter(node -> node.ovsdbId().equals(ovsdbId)) + .findFirst().orElse(null); + } + + /** + * Returns openstack node associated with a given integration bridge. + * + * @param bridgeId device id of integration bridge + * @return openstack node, null if it fails to find the node + */ + private OpenstackNode getNodeByBridgeId(DeviceId bridgeId) { + return getNodesAll().stream() + .filter(node -> node.intBrId().equals(bridgeId)) + .findFirst().orElse(null); + } + /** + * Disconnects OVSDB server for a given node. + * + * @param node openstack node + */ + private void disconnect(OpenstackNode node) { + checkNotNull(node, "Node cannot be null"); + + if (!nodeStore.containsKey(node)) { + log.warn("Node {} does not exist", node.hostName()); + return; + } + + if (getOvsdbConnectionState(node)) { + OvsdbClientService ovsdbClient = getOvsdbClient(node); + ovsdbClient.disconnect(); + } + } + + private class InternalDeviceListener implements DeviceListener { + + @Override + public void event(DeviceEvent event) { + + Device device = event.subject(); + ConnectionHandler handler = + (device.type().equals(SWITCH) ? bridgeHandler : ovsdbHandler); + + switch (event.type()) { + case PORT_ADDED: + eventExecutor.submit(() -> bridgeHandler.portAdded(event.port())); + break; + case PORT_UPDATED: + if (!event.port().isEnabled()) { + eventExecutor.submit(() -> bridgeHandler.portRemoved(event.port())); + } + break; + case DEVICE_ADDED: + case DEVICE_AVAILABILITY_CHANGED: + if (deviceService.isAvailable(device.id())) { + eventExecutor.submit(() -> handler.connected(device)); + } else { + eventExecutor.submit(() -> handler.disconnected(device)); + } + break; + default: + log.debug("Unsupported event type {}", event.type().toString()); + break; + } + } + } + + private class OvsdbHandler implements ConnectionHandler { + + @Override + public void connected(Device device) { + OpenstackNode node = getNodeByOvsdbId(device.id()); + if (node != null) { + setNodeState(node, checkNodeState(node)); + } + } + + @Override + public void disconnected(Device device) { + if (!deviceService.isAvailable(device.id())) { + adminService.removeDevice(device.id()); + } + } + } + + private class BridgeHandler implements ConnectionHandler { + + @Override + public void connected(Device device) { + OpenstackNode node = getNodeByBridgeId(device.id()); + if (node != null) { + setNodeState(node, checkNodeState(node)); + } + } + + @Override + public void disconnected(Device device) { + OpenstackNode node = getNodeByBridgeId(device.id()); + if (node != null) { + log.debug("Integration Bridge is disconnected from {}", node.hostName()); + setNodeState(node, NodeState.INCOMPLETE); + } + } + + /** + * Handles port added situation. + * If the added port is tunnel port, proceed remaining node initialization. + * Otherwise, do nothing. + * + * @param port port + */ + public void portAdded(Port port) { + if (!port.annotations().value(PORT_NAME).contains(DEFAULT_TUNNEL)) { + return; + } + + OpenstackNode node = getNodeByBridgeId((DeviceId) port.element().id()); + if (node != null) { + setNodeState(node, checkNodeState(node)); + } + } + + /** + * Handles port removed situation. + * If the removed port is tunnel port, proceed remaining node initialization. + * Others, do nothing. + * + * @param port port + */ + public void portRemoved(Port port) { + if (!port.annotations().value(PORT_NAME).contains(DEFAULT_TUNNEL)) { + return; + } + + OpenstackNode node = getNodeByBridgeId((DeviceId) port.element().id()); + if (node != null) { + log.info("Tunnel interface is removed from {}", node.hostName()); + setNodeState(node, NodeState.INCOMPLETE); + } + } + } + + + private void readConfiguration() { + OpenstackNodeConfig config = + configService.getConfig(appId, OpenstackNodeConfig.class); + + if (config == null) { + log.error("No configuration found"); + return; + } + + config.openstackNodes().stream().forEach(node -> addNode(node)); + log.info("Node configured"); + } + + private class InternalConfigListener implements NetworkConfigListener { + + @Override + public void event(NetworkConfigEvent event) { + if (!event.configClass().equals(OpenstackNodeConfig.class)) { + return; + } + + switch (event.type()) { + case CONFIG_ADDED: + case CONFIG_UPDATED: + eventExecutor.execute(OpenstackNodeManager.this::readConfiguration); + break; + default: + break; + } + } + } + + +} + diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeService.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeService.java new file mode 100644 index 0000000000..76b7e676d4 --- /dev/null +++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeService.java @@ -0,0 +1,61 @@ +/* + * Copyright 2016 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.openstacknode; + +import java.util.List; + +/** + * Handles the bootstrap request for compute/gateway node. + */ +public interface OpenstackNodeService { + + public enum OpenstackNodeType { + /** + * Compute or Gateway Node. + */ + COMPUTENODE, + GATEWAYNODE + } + /** + * Adds a new node to the service. + * + * @param node openstack node + */ + void addNode(OpenstackNode node); + + /** + * Deletes a node from the service. + * + * @param node openstack node + */ + void deleteNode(OpenstackNode node); + + /** + * Returns nodes known to the service for designated openstacktype. + * + * @param openstackNodeType openstack node type + * @return list of nodes + */ + List getNodes(OpenstackNodeType openstackNodeType); + + /** + * Returns the NodeState for a given node. + * + * @param node openstack node + * @return true if the NodeState for a given node is COMPLETE, false otherwise + */ + boolean isComplete(OpenstackNode node); +} diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/package-info.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/package-info.java new file mode 100644 index 0000000000..d4af18a147 --- /dev/null +++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Application for bootstrapping Compute/Gateway Node in OpenStack. + */ +package org.onosproject.openstacknode; \ No newline at end of file diff --git a/apps/pom.xml b/apps/pom.xml index 032b327a7b..3e3aebed9b 100644 --- a/apps/pom.xml +++ b/apps/pom.xml @@ -68,6 +68,7 @@ openstackrouting cordmcast vpls + openstacknode