Hyunsun Moon
Committed by Jonathan Hart

Support GATEWAY type node bootstrapping

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

Change-Id: Id653f2a2c01d94036f77e6ce1b1230111f3dbbb1
COMPILE_DEPS = [
'//lib:CORE_DEPS',
'//lib:org.apache.karaf.shell.console',
'//cli:onos-cli',
'//protocols/ovsdb/api:onos-protocols-ovsdb-api',
'//protocols/ovsdb/rfc:onos-protocols-ovsdb-rfc',
'//core/store/serializers:onos-core-serializers',
......@@ -14,5 +16,5 @@ onos_app (
category = 'Utility',
url = 'http://onosproject.org',
description = 'SONA Openstack Node Bootstrap Application.',
required_app = [ 'org.onosproject.ovsdb' ],
required_app = [ 'org.onosproject.ovsdb-base' ],
)
......
......@@ -3,29 +3,36 @@
"org.onosproject.openstacknode" : {
"openstacknode" : {
"nodes" : [
{
"hostname" : "compute-01",
"ovsdbIp" : "192.168.56.112",
"ovsdbPort" : "6640",
"bridgeId" : "of:0000000000000001",
"openstackNodeType" : "COMPUTENODE"
},
{
"hostname" : "compute-02",
"ovsdbIp" : "192.168.56.106",
"ovsdbPort" : "6640",
"bridgeId" : "of:0000000000000002",
"openstackNodeType" : "COMPUTENODE"
},
{
"hostname" : "network",
"ovsdbIp" : "192.168.56.108",
"ovsdbPort" : "6640",
"bridgeId" : "of:0000000000000003",
"openstackNodeType" : "GATEWAYNODE",
"gatewayExternalInterfaceName" : "eth1",
"gatewayExternalInterfaceMac" : "00:00:00:00:00:10"
}
{
"hostname" : "compute-01",
"type" : "COMPUTE",
"managementIp" : "10.203.25.244",
"dataIp" : "10.134.34.222",
"integrationBridge" : "of:00000000000000a1"
},
{
"hostname" : "compute-02",
"type" : "COMPUTE",
"managementIp" : "10.203.229.42",
"dataIp" : "10.134.34.223",
"integrationBridge" : "of:00000000000000a2"
},
{
"hostname" : "gateway-01",
"type" : "GATEWAY",
"managementIp" : "10.203.198.125",
"dataIp" : "10.134.33.208",
"integrationBridge" : "of:00000000000000a3",
"routerBridge" : "of:00000000000000b3"
},
{
"hostname" : "gateway-02",
"type" : "GATEWAY",
"managementIp" : "10.203.198.131",
"dataIp" : "10.134.33.209",
"integrationBridge" : "of:00000000000000a4",
"routerBridge" : "of:00000000000000b4"
}
]
}
}
......
......@@ -38,7 +38,7 @@
<onos.app.url>http://onosproject.org</onos.app.url>
<onos.app.readme>SONA Openstack Node Bootstrap Application</onos.app.readme>
<onos.app.requires>
org.onosproject.ovsdb
org.onosproject.ovsdb-base
</onos.app.requires>
</properties>
......@@ -58,6 +58,11 @@
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-cli</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-ovsdb-api</artifactId>
<version>${project.version}</version>
</dependency>
......@@ -69,5 +74,10 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.apache.karaf.shell</groupId>
<artifactId>org.apache.karaf.shell.console</artifactId>
<version>3.0.5</version>
</dependency>
</dependencies>
</project>
......
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.openstacknode;
/**
* Provides constants used in OpenStack node services.
*/
public final class Constants {
private Constants() {
}
public static final String INTEGRATION_BRIDGE = "br-int";
public static final String ROUTER_BRIDGE = "br-router";
public static final String DEFAULT_TUNNEL = "vxlan";
public static final String PATCH_INTG_BRIDGE = "patch-intg";
public static final String PATCH_ROUT_BRIDGE = "patch-rout";
}
\ No newline at end of file
......@@ -17,73 +17,94 @@ package org.onosproject.openstacknode;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Sets;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.MacAddress;
import org.onlab.packet.TpPort;
import org.onlab.packet.IpAddress;
import org.onosproject.core.ApplicationId;
import org.onosproject.net.DeviceId;
import org.slf4j.Logger;
import org.onosproject.openstacknode.OpenstackNodeService.NodeType;
import java.util.Set;
import org.onosproject.net.config.Config;
import static org.slf4j.LoggerFactory.getLogger;
import static org.onosproject.net.config.Config.FieldPresence.MANDATORY;
import static org.onosproject.openstacknode.OpenstackNodeService.NodeType.GATEWAY;
/**
* Configuration object for OpensatckNode service.
*/
public class OpenstackNodeConfig extends Config<ApplicationId> {
public final class OpenstackNodeConfig extends Config<ApplicationId> {
private static final String NODES = "nodes";
private static final String HOST_NAME = "hostname";
private static final String TYPE = "type";
private static final String MANAGEMENT_IP = "managementIp";
private static final String DATA_IP = "dataIp";
private static final String INTEGRATION_BRIDGE = "integrationBridge";
private static final String ROUTER_BRIDGE = "routerBridge";
protected final Logger log = getLogger(getClass());
@Override
public boolean isValid() {
boolean result = hasOnlyFields(NODES);
if (object.get(NODES) == null || object.get(NODES).size() < 1) {
final String msg = "No node is present";
throw new IllegalArgumentException(msg);
}
for (JsonNode node : object.get(NODES)) {
ObjectNode osNode = (ObjectNode) node;
result &= hasOnlyFields(osNode,
HOST_NAME,
TYPE,
MANAGEMENT_IP,
DATA_IP,
INTEGRATION_BRIDGE,
ROUTER_BRIDGE
);
public static final String NODES = "nodes";
public static final String HOST_NAME = "hostname";
public static final String OVSDB_IP = "ovsdbIp";
public static final String OVSDB_PORT = "ovsdbPort";
public static final String BRIDGE_ID = "bridgeId";
public static final String NODE_TYPE = "openstackNodeType";
public static final String GATEWAY_EXTERNAL_INTERFACE_NAME = "gatewayExternalInterfaceName";
public static final String GATEWAY_EXTERNAL_INTERFACE_MAC = "gatewayExternalInterfaceMac";
result &= isString(osNode, HOST_NAME, MANDATORY);
result &= isString(osNode, TYPE, MANDATORY);
result &= isIpAddress(osNode, MANAGEMENT_IP, MANDATORY);
result &= result && isIpAddress(osNode, DATA_IP, MANDATORY);
result &= isString(osNode, INTEGRATION_BRIDGE, MANDATORY);
DeviceId.deviceId(osNode.get(INTEGRATION_BRIDGE).asText());
NodeType.valueOf(osNode.get(TYPE).asText());
if (osNode.get(TYPE).asText().equals(GATEWAY.name())) {
result &= isString(osNode, ROUTER_BRIDGE, MANDATORY);
DeviceId.deviceId(osNode.get(ROUTER_BRIDGE).asText());
}
}
return result;
}
/**
* Returns the set of nodes read from network config.
*
* @return set of OpensatckNodeConfig or null
* @return set of openstack nodes
*/
public Set<OpenstackNode> openstackNodes() {
Set<OpenstackNode> nodes = Sets.newHashSet();
JsonNode jsonNodes = object.get(NODES);
if (jsonNodes == null) {
return null;
}
for (JsonNode node : object.get(NODES)) {
NodeType type = NodeType.valueOf(get(node, TYPE));
OpenstackNode.Builder nodeBuilder = OpenstackNode.builder()
.integrationBridge(DeviceId.deviceId(get(node, INTEGRATION_BRIDGE)))
.dataIp(IpAddress.valueOf(get(node, DATA_IP)))
.managementIp(IpAddress.valueOf(get(node, MANAGEMENT_IP)))
.type(type)
.hostname(get(node, HOST_NAME));
jsonNodes.forEach(jsonNode -> {
try {
if (OpenstackNodeService.OpenstackNodeType.valueOf(jsonNode.path(NODE_TYPE).asText()) ==
OpenstackNodeService.OpenstackNodeType.COMPUTENODE) {
nodes.add(new OpenstackNode(
jsonNode.path(HOST_NAME).asText(),
Ip4Address.valueOf(jsonNode.path(OVSDB_IP).asText()),
TpPort.tpPort(jsonNode.path(OVSDB_PORT).asInt()),
DeviceId.deviceId(jsonNode.path(BRIDGE_ID).asText()),
OpenstackNodeService.OpenstackNodeType.valueOf(jsonNode.path(NODE_TYPE).asText()),
null, MacAddress.NONE));
} else {
nodes.add(new OpenstackNode(
jsonNode.path(HOST_NAME).asText(),
Ip4Address.valueOf(jsonNode.path(OVSDB_IP).asText()),
TpPort.tpPort(jsonNode.path(OVSDB_PORT).asInt()),
DeviceId.deviceId(jsonNode.path(BRIDGE_ID).asText()),
OpenstackNodeService.OpenstackNodeType.valueOf(jsonNode.path(NODE_TYPE).asText()),
jsonNode.path(GATEWAY_EXTERNAL_INTERFACE_NAME).asText(),
MacAddress.valueOf(jsonNode.path(GATEWAY_EXTERNAL_INTERFACE_MAC).asText())));
}
} catch (IllegalArgumentException | NullPointerException e) {
log.error("Failed to read {}", e.toString());
if (type.equals(GATEWAY)) {
nodeBuilder.routerBridge(DeviceId.deviceId(get(node, ROUTER_BRIDGE)));
}
});
nodes.add(nodeBuilder.build());
}
return nodes;
}
private String get(JsonNode jsonNode, String path) {
return jsonNode.get(path).asText();
}
}
......
......@@ -15,26 +15,33 @@
*/
package org.onosproject.openstacknode;
import org.onlab.packet.IpAddress;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import java.util.List;
import java.util.Optional;
import java.util.Set;
/**
* Handles the bootstrap request for compute/gateway node.
*/
public interface OpenstackNodeService {
public enum OpenstackNodeType {
enum NodeType {
/**
* Compute or Gateway Node.
*/
COMPUTENODE,
GATEWAYNODE
COMPUTE,
GATEWAY
}
/**
* Adds a new node to the service.
* Adds or updates a new node to the service.
*
* @param node openstack node
*/
void addNode(OpenstackNode node);
void addOrUpdateNode(OpenstackNode node);
/**
* Deletes a node from the service.
......@@ -44,18 +51,58 @@ public interface OpenstackNodeService {
void deleteNode(OpenstackNode node);
/**
* Returns nodes known to the service for designated openstacktype.
* Returns all nodes known to the service.
*
* @param openstackNodeType openstack node type
* @return list of nodes
*/
List<OpenstackNode> getNodes(OpenstackNodeType openstackNodeType);
List<OpenstackNode> nodes();
/**
* Returns the NodeState for a given node.
* Returns all nodes in complete state.
*
* @param node openstack node
* @return true if the NodeState for a given node is COMPLETE, false otherwise
* @return set of nodes
*/
Set<OpenstackNode> completeNodes();
/**
* Returns node initialization state is complete or not.
*
* @param hostname hostname of the node
* @return true if initial node setup is completed, otherwise false
*/
boolean isComplete(String hostname);
/**
* Returns data network IP address of a given integration bridge device.
*
* @param intBridgeId integration bridge device id
* @return ip address; empty value otherwise
*/
Optional<IpAddress> dataIp(DeviceId intBridgeId);
/**
* Returns tunnel port number of a given integration bridge device.
*
* @param intBridgeId integration bridge device id
* @return port number; or empty value
*/
Optional<PortNumber> tunnelPort(DeviceId intBridgeId);
/**
* Returns router bridge device ID connected to a given integration bridge.
* It returns valid value only if the node type is GATEWAY.
*
* @param intBridgeId device id of the integration bridge
* @return device id of a router bridge; or empty value
*/
Optional<DeviceId> routerBridge(DeviceId intBridgeId);
/**
* Returns port number connected to the router bridge.
* It returns valid value only if the node type is GATEWAY.
*
* @param intBridgeId integration bridge device id
* @return port number; or empty value
*/
boolean isComplete(OpenstackNode node);
Optional<PortNumber> externalPort(DeviceId intBridgeId);
}
......
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.openstacknode;
/**
* Entity that defines possible init state of the OpenStack node.
*/
public interface OpenstackNodeState {
/**
* Returns null for no state.
*
* @return null
*/
static OpenstackNodeState noState() {
return null;
}
}
\ No newline at end of file
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.openstacknode.cli;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Port;
import org.onosproject.net.Device;
import org.onosproject.net.device.DeviceService;
import org.onosproject.openstacknode.OpenstackNode;
import org.onosproject.openstacknode.OpenstackNodeService;
import static org.onosproject.net.AnnotationKeys.PORT_NAME;
import static org.onosproject.openstacknode.Constants.*;
import static org.onosproject.openstacknode.OpenstackNodeService.NodeType.GATEWAY;
/**
* Checks detailed node init state.
*/
@Command(scope = "onos", name = "openstack-node-check",
description = "Shows detailed node init state")
public class OpenstackNodeCheckCommand extends AbstractShellCommand {
@Argument(index = 0, name = "hostname", description = "Hostname",
required = true, multiValued = false)
private String hostname = null;
private static final String MSG_OK = "OK";
private static final String MSG_NO = "NO";
@Override
protected void execute() {
OpenstackNodeService nodeService = AbstractShellCommand.get(OpenstackNodeService.class);
DeviceService deviceService = AbstractShellCommand.get(DeviceService.class);
OpenstackNode node = nodeService.nodes()
.stream()
.filter(n -> n.hostname().equals(hostname))
.findFirst()
.orElse(null);
if (node == null) {
print("Cannot find %s from registered nodes", hostname);
return;
}
print("%n[Integration Bridge Status]");
Device device = deviceService.getDevice(node.intBridge());
if (device != null) {
print("%s %s=%s available=%s %s",
deviceService.isAvailable(device.id()) ? MSG_OK : MSG_NO,
INTEGRATION_BRIDGE,
device.id(),
deviceService.isAvailable(device.id()),
device.annotations());
print(getPortState(deviceService, node.intBridge(), DEFAULT_TUNNEL));
} else {
print("%s %s=%s is not available",
MSG_NO,
INTEGRATION_BRIDGE,
node.intBridge());
}
if (node.type().equals(GATEWAY)) {
print("%n[Router Bridge Status]");
device = deviceService.getDevice(node.routerBridge().get());
if (device != null) {
print("%s %s=%s available=%s %s",
deviceService.isAvailable(device.id()) ? MSG_OK : MSG_NO,
ROUTER_BRIDGE,
device.id(),
deviceService.isAvailable(device.id()),
device.annotations());
print(getPortState(deviceService, node.routerBridge().get(), PATCH_ROUT_BRIDGE));
print(getPortState(deviceService, node.intBridge(), PATCH_INTG_BRIDGE));
} else {
print("%s %s=%s is not available",
MSG_NO,
ROUTER_BRIDGE,
node.intBridge());
}
}
}
private String getPortState(DeviceService deviceService, DeviceId deviceId, String portName) {
Port port = deviceService.getPorts(deviceId).stream()
.filter(p -> p.annotations().value(PORT_NAME).equals(portName) &&
p.isEnabled())
.findAny().orElse(null);
if (port != null) {
return String.format("%s %s portNum=%s enabled=%s %s",
port.isEnabled() ? MSG_OK : MSG_NO,
portName,
port.number(),
port.isEnabled() ? Boolean.TRUE : Boolean.FALSE,
port.annotations());
} else {
return String.format("%s %s does not exist", MSG_NO, portName);
}
}
}
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.openstacknode.cli;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.openstacknode.OpenstackNode;
import org.onosproject.openstacknode.OpenstackNodeService;
import java.util.NoSuchElementException;
/**
* Initializes nodes for OpenStack node service.
*/
@Command(scope = "onos", name = "openstack-node-init",
description = "Initializes nodes for OpenStack node service")
public class OpenstackNodeInitCommand extends AbstractShellCommand {
@Argument(index = 0, name = "hostnames", description = "Hostname(s)",
required = true, multiValued = true)
private String[] hostnames = null;
@Override
protected void execute() {
OpenstackNodeService nodeService = AbstractShellCommand.get(OpenstackNodeService.class);
for (String hostname : hostnames) {
OpenstackNode node;
try {
node = nodeService.nodes()
.stream()
.filter(n -> n.hostname().equals(hostname))
.findFirst().get();
} catch (NoSuchElementException e) {
print("Unable to find %s", hostname);
continue;
}
nodeService.addOrUpdateNode(node);
}
}
}
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.openstacknode.cli;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.openstacknode.OpenstackNode;
import org.onosproject.openstacknode.OpenstackNodeService;
import java.util.Collections;
import java.util.List;
/**
* Lists all nodes registered to the service.
*/
@Command(scope = "onos", name = "openstack-nodes",
description = "Lists all nodes registered in OpenStack node service")
public class OpenstackNodeListCommand extends AbstractShellCommand {
private static final String COMPLETE = "COMPLETE";
private static final String INCOMPLETE = "INCOMPLETE";
@Override
protected void execute() {
OpenstackNodeService nodeService = AbstractShellCommand.get(OpenstackNodeService.class);
List<OpenstackNode> nodes = nodeService.nodes();
Collections.sort(nodes, OpenstackNode.OPENSTACK_NODE_COMPARATOR);
if (outputJson()) {
print("%s", json(nodeService, nodes));
} else {
for (OpenstackNode node : nodes) {
print("hostname=%s, type=%s, managementIp=%s, dataIp=%s, intBridge=%s, routerBridge=%s init=%s",
node.hostname(),
node.type(),
node.managementIp(),
node.dataIp(),
node.intBridge(),
node.routerBridge(),
getState(nodeService, node));
}
print("Total %s nodes", nodeService.nodes().size());
}
}
private JsonNode json(OpenstackNodeService nodeService, List<OpenstackNode> nodes) {
ObjectMapper mapper = new ObjectMapper();
ArrayNode result = mapper.createArrayNode();
for (OpenstackNode node : nodes) {
result.add(mapper.createObjectNode()
.put("hostname", node.hostname())
.put("type", node.type().name())
.put("managementIp", node.managementIp().toString())
.put("dataIp", node.dataIp().toString())
.put("intBridge", node.intBridge().toString())
.put("routerBridge", node.routerBridge().toString())
.put("state", getState(nodeService, node)));
}
return result;
}
private String getState(OpenstackNodeService nodeService, OpenstackNode node) {
return nodeService.isComplete(node.hostname()) ? COMPLETE : INCOMPLETE;
}
}
\ No newline at end of file
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Console commands to manage OpenStack nodes.
*/
package org.onosproject.openstacknode.cli;
\ No newline at end of file
<!--
~ Copyright 2016-present Open Networking Laboratory
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
<command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
<command>
<action class="org.onosproject.openstacknode.cli.OpenstackNodeListCommand"/>
</command>
<command>
<action class="org.onosproject.openstacknode.cli.OpenstackNodeCheckCommand"/>
</command>
<command>
<action class="org.onosproject.openstacknode.cli.OpenstackNodeInitCommand"/>
</command>
</command-bundle>
</blueprint>