Added ability to form a cluster via REST API.
Change-Id: Ib71f6b4caed1b1c4b9db78596ee35bf5cab05184
diff --git a/cli/src/main/java/org/onosproject/cli/NodeAddCommand.java b/cli/src/main/java/org/onosproject/cli/NodeAddCommand.java
index 8c4cdc7..170e233 100644
--- a/cli/src/main/java/org/onosproject/cli/NodeAddCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/NodeAddCommand.java
@@ -18,6 +18,7 @@
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cluster.ClusterAdminService;
+import org.onosproject.cluster.DefaultControllerNode;
import org.onosproject.cluster.NodeId;
import org.onlab.packet.IpAddress;
@@ -38,7 +39,7 @@
@Argument(index = 2, name = "tcpPort", description = "Node TCP listen port",
required = false, multiValued = false)
- int tcpPort = 9876;
+ int tcpPort = DefaultControllerNode.DEFAULT_PORT;
@Override
protected void execute() {
diff --git a/core/api/src/main/java/org/onosproject/cluster/ClusterAdminService.java b/core/api/src/main/java/org/onosproject/cluster/ClusterAdminService.java
index e567540..c3eb3c1 100644
--- a/core/api/src/main/java/org/onosproject/cluster/ClusterAdminService.java
+++ b/core/api/src/main/java/org/onosproject/cluster/ClusterAdminService.java
@@ -17,12 +17,24 @@
import org.onlab.packet.IpAddress;
+import java.util.Set;
+
/**
* Service for administering the cluster node membership.
*/
public interface ClusterAdminService {
/**
+ * Forms cluster configuration based on the specified set of node
+ * information. This method resets and restarts the controller
+ * instance.
+ *
+ * @param nodes set of nodes that form the cluster
+ * @param ipPrefix IP address prefix, e.g. 10.0.1.*
+ */
+ void formCluster(Set<ControllerNode> nodes, String ipPrefix);
+
+ /**
* Adds a new controller node to the cluster.
*
* @param nodeId controller node identifier
diff --git a/core/api/src/main/java/org/onosproject/cluster/ClusterStore.java b/core/api/src/main/java/org/onosproject/cluster/ClusterStore.java
index 4229e6a..cdb03b2 100644
--- a/core/api/src/main/java/org/onosproject/cluster/ClusterStore.java
+++ b/core/api/src/main/java/org/onosproject/cluster/ClusterStore.java
@@ -15,12 +15,12 @@
*/
package org.onosproject.cluster;
-import java.util.Set;
-
import org.joda.time.DateTime;
import org.onlab.packet.IpAddress;
import org.onosproject.store.Store;
+import java.util.Set;
+
/**
* Manages inventory of controller cluster nodes; not intended for direct use.
*/
@@ -65,6 +65,16 @@
DateTime getLastUpdated(NodeId nodeId);
/**
+ * Forms cluster configuration based on the specified set of node
+ * information. Assumes subsequent restart for the new configuration to
+ * take hold.
+ *
+ * @param nodes set of nodes that form the cluster
+ * @param ipPrefix IP address prefix, e.g. 10.0.1.*
+ */
+ void formCluster(Set<ControllerNode> nodes, String ipPrefix);
+
+ /**
* Adds a new controller node to the cluster.
*
* @param nodeId controller node identifier
diff --git a/core/api/src/main/java/org/onosproject/cluster/DefaultControllerNode.java b/core/api/src/main/java/org/onosproject/cluster/DefaultControllerNode.java
index 31185dd..7d701d9 100644
--- a/core/api/src/main/java/org/onosproject/cluster/DefaultControllerNode.java
+++ b/core/api/src/main/java/org/onosproject/cluster/DefaultControllerNode.java
@@ -26,7 +26,7 @@
*/
public class DefaultControllerNode implements ControllerNode {
- private static final int DEFAULT_PORT = 9876;
+ public static final int DEFAULT_PORT = 9876;
private final NodeId id;
private final IpAddress ip;
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java b/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
index 4abb5e6..1f9a20a 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
@@ -15,15 +15,13 @@
*/
package org.onosproject.codec.impl;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
+import com.google.common.collect.ImmutableSet;
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.Service;
import org.onlab.packet.Ethernet;
+import org.onosproject.cluster.ControllerNode;
import org.onosproject.codec.CodecService;
import org.onosproject.codec.JsonCodec;
import org.onosproject.core.Application;
@@ -50,7 +48,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.common.collect.ImmutableSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
/**
* Implementation of the JSON codec brokering service.
@@ -67,6 +67,7 @@
public void activate() {
codecs.clear();
registerCodec(Application.class, new ApplicationCodec());
+ registerCodec(ControllerNode.class, new ControllerNodeCodec());
registerCodec(Annotations.class, new AnnotationsCodec());
registerCodec(Device.class, new DeviceCodec());
registerCodec(Port.class, new PortCodec());
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/ControllerNodeCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/ControllerNodeCodec.java
new file mode 100644
index 0000000..4b06ff0
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/ControllerNodeCodec.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2015 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.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.DefaultControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.cluster.DefaultControllerNode.DEFAULT_PORT;
+
+/**
+ * Device JSON codec.
+ */
+public final class ControllerNodeCodec extends JsonCodec<ControllerNode> {
+
+ @Override
+ public ObjectNode encode(ControllerNode node, CodecContext context) {
+ checkNotNull(node, "Controller node cannot be null");
+ ClusterService service = context.get(ClusterService.class);
+ return context.mapper().createObjectNode()
+ .put("id", node.id().toString())
+ .put("ip", node.ip().toString())
+ .put("tcpPort", node.tcpPort())
+ .put("status", service.getState(node.id()).toString());
+ }
+
+
+ @Override
+ public ControllerNode decode(ObjectNode json, CodecContext context) {
+ checkNotNull(json, "JSON cannot be null");
+ String ip = json.path("ip").asText();
+ return new DefaultControllerNode(new NodeId(json.path("id").asText(ip)),
+ IpAddress.valueOf(ip),
+ json.path("tcpPort").asInt(DEFAULT_PORT));
+ }
+
+
+}
diff --git a/core/net/pom.xml b/core/net/pom.xml
index c47f91d..d8aecf9 100644
--- a/core/net/pom.xml
+++ b/core/net/pom.xml
@@ -82,6 +82,11 @@
<groupId>org.apache.karaf.features</groupId>
<artifactId>org.apache.karaf.features.core</artifactId>
</dependency>
+
+ <dependency>
+ <groupId>org.apache.karaf.system</groupId>
+ <artifactId>org.apache.karaf.system.core</artifactId>
+ </dependency>
</dependencies>
<build>
diff --git a/core/net/src/main/java/org/onosproject/cluster/impl/ClusterManager.java b/core/net/src/main/java/org/onosproject/cluster/impl/ClusterManager.java
index d9da6c6..3f7e460 100644
--- a/core/net/src/main/java/org/onosproject/cluster/impl/ClusterManager.java
+++ b/core/net/src/main/java/org/onosproject/cluster/impl/ClusterManager.java
@@ -15,18 +15,13 @@
*/
package org.onosproject.cluster.impl;
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static org.slf4j.LoggerFactory.getLogger;
-
-import java.util.Set;
-
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
+import org.apache.karaf.system.SystemService;
import org.joda.time.DateTime;
import org.onlab.packet.IpAddress;
import org.onosproject.cluster.ClusterAdminService;
@@ -41,6 +36,12 @@
import org.onosproject.event.EventDeliveryService;
import org.slf4j.Logger;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
/**
* Implementation of the cluster service.
*/
@@ -62,6 +63,9 @@
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected EventDeliveryService eventDispatcher;
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected SystemService systemService;
+
@Activate
public void activate() {
store.setDelegate(delegate);
@@ -105,6 +109,20 @@
}
@Override
+ public void formCluster(Set<ControllerNode> nodes, String ipPrefix) {
+ checkNotNull(nodes, "Nodes cannot be null");
+ checkArgument(!nodes.isEmpty(), "Nodes cannot be empty");
+ checkNotNull(ipPrefix, "IP prefix cannot be null");
+ store.formCluster(nodes, ipPrefix);
+ try {
+ log.warn("Shutting down container for cluster reconfiguration!");
+ systemService.reboot("now", SystemService.Swipe.NONE);
+ } catch (Exception e) {
+ log.error("Unable to reboot container", e);
+ }
+ }
+
+ @Override
public ControllerNode addNode(NodeId nodeId, IpAddress ip, int tcpPort) {
checkNotNull(nodeId, INSTANCE_ID_NULL);
checkNotNull(ip, "IP address cannot be null");
diff --git a/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinitionStore.java b/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinitionStore.java
index a522a85..e3c521e 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinitionStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/ClusterDefinitionStore.java
@@ -18,6 +18,8 @@
import static com.google.common.base.Preconditions.checkNotNull;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.io.Files;
+
import java.io.File;
import java.io.IOException;
@@ -43,8 +45,7 @@
*/
public ClusterDefinition read() throws IOException {
ObjectMapper mapper = new ObjectMapper();
- ClusterDefinition definition = mapper.readValue(file, ClusterDefinition.class);
- return definition;
+ return mapper.readValue(file, ClusterDefinition.class);
}
/**
@@ -55,7 +56,8 @@
public void write(ClusterDefinition definition) throws IOException {
checkNotNull(definition);
// write back to file
- final ObjectMapper mapper = new ObjectMapper();
+ Files.createParentDirs(file);
+ ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(file, definition);
}
}
\ No newline at end of file
diff --git a/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/DistributedClusterStore.java b/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/DistributedClusterStore.java
index 0472cff..f5bd773 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/DistributedClusterStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/DistributedClusterStore.java
@@ -17,6 +17,7 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
import com.hazelcast.util.AddressUtil;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
@@ -37,6 +38,8 @@
import org.onosproject.cluster.DefaultControllerNode;
import org.onosproject.cluster.NodeId;
import org.onosproject.store.AbstractStore;
+import org.onosproject.store.consistent.impl.DatabaseDefinition;
+import org.onosproject.store.consistent.impl.DatabaseDefinitionStore;
import org.onosproject.store.serializers.KryoNamespaces;
import org.onosproject.store.serializers.KryoSerializer;
import org.slf4j.Logger;
@@ -55,11 +58,13 @@
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
-import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.hazelcast.util.AddressUtil.matchInterface;
import static java.net.NetworkInterface.getNetworkInterfaces;
import static java.util.Collections.list;
import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.cluster.DefaultControllerNode.DEFAULT_PORT;
+import static org.onosproject.store.consistent.impl.DatabaseManager.PARTITION_DEFINITION_FILE;
import static org.slf4j.LoggerFactory.getLogger;
@Component(immediate = true)
@@ -74,17 +79,14 @@
private static final Logger log = getLogger(DistributedClusterStore.class);
+ public static final String CLUSTER_DEFINITION_FILE = "../config/cluster.json";
+ public static final String HEARTBEAT_MESSAGE = "onos-cluster-heartbeat";
+
// TODO: make these configurable.
private static final int HEARTBEAT_FD_PORT = 2419;
private static final int HEARTBEAT_INTERVAL_MS = 100;
private static final int PHI_FAILURE_THRESHOLD = 10;
- private static final String CONFIG_DIR = "../config";
- private static final String CLUSTER_DEFINITION_FILE = "cluster.json";
- private static final String HEARTBEAT_MESSAGE = "onos-cluster-heartbeat";
-
- public static final int DEFAULT_PORT = 9876;
-
private static final KryoSerializer SERIALIZER = new KryoSerializer() {
@Override
protected void setupKryoPool() {
@@ -97,6 +99,8 @@
};
private static final String INSTANCE_ID_NULL = "Instance ID cannot be null";
+ private static final byte SITE_LOCAL_BYTE = (byte) 0xC0;
+ private static final String ONOS_NIC = "ONOS_NIC";
private ClusterDefinition clusterDefinition;
@@ -116,7 +120,7 @@
@Activate
public void activate() {
- File clusterDefinitionFile = new File(CONFIG_DIR, CLUSTER_DEFINITION_FILE);
+ File clusterDefinitionFile = new File(CLUSTER_DEFINITION_FILE);
ClusterDefinitionStore clusterDefinitionStore =
new ClusterDefinitionStore(clusterDefinitionFile.getPath());
@@ -129,13 +133,12 @@
seedNodes = ImmutableSet
.copyOf(clusterDefinition.getNodes())
.stream()
- .map(nodeInfo -> new DefaultControllerNode(new NodeId(nodeInfo.getId()),
- IpAddress.valueOf(nodeInfo.getIp()),
- nodeInfo.getTcpPort()))
+ .map(n -> new DefaultControllerNode(new NodeId(n.getId()),
+ IpAddress.valueOf(n.getIp()),
+ n.getTcpPort()))
.collect(Collectors.toSet());
} catch (IOException e) {
- throw new IllegalStateException(
- "Failed to read cluster definition.", e);
+ throw new IllegalStateException("Failed to read cluster definition.", e);
}
seedNodes.forEach(node -> {
@@ -179,26 +182,30 @@
}
/**
- * Returns the site local address if one can be found, loopback otherwise.
+ * Returns the address that matches the IP prefix given in ONOS_NIC
+ * environment variable if one was specified, or the first site local
+ * address if one can be found or the loopback address otherwise.
*
* @return site-local address in string form
*/
public static String getSiteLocalAddress() {
try {
+ String ipPrefix = System.getenv(ONOS_NIC);
for (NetworkInterface nif : list(getNetworkInterfaces())) {
for (InetAddress address : list(nif.getInetAddresses())) {
- if (address.getAddress()[0] == (byte) 0xC0) {
- return address.toString().substring(1);
+ IpAddress ip = IpAddress.valueOf(address);
+ if (ipPrefix == null && address.isSiteLocalAddress() ||
+ ipPrefix != null && matchInterface(ip.toString(), ipPrefix)) {
+ return ip.toString();
}
}
}
- return InetAddress.getLoopbackAddress().toString().substring(1);
} catch (SocketException e) {
log.error("Unable to get network interfaces", e);
}
- return null;
+ return IpAddress.valueOf(InetAddress.getLoopbackAddress()).toString();
}
@Deactivate
@@ -255,9 +262,6 @@
@Override
public ControllerNode addNode(NodeId nodeId, IpAddress ip, int tcpPort) {
- checkNotNull(nodeId, INSTANCE_ID_NULL);
- checkNotNull(ip, "IP address must not be null");
- checkArgument(tcpPort > 5000, "Tcp port must be greater than 5000");
ControllerNode node = new DefaultControllerNode(nodeId, ip, tcpPort);
allNodes.put(node.id(), node);
updateState(nodeId, State.INACTIVE);
@@ -275,6 +279,24 @@
}
}
+ @Override
+ public void formCluster(Set<ControllerNode> nodes, String ipPrefix) {
+ try {
+ Set<NodeInfo> infos = Sets.newHashSet();
+ nodes.forEach(n -> infos.add(NodeInfo.from(n.id().toString(),
+ n.ip().toString(),
+ n.tcpPort())));
+
+ ClusterDefinition cdef = ClusterDefinition.from(infos, ipPrefix);
+ new ClusterDefinitionStore(CLUSTER_DEFINITION_FILE).write(cdef);
+
+ DatabaseDefinition ddef = DatabaseDefinition.from(infos);
+ new DatabaseDefinitionStore(PARTITION_DEFINITION_FILE).write(ddef);
+ } catch (IOException e) {
+ log.error("Unable to form cluster", e);
+ }
+ }
+
private void updateState(NodeId nodeId, State newState) {
nodeStates.put(nodeId, newState);
nodeStateLastUpdatedTimes.put(nodeId, DateTime.now());
@@ -387,4 +409,5 @@
public DateTime getLastUpdated(NodeId nodeId) {
return nodeStateLastUpdatedTimes.get(nodeId);
}
+
}
diff --git a/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/HazelcastClusterStore.java b/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/HazelcastClusterStore.java
index 55d877d..827fec2 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/HazelcastClusterStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/cluster/impl/HazelcastClusterStore.java
@@ -24,12 +24,12 @@
import com.hazelcast.core.MemberAttributeEvent;
import com.hazelcast.core.MembershipEvent;
import com.hazelcast.core.MembershipListener;
-
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.Service;
import org.joda.time.DateTime;
+import org.onlab.packet.IpAddress;
import org.onosproject.cluster.ClusterEvent;
import org.onosproject.cluster.ClusterStore;
import org.onosproject.cluster.ClusterStoreDelegate;
@@ -39,7 +39,6 @@
import org.onosproject.store.hz.AbsentInvalidatingLoadingCache;
import org.onosproject.store.hz.AbstractHazelcastStore;
import org.onosproject.store.hz.OptionalCacheLoader;
-import org.onlab.packet.IpAddress;
import java.util.Map;
import java.util.Set;
@@ -131,6 +130,11 @@
}
@Override
+ public void formCluster(Set<ControllerNode> nodes, String ipPrefix) {
+ throw new UnsupportedOperationException("formCluster not implemented");
+ }
+
+ @Override
public ControllerNode addNode(NodeId nodeId, IpAddress ip, int tcpPort) {
return addNode(new DefaultControllerNode(nodeId, ip, tcpPort));
}
diff --git a/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinition.java b/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinition.java
index 46754e5..11b56c1 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinition.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinition.java
@@ -15,16 +15,20 @@
*/
package org.onosproject.store.consistent.impl;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import org.onosproject.store.cluster.impl.NodeInfo;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
-import org.onosproject.store.cluster.impl.NodeInfo;
-
import static com.google.common.base.Preconditions.checkNotNull;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-
/**
* Partitioned database configuration.
*/
@@ -34,11 +38,13 @@
/**
* Creates a new DatabaseDefinition.
+ *
* @param partitions partition map
- * @param nodes set of nodes
+ * @param nodes set of nodes
* @return database definition
*/
- public static DatabaseDefinition from(Map<String, Set<NodeInfo>> partitions, Set<NodeInfo> nodes) {
+ public static DatabaseDefinition from(Map<String, Set<NodeInfo>> partitions,
+ Set<NodeInfo> nodes) {
checkNotNull(partitions);
checkNotNull(nodes);
DatabaseDefinition definition = new DatabaseDefinition();
@@ -48,7 +54,18 @@
}
/**
+ * Creates a new DatabaseDefinition using default partitions.
+ *
+ * @param nodes set of nodes
+ * @return database definition
+ */
+ public static DatabaseDefinition from(Set<NodeInfo> nodes) {
+ return from(generateDefaultPartitions(nodes), nodes);
+ }
+
+ /**
* Returns the map of database partitions.
+ *
* @return db partition map
*/
public Map<String, Set<NodeInfo>> getPartitions() {
@@ -57,9 +74,35 @@
/**
* Returns the set of nodes.
+ *
* @return nodes
*/
public Set<NodeInfo> getNodes() {
return nodes;
}
+
+
+ /**
+ * Generates set of default partitions using permutations of the nodes.
+ *
+ * @param nodes information about cluster nodes
+ * @return default partition map
+ */
+ private static Map<String, Set<NodeInfo>> generateDefaultPartitions(Set<NodeInfo> nodes) {
+ List<NodeInfo> sorted = new ArrayList<>(nodes);
+ Collections.sort(sorted, (o1, o2) -> o1.getId().compareTo(o2.getId()));
+ Map<String, Set<NodeInfo>> partitions = Maps.newHashMap();
+
+ int length = nodes.size();
+ int count = 3;
+ for (int i = 0; i < length; i++) {
+ Set<NodeInfo> set = new HashSet<>(count);
+ for (int j = 0; j < count; j++) {
+ set.add(sorted.get((i + j) % length));
+ }
+ partitions.put("p" + (i + 1), set);
+ }
+ return partitions;
+ }
+
}
\ No newline at end of file
diff --git a/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinitionStore.java b/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinitionStore.java
index e54fe3c..b77667b 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinitionStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseDefinitionStore.java
@@ -20,13 +20,14 @@
import java.io.File;
import java.io.IOException;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.io.Files;
/**
* Allows for reading and writing partitioned database definition as a JSON file.
*/
public class DatabaseDefinitionStore {
- private final File definitionfile;
+ private final File file;
/**
* Creates a reader/writer of the database definition file.
@@ -34,7 +35,7 @@
* @param filePath location of the definition file
*/
public DatabaseDefinitionStore(String filePath) {
- definitionfile = new File(checkNotNull(filePath));
+ file = new File(checkNotNull(filePath));
}
/**
@@ -43,7 +44,7 @@
* @param filePath location of the definition file
*/
public DatabaseDefinitionStore(File filePath) {
- definitionfile = checkNotNull(filePath);
+ file = checkNotNull(filePath);
}
/**
@@ -54,8 +55,7 @@
*/
public DatabaseDefinition read() throws IOException {
ObjectMapper mapper = new ObjectMapper();
- DatabaseDefinition definition = mapper.readValue(definitionfile, DatabaseDefinition.class);
- return definition;
+ return mapper.readValue(file, DatabaseDefinition.class);
}
/**
@@ -67,7 +67,8 @@
public void write(DatabaseDefinition definition) throws IOException {
checkNotNull(definition);
// write back to file
- final ObjectMapper mapper = new ObjectMapper();
- mapper.writeValue(definitionfile, definition);
+ Files.createParentDirs(file);
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.writeValue(file, definition);
}
}
diff --git a/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseManager.java b/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseManager.java
index eb5a70f..b939780 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseManager.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DatabaseManager.java
@@ -16,11 +16,9 @@
package org.onosproject.store.consistent.impl;
-import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
-
import net.kuujo.copycat.CopycatConfig;
import net.kuujo.copycat.cluster.ClusterConfig;
import net.kuujo.copycat.cluster.Member;
@@ -34,7 +32,6 @@
import net.kuujo.copycat.protocol.Consistency;
import net.kuujo.copycat.protocol.Protocol;
import net.kuujo.copycat.util.concurrent.NamedThreadFactory;
-
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
@@ -75,16 +72,18 @@
public class DatabaseManager implements StorageService, StorageAdminService {
private final Logger log = getLogger(getClass());
+
+ public static final int COPYCAT_TCP_PORT = 7238; // 7238 = RAFT
+ public static final String PARTITION_DEFINITION_FILE = "../config/tablets.json";
+ public static final String BASE_PARTITION_NAME = "p0";
+
+ private static final int DATABASE_STARTUP_TIMEOUT_SEC = 60;
+ private static final int RAFT_ELECTION_TIMEOUT = 3000;
+ private static final int RAFT_HEARTBEAT_TIMEOUT = 1500;
+
private ClusterCoordinator coordinator;
private PartitionedDatabase partitionedDatabase;
private Database inMemoryDatabase;
- public static final int COPYCAT_TCP_PORT = 7238; // 7238 = RAFT
- private static final String CONFIG_DIR = "../config";
- private static final String PARTITION_DEFINITION_FILE = "tablets.json";
- private static final int DATABASE_STARTUP_TIMEOUT_SEC = 60;
- public static final String BASE_PARTITION_NAME = "p0";
- private static final int RAFT_ELECTION_TIMEOUT = 3000;
- private static final int RAFT_HEARTBEAT_TIMEOUT = 1500;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ClusterService clusterService;
@@ -98,15 +97,14 @@
@Activate
public void activate() {
-
// load database configuration
- File file = new File(CONFIG_DIR, PARTITION_DEFINITION_FILE);
- log.info("Loading database definition: {}", file.getAbsolutePath());
+ File databaseDefFile = new File(PARTITION_DEFINITION_FILE);
+ log.info("Loading database definition: {}", databaseDefFile.getAbsolutePath());
Map<String, Set<NodeInfo>> partitionMap;
try {
- DatabaseDefinitionStore databaseDefStore = new DatabaseDefinitionStore(file);
- if (!file.exists()) {
+ DatabaseDefinitionStore databaseDefStore = new DatabaseDefinitionStore(databaseDefFile);
+ if (!databaseDefFile.exists()) {
createDefaultDatabaseDefinition(databaseDefStore);
}
partitionMap = databaseDefStore.read().getPartitions();
@@ -189,10 +187,9 @@
private void createDefaultDatabaseDefinition(DatabaseDefinitionStore store) {
// Assumes IPv4 is returned.
String ip = DistributedClusterStore.getSiteLocalAddress();
- NodeInfo node = NodeInfo.from(ip, ip, DistributedClusterStore.DEFAULT_PORT);
+ NodeInfo node = NodeInfo.from(ip, ip, COPYCAT_TCP_PORT);
try {
- store.write(DatabaseDefinition.from(ImmutableMap.of("p1", ImmutableSet.of(node)),
- ImmutableSet.of(node)));
+ store.write(DatabaseDefinition.from(ImmutableSet.of(node)));
} catch (IOException e) {
log.warn("Unable to write default cluster definition", e);
}
diff --git a/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleClusterStore.java b/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleClusterStore.java
index 3849555..e0a4b9d 100644
--- a/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleClusterStore.java
+++ b/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleClusterStore.java
@@ -15,10 +15,7 @@
*/
package org.onosproject.store.trivial.impl;
-import static org.slf4j.LoggerFactory.getLogger;
-
-import java.util.Set;
-
+import com.google.common.collect.ImmutableSet;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
@@ -36,7 +33,9 @@
import org.onosproject.store.AbstractStore;
import org.slf4j.Logger;
-import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+
+import static org.slf4j.LoggerFactory.getLogger;
/**
* Manages inventory of infrastructure devices using trivial in-memory
@@ -94,6 +93,11 @@
}
@Override
+ public void formCluster(Set<ControllerNode> nodes, String ipPrefix) {
+
+ }
+
+ @Override
public ControllerNode addNode(NodeId nodeId, IpAddress ip, int tcpPort) {
return null;
}
diff --git a/pom.xml b/pom.xml
index 3db4169..6594c4e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -258,7 +258,12 @@
<version>${karaf.version}</version>
<scope>provided</scope>
</dependency>
-
+ <dependency>
+ <groupId>org.apache.karaf.system</groupId>
+ <artifactId>org.apache.karaf.system.core</artifactId>
+ <version>${karaf.version}</version>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>org.apache.karaf.shell</groupId>
<artifactId>org.apache.karaf.shell.console</artifactId>
diff --git a/tools/test/bin/onos-form-cluster b/tools/test/bin/onos-form-cluster
new file mode 100755
index 0000000..ea50968
--- /dev/null
+++ b/tools/test/bin/onos-form-cluster
@@ -0,0 +1,33 @@
+#!/bin/bash
+# -----------------------------------------------------------------------------
+# Forms ONOS cluster using REST API.
+# -----------------------------------------------------------------------------
+
+[ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
+. $ONOS_ROOT/tools/build/envDefaults
+
+ip=${1:-$OCI}
+
+if [ $ip = "cell" ]; then
+ ip=$OC1
+ nodes=$(env | grep "OC[0-9]*=" | grep -v "OC1=" | cut -d= -f2)
+else
+ shift
+ nodes=$*
+fi
+
+ipPrefix=${ip%.*}
+
+aux=/tmp/${ipPrefix}.cluster.json
+trap "rm -f $aux" EXIT
+
+echo "{ \"nodes\": [ { \"ip\": \"$ip\" }" > $aux
+for node in $nodes; do
+ echo ", { \"ip\": \"$node\" }" >> $aux
+done
+echo "], \"ipPrefix\": \"$ipPrefix.*\" }" >> $aux
+
+for node in $ip $nodes; do
+ echo "Forming cluster on $node..."
+ curl -X POST http://$node:8181/onos/v1/cluster/configuration -d @$aux
+done
\ No newline at end of file
diff --git a/web/api/src/main/java/org/onosproject/rest/ClusterWebResource.java b/web/api/src/main/java/org/onosproject/rest/ClusterWebResource.java
new file mode 100644
index 0000000..5f2fde9
--- /dev/null
+++ b/web/api/src/main/java/org/onosproject/rest/ClusterWebResource.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2015 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.rest;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.cluster.ClusterAdminService;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.codec.JsonCodec;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * REST resource for interacting with the ONOS cluster subsystem.
+ */
+@Path("cluster")
+public class ClusterWebResource extends AbstractWebResource {
+
+ public static final String NODE_NOT_FOUND = "Node is not found";
+
+ @GET
+ public Response getClusterNodes() {
+ Iterable<ControllerNode> nodes = get(ClusterService.class).getNodes();
+ return ok(encodeArray(ControllerNode.class, "nodes", nodes)).build();
+ }
+
+ @GET
+ @Path("{id}")
+ public Response getClusterNode(@PathParam("id") String id) {
+ ControllerNode node = nullIsNotFound(get(ClusterService.class).getNode(new NodeId(id)),
+ NODE_NOT_FOUND);
+ return ok(codec(ControllerNode.class).encode(node, this)).build();
+ }
+
+ @POST
+ @Path("configuration")
+ public Response formCluster(InputStream config) throws IOException {
+ JsonCodec<ControllerNode> codec = codec(ControllerNode.class);
+ ObjectNode root = (ObjectNode) mapper().readTree(config);
+ String ipPrefix = root.path("ipPrefix").asText();
+
+ List<ControllerNode> nodes = codec.decode((ArrayNode) root.path("nodes"), this);
+ get(ClusterAdminService.class).formCluster(new HashSet<>(nodes), ipPrefix);
+
+ return Response.ok().build();
+ }
+
+}
diff --git a/web/api/src/main/webapp/WEB-INF/web.xml b/web/api/src/main/webapp/WEB-INF/web.xml
index 0f1ee18..7741afa 100644
--- a/web/api/src/main/webapp/WEB-INF/web.xml
+++ b/web/api/src/main/webapp/WEB-INF/web.xml
@@ -62,6 +62,7 @@
org.onosproject.rest.JsonBodyWriter,
org.onosproject.rest.ApplicationsWebResource,
+ org.onosproject.rest.ClusterWebResource,
org.onosproject.rest.DevicesWebResource,
org.onosproject.rest.LinksWebResource,
org.onosproject.rest.HostsWebResource,