From 34bbe17ef01ee84dcb3e20e04379a8c8a6d5b0dc Mon Sep 17 00:00:00 2001 From: Hyunsun Moon Date: Tue, 28 Jun 2016 19:18:40 -0700 Subject: [PATCH] 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 --- apps/openstacknode/BUCK | 4 +- apps/openstacknode/network-cfg.json | 53 +- apps/openstacknode/pom.xml | 12 +- .../onosproject/openstacknode/Constants.java | 31 + .../openstacknode/OpenstackNode.java | 334 +++++--- .../openstacknode/OpenstackNodeConfig.java | 111 ++- .../openstacknode/OpenstackNodeManager.java | 780 +++++++++--------- .../openstacknode/OpenstackNodeService.java | 71 +- .../openstacknode/OpenstackNodeState.java | 30 + .../cli/OpenstackNodeCheckCommand.java | 120 +++ .../cli/OpenstackNodeInitCommand.java | 57 ++ .../cli/OpenstackNodeListCommand.java | 82 ++ .../openstacknode/cli/package-info.java | 20 + .../OSGI-INF/blueprint/shell-config.xml | 29 + 14 files changed, 1170 insertions(+), 564 deletions(-) create mode 100644 apps/openstacknode/src/main/java/org/onosproject/openstacknode/Constants.java create mode 100644 apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeState.java create mode 100644 apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeCheckCommand.java create mode 100644 apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeInitCommand.java create mode 100644 apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeListCommand.java create mode 100644 apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/package-info.java create mode 100644 apps/openstacknode/src/main/resources/OSGI-INF/blueprint/shell-config.xml diff --git a/apps/openstacknode/BUCK b/apps/openstacknode/BUCK index 0921c3d541..e614257bf6 100644 --- a/apps/openstacknode/BUCK +++ b/apps/openstacknode/BUCK @@ -1,5 +1,7 @@ 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' ], ) diff --git a/apps/openstacknode/network-cfg.json b/apps/openstacknode/network-cfg.json index 959b4b34a3..47d7b8014a 100644 --- a/apps/openstacknode/network-cfg.json +++ b/apps/openstacknode/network-cfg.json @@ -3,29 +3,36 @@ "org.onosproject.openstacknode" : { "openstacknode" : { "nodes" : [ - { - "hostname" : "compute-01", - "ovsdbIp" : "192.168.56.112", - "ovsdbPort" : "6640", - "bridgeId" : "of:0000000000000001", - "openstackNodeType" : "COMPUTENODE" - }, - { - "hostname" : "compute-02", - "ovsdbIp" : "192.168.56.106", - "ovsdbPort" : "6640", - "bridgeId" : "of:0000000000000002", - "openstackNodeType" : "COMPUTENODE" - }, - { - "hostname" : "network", - "ovsdbIp" : "192.168.56.108", - "ovsdbPort" : "6640", - "bridgeId" : "of:0000000000000003", - "openstackNodeType" : "GATEWAYNODE", - "gatewayExternalInterfaceName" : "eth1", - "gatewayExternalInterfaceMac" : "00:00:00:00:00:10" - } + { + "hostname" : "compute-01", + "type" : "COMPUTE", + "managementIp" : "10.203.25.244", + "dataIp" : "10.134.34.222", + "integrationBridge" : "of:00000000000000a1" + }, + { + "hostname" : "compute-02", + "type" : "COMPUTE", + "managementIp" : "10.203.229.42", + "dataIp" : "10.134.34.223", + "integrationBridge" : "of:00000000000000a2" + }, + { + "hostname" : "gateway-01", + "type" : "GATEWAY", + "managementIp" : "10.203.198.125", + "dataIp" : "10.134.33.208", + "integrationBridge" : "of:00000000000000a3", + "routerBridge" : "of:00000000000000b3" + }, + { + "hostname" : "gateway-02", + "type" : "GATEWAY", + "managementIp" : "10.203.198.131", + "dataIp" : "10.134.33.209", + "integrationBridge" : "of:00000000000000a4", + "routerBridge" : "of:00000000000000b4" + } ] } } diff --git a/apps/openstacknode/pom.xml b/apps/openstacknode/pom.xml index 7dfb95a08a..5524b22d35 100644 --- a/apps/openstacknode/pom.xml +++ b/apps/openstacknode/pom.xml @@ -38,7 +38,7 @@ http://onosproject.org SONA Openstack Node Bootstrap Application - org.onosproject.ovsdb + org.onosproject.ovsdb-base @@ -56,6 +56,11 @@ onos-core-serializers ${project.version} + + org.onosproject + onos-cli + ${project.version} + org.onosproject onos-ovsdb-api @@ -69,5 +74,10 @@ com.fasterxml.jackson.core jackson-annotations + + org.apache.karaf.shell + org.apache.karaf.shell.console + 3.0.5 + diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/Constants.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/Constants.java new file mode 100644 index 0000000000..b4c3f0423f --- /dev/null +++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/Constants.java @@ -0,0 +1,31 @@ +/* + * 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 diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNode.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNode.java index a285c647b3..563dea7607 100644 --- a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNode.java +++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNode.java @@ -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 routerBridge; + private final OpenstackNodeState state; public static final Comparator OPENSTACK_NODE_COMPARATOR = - (node1, node2) -> node1.hostName().compareTo(node2.hostName()); + (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"); - } + private OpenstackNode(String hostname, + NodeType type, + IpAddress managementIp, + IpAddress dataIp, + DeviceId integrationBridge, + Optional routerBridge, + OpenstackNodeState state) { + this.hostname = hostname; + this.type = type; + this.managementIp = managementIp; + this.dataIp = dataIp; + this.integrationBridge = integrationBridge; + this.routerBridge = routerBridge; + this.state = state; } /** - * Returns the OVSDB server IP address. + * Returns OpenStack node with new state. * - * @return ip address + * @param node openstack node + * @param state openstack node init state + * @return openstack node */ - public IpAddress ovsdbIp() { - return this.ovsdbIp; + 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 port number. + * Returns hostname of the node. * - * @return port number + * @return hostname */ - public TpPort ovsdbPort() { - return this.ovsdbPort; + public String hostname() { + return hostname; } /** - * Returns the hostName. + * Returns the type of the node. * - * @return hostName + * @return node type */ - public String hostName() { - return this.hostName; + public NodeType type() { + return type; } /** - * Returns the identifier of the integration bridge. + * Returns the management network IP address of the node. + * + * @return management network ip address + */ + public IpAddress managementIp() { + return managementIp; + } + + /** + * Returns the data network IP address of the node. + * + * @return data network ip address + */ + public IpAddress dataIp() { + return dataIp; + } + + /** + * Returns the integration bridge device ID. * * @return device id */ - public DeviceId intBrId() { - return this.bridgeId; + public DeviceId intBridge() { + return integrationBridge; } /** - * Returns the identifier of the OVSDB device. + * Returns the router bridge device ID. + * It returns valid value only if the node type is GATEWAY. + * + * @return device id; or empty device id + */ + public Optional routerBridge() { + return routerBridge; + } + + /** + * Returns the init state of the node. + * + * @return init state + */ + public OpenstackNodeState state() { + return state; + } + + /** + * Returns the device ID of the OVSDB session of the node. * * @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; + return DeviceId.deviceId("ovsdb:" + managementIp.toString()); } @Override @@ -153,12 +158,12 @@ public final class OpenstackNode { if (obj instanceof OpenstackNode) { OpenstackNode that = (OpenstackNode) obj; - - if (Objects.equals(hostName, that.hostName) && - Objects.equals(ovsdbIp, that.ovsdbIp) && - Objects.equals(ovsdbPort, that.ovsdbPort) && - Objects.equals(bridgeId, that.bridgeId) && - Objects.equals(openstackNodeType, that.openstackNodeType)) { + if (Objects.equals(hostname, that.hostname) && + Objects.equals(type, that.type) && + Objects.equals(managementIp, that.managementIp) && + Objects.equals(dataIp, that.dataIp) && + Objects.equals(integrationBridge, that.integrationBridge) && + Objects.equals(routerBridge, that.routerBridge)) { return true; } } @@ -167,29 +172,142 @@ public final class OpenstackNode { @Override public int hashCode() { - return Objects.hash(hostName, ovsdbIp, ovsdbPort, bridgeId, openstackNodeType); + return Objects.hash(hostname, + type, + managementIp, + dataIp, + integrationBridge, + routerBridge); } @Override public String toString() { - if (openstackNodeType == OpenstackNodeService.OpenstackNodeType.COMPUTENODE) { - return MoreObjects.toStringHelper(getClass()) - .add("host", hostName) - .add("ip", ovsdbIp) - .add("port", ovsdbPort) - .add("bridgeId", bridgeId) - .add("openstacknodetype", openstackNodeType) - .toString(); - } else { - return MoreObjects.toStringHelper(getClass()) - .add("host", hostName) - .add("ip", ovsdbIp) - .add("port", ovsdbPort) - .add("bridgeId", bridgeId) - .add("openstacknodetype", openstackNodeType) - .add("gatewayExternalInterfaceName", gatewayExternalInterfaceName) - .add("gatewayExternalInterfaceMac", gatewayExternalInterfaceMac) - .toString(); + return MoreObjects.toStringHelper(getClass()) + .add("hostname", hostname) + .add("type", type) + .add("managementIp", managementIp) + .add("dataIp", dataIp) + .add("integrationBridge", integrationBridge) + .add("routerBridge", routerBridge) + .add("state", state) + .toString(); + } + + /** + * Returns a new builder instance. + * + * @return openstack node builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder of OpenStack node entities. + */ + public static final class Builder { + private String hostname; + private NodeType type; + private IpAddress managementIp; + private IpAddress dataIp; + private DeviceId integrationBridge; + private Optional 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; } } } diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeConfig.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeConfig.java index 5d4f6b7b12..ea2d30d54b 100644 --- a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeConfig.java +++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeConfig.java @@ -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 { +public final class OpenstackNodeConfig extends Config { - protected final Logger log = getLogger(getClass()); + 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"; + @Override + public boolean isValid() { + boolean result = hasOnlyFields(NODES); - 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"; + 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 + ); + + 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 openstackNodes() { - Set nodes = Sets.newHashSet(); - JsonNode jsonNodes = object.get(NODES); - if (jsonNodes == null) { - return null; - } + for (JsonNode node : object.get(NODES)) { + NodeType type = NodeType.valueOf(get(node, TYPE)); + OpenstackNode.Builder nodeBuilder = OpenstackNode.builder() + .integrationBridge(DeviceId.deviceId(get(node, INTEGRATION_BRIDGE))) + .dataIp(IpAddress.valueOf(get(node, DATA_IP))) + .managementIp(IpAddress.valueOf(get(node, MANAGEMENT_IP))) + .type(type) + .hostname(get(node, HOST_NAME)); - jsonNodes.forEach(jsonNode -> { - try { - if (OpenstackNodeService.OpenstackNodeType.valueOf(jsonNode.path(NODE_TYPE).asText()) == - OpenstackNodeService.OpenstackNodeType.COMPUTENODE) { - nodes.add(new OpenstackNode( - jsonNode.path(HOST_NAME).asText(), - Ip4Address.valueOf(jsonNode.path(OVSDB_IP).asText()), - TpPort.tpPort(jsonNode.path(OVSDB_PORT).asInt()), - DeviceId.deviceId(jsonNode.path(BRIDGE_ID).asText()), - OpenstackNodeService.OpenstackNodeType.valueOf(jsonNode.path(NODE_TYPE).asText()), - null, MacAddress.NONE)); - } else { - nodes.add(new OpenstackNode( - jsonNode.path(HOST_NAME).asText(), - Ip4Address.valueOf(jsonNode.path(OVSDB_IP).asText()), - TpPort.tpPort(jsonNode.path(OVSDB_PORT).asInt()), - DeviceId.deviceId(jsonNode.path(BRIDGE_ID).asText()), - OpenstackNodeService.OpenstackNodeType.valueOf(jsonNode.path(NODE_TYPE).asText()), - jsonNode.path(GATEWAY_EXTERNAL_INTERFACE_NAME).asText(), - MacAddress.valueOf(jsonNode.path(GATEWAY_EXTERNAL_INTERFACE_MAC).asText()))); - } - } catch (IllegalArgumentException | NullPointerException e) { - log.error("Failed to read {}", e.toString()); + if (type.equals(GATEWAY)) { + nodeBuilder.routerBridge(DeviceId.deviceId(get(node, ROUTER_BRIDGE))); } - }); + nodes.add(nodeBuilder.build()); + } return nodes; } + + private String get(JsonNode jsonNode, String path) { + return jsonNode.get(path).asText(); + } } diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeManager.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeManager.java index a645ec5c28..19951115ce 100644 --- a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeManager.java +++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeManager.java @@ -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 CONFIG_CLASS = OpenstackNodeConfig.class; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected CoreService coreService; @@ -108,17 +121,11 @@ public class OpenstackNodeManager implements OpenstackNodeService { @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; + protected ComponentConfigService componentConfigService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected NetworkConfigRegistry configRegistry; @@ -126,378 +133,439 @@ 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( + 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 nodeStoreListener = new InternalMapListener(); + private final OvsdbHandler ovsdbHandler = new OvsdbHandler(); + private final BridgeHandler bridgeHandler = new BridgeHandler(); + + private ConsistentMap nodeStore; private ApplicationId appId; - private ConsistentMap nodeStore; private NodeId localNodeId; - private enum NodeState { + private enum NodeState implements OpenstackNodeState { INIT { @Override - public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) { - openstackNodeManager.connect(node); - } - }, - OVSDB_CONNECTED { - @Override - public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) { - if (!openstackNodeManager.getOvsdbConnectionState(node)) { - openstackNodeManager.connect(node); - } else { - openstackNodeManager.createIntegrationBridge(node); + public void process(OpenstackNodeManager nodeManager, OpenstackNode node) { + // make sure there is OVSDB connection + if (!nodeManager.isOvsdbConnected(node)) { + nodeManager.connectOvsdb(node); + return; + } + nodeManager.createBridge(node, + INTEGRATION_BRIDGE, + node.intBridge().toString().substring(DPID_BEGIN)); + + // creates additional router bridge if the node type is GATEWAY + if (node.type().equals(NodeType.GATEWAY)) { + nodeManager.createBridge(node, + ROUTER_BRIDGE, + node.routerBridge().get().toString().substring(DPID_BEGIN)); } } }, BRIDGE_CREATED { @Override - public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) { - if (!openstackNodeManager.getOvsdbConnectionState(node)) { - openstackNodeManager.connect(node); - } else { - openstackNodeManager.createTunnelInterface(node); + public void process(OpenstackNodeManager nodeManager, OpenstackNode node) { + // make sure there is OVSDB connection + if (!nodeManager.isOvsdbConnected(node)) { + nodeManager.connectOvsdb(node); + return; + } + nodeManager.createTunnelInterface(node); + // creates additional patch ports connecting integration bridge and + // router bridge if the node type is GATEWAY + if (node.type().equals(NodeType.GATEWAY)) { + nodeManager.createPatchInterface(node); } } }, COMPLETE { @Override - public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) { - openstackNodeManager.postInit(node); + public void process(OpenstackNodeManager nodeManager, OpenstackNode node) { + nodeManager.postInit(node); } }, INCOMPLETE { @Override - public void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node) { + public void process(OpenstackNodeManager nodeManager, OpenstackNode node) { } }; - public abstract void process(OpenstackNodeManager openstackNodeManager, OpenstackNode node); + public abstract void process(OpenstackNodeManager nodeManager, OpenstackNode node); } @Activate protected void activate() { - appId = coreService.registerApplication(OPENSTACK_NODEMANAGER_ID); + appId = coreService.getAppId(APP_ID); + localNodeId = clusterService.getLocalNode().id(); leadershipService.runForLeadership(appId.name()); - nodeStore = storageService.consistentMapBuilder() + nodeStore = storageService.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; + } + + log.info("Modified"); + } @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; - } - nodeStore.putIfAbsent(node, checkNodeState(node)); - NodeState state = checkNodeState(node); - state.process(this, node); + 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 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; + public List nodes() { + return nodeStore.values().stream().map(Versioned::value).collect(Collectors.toList()); } @Override - public boolean isComplete(OpenstackNode node) { - checkNotNull(node, "Node cannot be null"); + public Set completeNodes() { + return nodeStore.values().stream().map(Versioned::value) + .filter(node -> node.state().equals(NodeState.COMPLETE)) + .collect(Collectors.toSet()); + } - if (!nodeStore.containsKey(node)) { - log.warn("Node {} does not exist", node.hostName()); + @Override + public boolean isComplete(String hostname) { + Versioned 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"); + @Override + public Optional 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()); + } - 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 { + @Override + public Optional tunnelPort(DeviceId deviceId) { + return deviceService.getPorts(deviceId).stream() + .filter(p -> p.annotations().value(PORT_NAME).equals(DEFAULT_TUNNEL) && + p.isEnabled()) + .map(Port::number).findFirst(); + } + + @Override + public Optional routerBridge(DeviceId intBridgeId) { + OpenstackNode node = nodeByDeviceId(intBridgeId); + if (node == null || node.type().equals(NodeType.COMPUTE)) { + log.warn("Failed to find router bridge connected to {}", intBridgeId); + return Optional.empty(); + } + return node.routerBridge(); + } + + @Override + public Optional 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(); + } + + private void initNode(OpenstackNode node) { + NodeState state = (NodeState) node.state(); + state.process(this, node); + log.debug("Processing node: {} state: {}", node.hostname(), state); + } + + private void postInit(OpenstackNode node) { + if (isOvsdbConnected(node)) { + OvsdbNodeId ovsdb = new OvsdbNodeId(node.managementIp(), ovsdbPort); + controller.getOvsdbClient(ovsdb).disconnect(); + } + + // TODO add gateway node to scalable gateway pool + log.info("Finished init {}", node.hostname()); + } + + private void setNodeState(OpenstackNode node, NodeState newState) { + log.debug("Changed {} state: {}", node.hostname(), newState); + nodeStore.put(node.hostname(), OpenstackNode.getUpdatedNode(node, newState)); + } + + private NodeState nodeState(OpenstackNode node) { + if (!deviceService.isAvailable(node.intBridge())) { 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 (node.type().equals(NodeType.GATEWAY) && + !deviceService.isAvailable(node.routerBridge().get())) { + return NodeState.INIT; } - if (!getOvsdbConnectionState(node)) { - controller.connect(node.ovsdbIp(), node.ovsdbPort()); + if (!isIfaceCreated(node.intBridge(), DEFAULT_TUNNEL)) { + return NodeState.BRIDGE_CREATED; } + if (node.type().equals(NodeType.GATEWAY) && ( + !isIfaceCreated(node.routerBridge().get(), PATCH_ROUT_BRIDGE) || + !isIfaceCreated(node.intBridge(), PATCH_INTG_BRIDGE))) { + return NodeState.BRIDGE_CREATED; + } + + return NodeState.COMPLETE; } - /** - * Creates an integration bridge for a given node. - * - * @param node openstack node - */ - private void createIntegrationBridge(OpenstackNode node) { - if (checkIntegrationBridge(node)) { + private boolean isIfaceCreated(DeviceId deviceId, String ifaceName) { + return deviceService.getPorts(deviceId).stream() + .filter(p -> p.annotations().value(PORT_NAME).contains(ifaceName) && + p.isEnabled()) + .findAny() + .isPresent(); + } + + private void createBridge(OpenstackNode node, String bridgeName, String dpid) { + Device device = deviceService.getDevice(node.ovsdbId()); + if (device == null || !device.is(BridgeConfig.class)) { + log.error("Failed to create integration bridge on {}", node.ovsdbId()); return; } List controllers = clusterService.getNodes().stream() - .map(controller -> new ControllerInfo(controller.ip(), OFPORT, "tcp")) + .map(controller -> new ControllerInfo(controller.ip(), DEFAULT_OFPORT, "tcp")) .collect(Collectors.toList()); - String dpid = node.intBrId().toString().substring(DPID_BEGIN); BridgeDescription bridgeDesc = DefaultBridgeDescription.builder() - .name(DEFAULT_BRIDGE) + .name(bridgeName) .failMode(BridgeDescription.FailMode.SECURE) .datapathId(dpid) .disableInBand() .controllers(controllers) .build(); - try { - DriverHandler handler = driverService.createHandler(node.ovsdbId()); - BridgeConfig bridgeConfig = handler.behaviour(BridgeConfig.class); - bridgeConfig.addBridge(bridgeDesc); - } catch (ItemNotFoundException e) { - log.warn("Failed to create integration bridge on {}", node.ovsdbId()); - } + BridgeConfig bridgeConfig = device.as(BridgeConfig.class); + bridgeConfig.addBridge(bridgeDesc); } - /** - * Creates tunnel interface to the integration bridge for a given node. - * - * @param node openstack node - */ private void createTunnelInterface(OpenstackNode node) { - if (checkTunnelInterface(node)) { + Device device = deviceService.getDevice(node.ovsdbId()); + if (device == null || !device.is(InterfaceConfig.class)) { + log.error("Failed to create tunnel interface on {}", node.ovsdbId()); return; } - TunnelDescription description = DefaultTunnelDescription.builder() - .deviceId(DEFAULT_BRIDGE) + TunnelDescription tunnelDesc = DefaultTunnelDescription.builder() + .deviceId(INTEGRATION_BRIDGE) .ifaceName(DEFAULT_TUNNEL) .type(VXLAN) .remote(TunnelEndPoints.flowTunnelEndpoint()) .key(TunnelKeys.flowTunnelKey()) .build(); - try { - DriverHandler handler = driverService.createHandler(node.ovsdbId()); - InterfaceConfig ifaceConfig = handler.behaviour(InterfaceConfig.class); - ifaceConfig.addTunnelMode(DEFAULT_TUNNEL, description); - } catch (ItemNotFoundException e) { - log.warn("Failed to create tunnel interface on {}", node.ovsdbId()); - } + + InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class); + ifaceConfig.addTunnelMode(DEFAULT_TUNNEL, tunnelDesc); } - /** - * 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()); + 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; } - if (getOvsdbConnectionState(node)) { - OvsdbClientService ovsdbClient = getOvsdbClient(node); - ovsdbClient.disconnect(); + PatchDescription patchIntg = DefaultPatchDescription.builder() + .deviceId(INTEGRATION_BRIDGE) + .ifaceName(PATCH_INTG_BRIDGE) + .peer(PATCH_ROUT_BRIDGE) + .build(); + + PatchDescription patchRout = DefaultPatchDescription.builder() + .deviceId(ROUTER_BRIDGE) + .ifaceName(PATCH_ROUT_BRIDGE) + .peer(PATCH_INTG_BRIDGE) + .build(); + + InterfaceConfig ifaceConfig = device.as(InterfaceConfig.class); + ifaceConfig.addPatchMode(PATCH_INTG_BRIDGE, patchIntg); + ifaceConfig.addPatchMode(PATCH_ROUT_BRIDGE, patchRout); + } + + 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(); + } + + private void connectOvsdb(OpenstackNode node) { + controller.connect(node.managementIp(), TpPort.tpPort(ovsdbPort)); + } + + private Set systemIfaces(OpenstackNode node) { + Set ifaces = Sets.newHashSet(DEFAULT_TUNNEL); + if (node.type().equals(NodeType.GATEWAY)) { + ifaces.add(PATCH_INTG_BRIDGE); + ifaces.add(PATCH_ROUT_BRIDGE); + } + return ifaces; + } + + 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)); + + return node; + } + + private class OvsdbHandler implements ConnectionHandler { + + @Override + public void connected(Device device) { + OpenstackNode node = nodes().stream() + .filter(n -> n.ovsdbId().equals(device.id())) + .findFirst() + .orElse(null); + if (node != null) { + setNodeState(node, nodeState(node)); + } else { + log.debug("{} is detected on unregistered node, ignore it.", device.id()); + } + } + + @Override + public void disconnected(Device device) { + log.debug("Device {} is disconnected", device.id()); + } + } + + private class BridgeHandler implements ConnectionHandler { + + @Override + public void connected(Device device) { + OpenstackNode node = nodeByDeviceId(device.id()); + if (node != null) { + setNodeState(node, nodeState(node)); + } else { + log.debug("{} is detected on unregistered node, ignore it.", device.id()); + } + } + + @Override + public void disconnected(Device device) { + OpenstackNode node = nodeByDeviceId(device.id()); + if (node != null) { + log.warn("Device {} is disconnected", device.id()); + setNodeState(node, NodeState.INCOMPLETE); + } + } + + /** + * Handles port added situation. + * If the added port is tunnel or data plane interface, proceed to the remaining + * node initialization. Otherwise, do nothing. + * + * @param port port + */ + public void portAdded(Port port) { + 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; + } + + 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 or data plane interface, proceed to the remaining + * node initialization.Others, do nothing. + * + * @param port port + */ + public void portRemoved(Port port) { + OpenstackNode node = nodeByDeviceId((DeviceId) port.element().id()); + String portName = port.annotations().value(PORT_NAME); + + if (node == null) { + return; + } + + log.info("Port {} is removed from {}", portName, node.hostname()); + if (systemIfaces(node).contains(portName)) { + setNodeState(node, NodeState.INCOMPLETE); + } } } @@ -505,11 +573,10 @@ public class OpenstackNodeManager implements OpenstackNodeService { @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"); + NodeId leaderNodeId = leadershipService.getLeader(appId.name()); + if (!Objects.equals(localNodeId, leaderNodeId)) { + // do not allow to proceed without leadership return; } @@ -519,122 +586,47 @@ public class OpenstackNodeManager implements OpenstackNodeService { switch (event.type()) { case PORT_ADDED: - eventExecutor.submit(() -> bridgeHandler.portAdded(event.port())); + eventExecutor.execute(() -> bridgeHandler.portAdded(event.port())); break; case PORT_UPDATED: if (!event.port().isEnabled()) { - eventExecutor.submit(() -> bridgeHandler.portRemoved(event.port())); + eventExecutor.execute(() -> bridgeHandler.portRemoved(event.port())); } break; case DEVICE_ADDED: case DEVICE_AVAILABILITY_CHANGED: if (deviceService.isAvailable(device.id())) { - eventExecutor.submit(() -> handler.connected(device)); + eventExecutor.execute(() -> handler.connected(device)); } else { - eventExecutor.submit(() -> handler.disconnected(device)); + eventExecutor.execute(() -> 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); - + OpenstackNodeConfig config = configRegistry.getConfig(appId, CONFIG_CLASS); if (config == null) { - log.error("No configuration found"); + log.debug("No configuration found"); return; } - - config.openstackNodes().stream().forEach(node -> addNode(node)); - log.info("Node configured"); + config.openstackNodes().forEach(this::addOrUpdateNode); } private class InternalConfigListener implements NetworkConfigListener { @Override public void event(NetworkConfigEvent event) { - if (!event.configClass().equals(OpenstackNodeConfig.class)) { + NodeId leaderNodeId = leadershipService.getLeader(appId.name()); + if (!Objects.equals(localNodeId, leaderNodeId)) { + // do not allow to proceed without leadership + return; + } + + if (!event.configClass().equals(CONFIG_CLASS)) { return; } @@ -649,6 +641,46 @@ public class OpenstackNodeManager implements OpenstackNodeService { } } + private class InternalMapListener implements MapEventListener { + @Override + public void event(MapEvent 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; + } + } + } } diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeService.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeService.java index 7089f8ee1f..1987150a04 100644 --- a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeService.java +++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeService.java @@ -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 getNodes(OpenstackNodeType openstackNodeType); + List 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 */ - boolean isComplete(OpenstackNode node); + Set 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 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 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 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 + */ + Optional externalPort(DeviceId intBridgeId); } diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeState.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeState.java new file mode 100644 index 0000000000..e8fdba98bc --- /dev/null +++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/OpenstackNodeState.java @@ -0,0 +1,30 @@ +/* + * Copyright 2016-present Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.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 diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeCheckCommand.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeCheckCommand.java new file mode 100644 index 0000000000..d5cb8ed23a --- /dev/null +++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeCheckCommand.java @@ -0,0 +1,120 @@ +/* + * 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); + } + } +} diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeInitCommand.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeInitCommand.java new file mode 100644 index 0000000000..8f4dbb1ebc --- /dev/null +++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeInitCommand.java @@ -0,0 +1,57 @@ +/* + * Copyright 2016-present Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.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); + } + } +} diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeListCommand.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeListCommand.java new file mode 100644 index 0000000000..bfb1070a2e --- /dev/null +++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/OpenstackNodeListCommand.java @@ -0,0 +1,82 @@ +/* + * 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 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 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 diff --git a/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/package-info.java b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/package-info.java new file mode 100644 index 0000000000..6a6e636091 --- /dev/null +++ b/apps/openstacknode/src/main/java/org/onosproject/openstacknode/cli/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2016-present Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Console commands to manage OpenStack nodes. + */ +package org.onosproject.openstacknode.cli; \ No newline at end of file diff --git a/apps/openstacknode/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/openstacknode/src/main/resources/OSGI-INF/blueprint/shell-config.xml new file mode 100644 index 0000000000..f6bf14b3fa --- /dev/null +++ b/apps/openstacknode/src/main/resources/OSGI-INF/blueprint/shell-config.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + +