CORD-633 Make controllers of the br-int configurable
Change-Id: Ie7b17d2be9d46dae201de0092183b5301ef899b3
diff --git a/src/main/java/org/opencord/cordvtn/api/Constants.java b/src/main/java/org/opencord/cordvtn/api/Constants.java
index 9fd3b2c..d6d2831 100644
--- a/src/main/java/org/opencord/cordvtn/api/Constants.java
+++ b/src/main/java/org/opencord/cordvtn/api/Constants.java
@@ -16,7 +16,6 @@
package org.opencord.cordvtn.api;
import org.onlab.packet.Ip4Address;
-import org.onlab.packet.TpPort;
/**
* Provides constants used in CORD VTN services.
@@ -28,8 +27,6 @@
public static final String CORDVTN_APP_ID = "org.opencord.vtn";
- public static final String ERROR_XOS_ACCESS = "XOS access is not configured";
- public static final String ERROR_OPENSTACK_ACCESS = "OpenStack access is not configured";
public static final String MSG_OK = "OK";
public static final String MSG_NO = "NO";
@@ -37,8 +34,9 @@
public static final String INTEGRATION_BRIDGE = "br-int";
public static final String NOT_APPLICABLE = "N/A";
- public static final int OF_PORT = 6653;
- public static final TpPort OVSDB_PORT = TpPort.tpPort(6640);
+ public static final String DEFAULT_OF_PROTOCOL = "tcp";
+ public static final int DEFAULT_OF_PORT = 6653;
+ public static final int DEFAULT_OVSDB_PORT = 6640;
public static final Ip4Address DEFAULT_DNS = Ip4Address.valueOf("8.8.8.8");
public static final int DHCP_INFINITE_LEASE = -1;
}
diff --git a/src/main/java/org/opencord/cordvtn/api/config/CordVtnConfig.java b/src/main/java/org/opencord/cordvtn/api/config/CordVtnConfig.java
index 3f2b094..7f5a102 100644
--- a/src/main/java/org/opencord/cordvtn/api/config/CordVtnConfig.java
+++ b/src/main/java/org/opencord/cordvtn/api/config/CordVtnConfig.java
@@ -18,23 +18,33 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
+import org.onlab.osgi.DefaultServiceDirectory;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onlab.packet.TpPort;
+import org.onosproject.cluster.ClusterService;
import org.onosproject.core.ApplicationId;
+import org.onosproject.net.behaviour.ControllerInfo;
import org.onosproject.net.config.Config;
+import org.onosproject.net.config.InvalidFieldException;
import org.opencord.cordvtn.api.node.CordVtnNode;
import org.opencord.cordvtn.api.node.NetworkAddress;
import org.opencord.cordvtn.api.node.SshAccessInfo;
import org.slf4j.Logger;
+import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
import static org.onosproject.net.config.Config.FieldPresence.MANDATORY;
import static org.onosproject.net.config.Config.FieldPresence.OPTIONAL;
+import static org.opencord.cordvtn.api.Constants.DEFAULT_OF_PORT;
+import static org.opencord.cordvtn.api.Constants.DEFAULT_OF_PROTOCOL;
import static org.slf4j.LoggerFactory.getLogger;
/**
@@ -66,13 +76,18 @@
private static final String OPENSTACK = "openstack";
private static final String XOS = "xos";
-
private static final String ENDPOINT = "endpoint";
private static final String TENANT = "tenant";
private static final String USER = "user";
private static final String PASSWORD = "password";
- // TODO implement isValid
+ private static final String CONTROLLERS = "controllers";
+ private static final int INDEX_IP = 0;
+ private static final int INDEX_PORT = 1;
+
+ private final ClusterService clusterService =
+ DefaultServiceDirectory.getService(ClusterService.class);
+
@Override
public boolean isValid() {
// check only allowed fields are present
@@ -84,7 +99,8 @@
SSH,
OPENSTACK,
XOS,
- CORDVTN_NODES);
+ CORDVTN_NODES,
+ CONTROLLERS);
if (object.get(CORDVTN_NODES) == null || object.get(CORDVTN_NODES).size() < 1) {
final String msg = "No node is present";
@@ -92,20 +108,20 @@
}
// check all mandatory fields are present and valid
- result = result && isMacAddress(PRIVATE_GATEWAY_MAC, MANDATORY);
- result = result && isIpPrefix(LOCAL_MANAGEMENT_IP, MANDATORY);
+ result &= isMacAddress(PRIVATE_GATEWAY_MAC, MANDATORY);
+ result &= isIpPrefix(LOCAL_MANAGEMENT_IP, MANDATORY);
for (JsonNode node : object.get(CORDVTN_NODES)) {
ObjectNode vtnNode = (ObjectNode) node;
- result = result && hasFields(
+ result &= hasFields(
vtnNode,
HOSTNAME,
HOST_MANAGEMENT_IP,
DATA_IP,
DATA_IFACE,
INTEGRATION_BRIDGE_ID);
- result = result && isIpPrefix(vtnNode, HOST_MANAGEMENT_IP, MANDATORY);
- result = result && isIpPrefix(vtnNode, DATA_IP, MANDATORY);
+ result &= isIpPrefix(vtnNode, HOST_MANAGEMENT_IP, MANDATORY);
+ result &= isIpPrefix(vtnNode, DATA_IP, MANDATORY);
NetworkAddress localMgmt = NetworkAddress.valueOf(get(LOCAL_MANAGEMENT_IP, ""));
NetworkAddress hostsMgmt = NetworkAddress.valueOf(getConfig(vtnNode, HOST_MANAGEMENT_IP));
@@ -116,42 +132,74 @@
}
}
- result = result && hasFields(
+ result &= hasFields(
(ObjectNode) object.get(SSH),
SSH_PORT,
SSH_USER,
SSH_KEY_FILE);
- result = result && isTpPort(
+ result &= isTpPort(
(ObjectNode) object.get(SSH),
SSH_PORT,
MANDATORY);
- result = result && hasFields(
+ result &= hasFields(
(ObjectNode) object.get(OPENSTACK),
ENDPOINT,
TENANT,
USER,
PASSWORD);
- result = result && hasFields(
+ result &= hasFields(
(ObjectNode) object.get(XOS),
ENDPOINT,
USER,
PASSWORD);
// check all optional fields are valid
- result = result && isTpPort(OVSDB_PORT, OPTIONAL);
+ result &= isTpPort(OVSDB_PORT, OPTIONAL);
if (object.get(PUBLIC_GATEWAYS) != null && object.get(PUBLIC_GATEWAYS).isArray()) {
for (JsonNode node : object.get(PUBLIC_GATEWAYS)) {
ObjectNode gateway = (ObjectNode) node;
- result = result && isIpAddress(gateway, GATEWAY_IP, MANDATORY);
- result = result && isMacAddress(gateway, GATEWAY_MAC, MANDATORY);
+ result &= isIpAddress(gateway, GATEWAY_IP, MANDATORY);
+ result &= isMacAddress(gateway, GATEWAY_MAC, MANDATORY);
+ }
+ }
+
+ if (object.get(CONTROLLERS) != null) {
+ for (JsonNode jsonNode : object.get(CONTROLLERS)) {
+ result &= isController(jsonNode);
}
}
return result;
}
+ private boolean isController(JsonNode jsonNode) {
+ String[] ctrl = jsonNode.asText().split(":");
+ final String error = "Malformed controller string " + jsonNode.asText() +
+ ". Controller only takes a list of 'IP:port', 'IP', " +
+ "or just one ':port'.";
+ try {
+ if (ctrl.length == 1) {
+ IpAddress.valueOf(ctrl[INDEX_IP]);
+ return true;
+ }
+ if (ctrl.length == 2 && ctrl[INDEX_IP].isEmpty() &&
+ object.get(CONTROLLERS).size() == 1) {
+ TpPort.tpPort(Integer.valueOf(ctrl[INDEX_PORT]));
+ return true;
+ }
+ if (ctrl.length == 2 && !ctrl[INDEX_IP].isEmpty()) {
+ IpAddress.valueOf(ctrl[INDEX_IP]);
+ TpPort.tpPort(Integer.valueOf(ctrl[INDEX_PORT]));
+ return true;
+ }
+ throw new InvalidFieldException(CONTROLLERS, error);
+ } catch (IllegalArgumentException e) {
+ throw new InvalidFieldException(CONTROLLERS, error);
+ }
+ }
+
/**
* Returns the set of nodes read from network config.
*
@@ -261,4 +309,50 @@
jsonNode.path(USER).asText(),
jsonNode.path(PASSWORD).asText());
}
+
+ /**
+ * Returns controllers for the integration bridge.
+ * It returns the information taken from cluster service with the default OF
+ * port if no controller is specified in the network config.
+ *
+ * @return list of controller information
+ */
+ public List<ControllerInfo> controllers() {
+ List<ControllerInfo> ctrls = Lists.newArrayList();
+ JsonNode ctrlNodes = object.get(CONTROLLERS);
+
+ if (ctrlNodes == null || isCtrlPortOnly()) {
+ ctrls = clusterService.getNodes().stream()
+ .map(ctrl -> new ControllerInfo(
+ ctrl.ip(),
+ ctrlNodes == null ? DEFAULT_OF_PORT : getCtrlPort(),
+ DEFAULT_OF_PROTOCOL))
+ .collect(Collectors.toList());
+ } else {
+ for (JsonNode ctrlNode : ctrlNodes) {
+ String[] ctrl = ctrlNode.asText().split(":");
+ ctrls.add(new ControllerInfo(
+ IpAddress.valueOf(ctrl[INDEX_IP]),
+ ctrl.length == 1 ? DEFAULT_OF_PORT :
+ Integer.parseInt(ctrl[INDEX_PORT]),
+ DEFAULT_OF_PROTOCOL));
+ }
+ }
+ return ImmutableList.copyOf(ctrls);
+ }
+
+ private boolean isCtrlPortOnly() {
+ if (object.get(CONTROLLERS).size() != 1) {
+ return false;
+ }
+ JsonNode jsonNode = object.get(CONTROLLERS).get(0);
+ String[] ctrl = jsonNode.asText().split(":");
+ return ctrl.length == 2 && ctrl[INDEX_IP].isEmpty();
+ }
+
+ private int getCtrlPort() {
+ JsonNode jsonNode = object.get(CONTROLLERS).get(0);
+ String[] ctrl = jsonNode.asText().split(":");
+ return Integer.parseInt(ctrl[INDEX_PORT]);
+ }
}
diff --git a/src/main/java/org/opencord/cordvtn/api/node/CordVtnNode.java b/src/main/java/org/opencord/cordvtn/api/node/CordVtnNode.java
index 2c3916c..d4339df 100644
--- a/src/main/java/org/opencord/cordvtn/api/node/CordVtnNode.java
+++ b/src/main/java/org/opencord/cordvtn/api/node/CordVtnNode.java
@@ -28,8 +28,8 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
+import static org.opencord.cordvtn.api.Constants.DEFAULT_OVSDB_PORT;
import static org.opencord.cordvtn.api.Constants.DEFAULT_TUNNEL;
-import static org.opencord.cordvtn.api.Constants.OVSDB_PORT;
/**
* Representation of a compute infrastructure node for CORD VTN service.
@@ -149,7 +149,7 @@
if (this.ovsdbPort.isPresent()) {
return this.ovsdbPort.get();
} else {
- return OVSDB_PORT;
+ return TpPort.tpPort(DEFAULT_OVSDB_PORT);
}
}
@@ -289,7 +289,8 @@
private NetworkAddress hostMgmtIp;
private NetworkAddress localMgmtIp;
private NetworkAddress dataIp;
- private Optional<TpPort> ovsdbPort = Optional.of(OVSDB_PORT);
+ private Optional<TpPort> ovsdbPort =
+ Optional.of(TpPort.tpPort(DEFAULT_OVSDB_PORT));
private SshAccessInfo sshInfo;
private DeviceId integrationBridgeId;
private String dataIface;
diff --git a/src/main/java/org/opencord/cordvtn/impl/CordVtnNodeManager.java b/src/main/java/org/opencord/cordvtn/impl/CordVtnNodeManager.java
index 78cc228..c2a89b9 100644
--- a/src/main/java/org/opencord/cordvtn/impl/CordVtnNodeManager.java
+++ b/src/main/java/org/opencord/cordvtn/impl/CordVtnNodeManager.java
@@ -16,6 +16,7 @@
package org.opencord.cordvtn.impl;
import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
import com.jcraft.jsch.Session;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
@@ -170,6 +171,7 @@
private final BridgeHandler bridgeHandler = new BridgeHandler();
private ConsistentMap<String, CordVtnNode> nodeStore;
+ private List<ControllerInfo> controllers = Lists.newArrayList();
private ApplicationId appId;
private NodeId localNodeId;
@@ -226,10 +228,9 @@
@Activate
protected void activate() {
appId = coreService.registerApplication(CORDVTN_APP_ID);
-
- configRegistry.registerConfigFactory(configFactory);
- localNodeId = clusterService.getLocalNode().id();
leadershipService.runForLeadership(appId.name());
+ localNodeId = clusterService.getLocalNode().id();
+ configRegistry.registerConfigFactory(configFactory);
nodeStore = storageService.<String, CordVtnNode>consistentMapBuilder()
.withSerializer(Serializer.using(NODE_SERIALIZER.build()))
@@ -241,6 +242,8 @@
deviceService.addListener(deviceListener);
configService.addListener(configListener);
+ // TODO read nodes as well after more tests
+ readControllers();
log.info("Started");
}
@@ -329,7 +332,7 @@
public PortNumber dataPort(DeviceId deviceId) {
CordVtnNode node = nodeByBridgeId(deviceId);
if (node == null) {
- log.warn("Failed to get node for {}", deviceId);
+ log.debug("Failed to get node for {}", deviceId);
return null;
}
@@ -346,7 +349,7 @@
public IpAddress dataIp(DeviceId deviceId) {
CordVtnNode node = nodeByBridgeId(deviceId);
if (node == null) {
- log.warn("Failed to get node for {}", deviceId);
+ log.debug("Failed to get node for {}", deviceId);
return null;
}
return node.dataIp().ip();
@@ -372,7 +375,7 @@
public PortNumber hostManagementPort(DeviceId deviceId) {
CordVtnNode node = nodeByBridgeId(deviceId);
if (node == null) {
- log.warn("Failed to get node for {}", deviceId);
+ log.debug("Failed to get node for {}", deviceId);
return null;
}
@@ -540,10 +543,6 @@
return;
}
- List<ControllerInfo> controllers = clusterService.getNodes().stream()
- .map(controller -> new ControllerInfo(controller.ip(), OF_PORT, "tcp"))
- .collect(Collectors.toList());
-
String dpid = node.integrationBridgeId().toString().substring(DPID_BEGIN);
BridgeDescription bridgeDesc = DefaultBridgeDescription.builder()
.name(INTEGRATION_BRIDGE)
@@ -844,7 +843,13 @@
/**
* Reads cordvtn nodes from config file.
*/
- private void readConfiguration() {
+ private void readNodes() {
+ NodeId leaderNodeId = leadershipService.getLeader(appId.name());
+ if (!Objects.equals(localNodeId, leaderNodeId)) {
+ // do not allow to proceed without leadership
+ return;
+ }
+
CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
if (config == null) {
log.debug("No configuration found");
@@ -853,16 +858,22 @@
config.cordVtnNodes().forEach(this::addOrUpdateNode);
}
+ private void readControllers() {
+ CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
+ if (config == null) {
+ log.debug("No configuration found");
+ return;
+ }
+ controllers = config.controllers();
+ controllers.stream().forEach(ctrl -> {
+ log.debug("Added controller {}:{}", ctrl.ip(), ctrl.port());
+ });
+ }
+
private class InternalConfigListener implements NetworkConfigListener {
@Override
public void event(NetworkConfigEvent event) {
- NodeId leaderNodeId = leadershipService.getLeader(appId.name());
- if (!Objects.equals(localNodeId, leaderNodeId)) {
- // do not allow to proceed without leadership
- return;
- }
-
if (!event.configClass().equals(CordVtnConfig.class)) {
return;
}
@@ -870,7 +881,10 @@
switch (event.type()) {
case CONFIG_ADDED:
case CONFIG_UPDATED:
- eventExecutor.execute(CordVtnNodeManager.this::readConfiguration);
+ eventExecutor.execute(() -> {
+ readControllers();
+ readNodes();
+ });
break;
default:
break;