You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by th...@apache.org on 2013/04/10 10:05:17 UTC
svn commit: r1466363 - in /jackrabbit/oak/trunk/oak-mongomk/src:
main/java/org/apache/jackrabbit/mongomk/prototype/
test/java/org/apache/jackrabbit/mongomk/prototype/
Author: thomasm
Date: Wed Apr 10 08:05:17 2013
New Revision: 1466363
URL: http://svn.apache.org/r1466363
Log:
OAK-731 The cluster id should be automatically generated (WIP)
Added:
jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/ClusterNodeInfo.java
Modified:
jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/DocumentStore.java
jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MemoryDocumentStore.java
jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoDocumentStore.java
jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoMK.java
jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/prototype/ClusterTest.java
Added: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/ClusterNodeInfo.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/ClusterNodeInfo.java?rev=1466363&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/ClusterNodeInfo.java (added)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/ClusterNodeInfo.java Wed Apr 10 08:05:17 2013
@@ -0,0 +1,291 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.jackrabbit.mongomk.prototype;
+
+import java.lang.management.ManagementFactory;
+import java.net.NetworkInterface;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.jackrabbit.mk.api.MicroKernelException;
+import org.apache.jackrabbit.mk.util.StringUtils;
+import org.apache.jackrabbit.mongomk.prototype.DocumentStore.Collection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Information about a cluster node.
+ */
+public class ClusterNodeInfo {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MongoDocumentStore.class);
+
+ /**
+ * The prefix for random (non-reusable) keys.
+ */
+ private static final String RANDOM_PREFIX = "random:";
+
+ /**
+ * The cluster node id.
+ */
+ private static final String ID_KEY = "_id";
+
+ /**
+ * The machine id.
+ */
+ private static final String MACHINE_ID_KEY = "machine";
+
+ /**
+ * The unique instance id within this machine (the current working directory
+ * if not set).
+ */
+ private static final String INSTANCE_ID_KEY = "instance";
+
+ /**
+ * The end of the lease.
+ */
+ private static final String LEASE_END_KEY = "leaseEnd";
+
+ /**
+ * Additional info, such as the process id, for support.
+ */
+ private static final String INFO_KEY = "info";
+
+ /**
+ * The unique machine id (the MAC address if available).
+ */
+ private static final String MACHINE_ID = getMachineId();
+
+ /**
+ * The process id (if available).
+ */
+ private static final long PROCESS_ID = getProcessId();
+
+ /**
+ * The current working directory.
+ */
+ private static final String WORKING_DIR = System.getProperty("user.dir", "");
+
+ /**
+ * The initial number of milliseconds for a lease (1 minute). This value is
+ * only used until the first renewal.
+ */
+ private static final long LEASE_INITIAL = 1000 * 60;
+
+ /**
+ * The assigned cluster id.
+ */
+ private final int id;
+
+ /**
+ * The machine id.
+ */
+ private final String machineId;
+
+ /**
+ * The instance id.
+ */
+ private final String instanceId;
+
+ /**
+ * The document store that is used to renew the lease.
+ */
+ private final DocumentStore store;
+
+ /**
+ * The time (in milliseconds UTC) where this instance was started.
+ */
+ private final long startTime;
+
+ /**
+ * A unique id.
+ */
+ private final String uuid = UUID.randomUUID().toString();
+
+ /**
+ * The time (in milliseconds UTC) where the lease of this instance ends.
+ */
+ private long leaseEndTime;
+
+ ClusterNodeInfo(int id, DocumentStore store, String machineId, String instanceId) {
+ this.id = id;
+ this.startTime = System.currentTimeMillis();
+ this.leaseEndTime = startTime;
+ this.store = store;
+ this.machineId = machineId;
+ this.instanceId = instanceId;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ /**
+ * Create a cluster node info instance for the store.
+ *
+ * @param store the document store (for the lease)
+ * @param machineId the machine id (null for MAC address)
+ * @param instanceId the instance id (null for current working directory)
+ * @return the cluster node info
+ */
+ public static ClusterNodeInfo getInstance(DocumentStore store, String machineId, String instanceId) {
+ if (machineId == null) {
+ machineId = MACHINE_ID;
+ }
+ if (instanceId == null) {
+ instanceId = WORKING_DIR;
+ }
+ for (int i = 0; i < 10; i++) {
+ ClusterNodeInfo clusterNode = createInstance(store, machineId, instanceId);
+ UpdateOp update = new UpdateOp(null, "" + clusterNode.id, true);
+ update.set(ID_KEY, "" + clusterNode.id);
+ update.set(MACHINE_ID_KEY, clusterNode.machineId);
+ update.set(INSTANCE_ID_KEY, clusterNode.instanceId);
+ update.set(LEASE_END_KEY, System.currentTimeMillis() + LEASE_INITIAL);
+ update.set(INFO_KEY, clusterNode.toString());
+ boolean success = store.create(Collection.CLUSTER_NODES, Collections.singletonList(update));
+ if (success) {
+ return clusterNode;
+ }
+ }
+ throw new MicroKernelException("Could not get cluster node info");
+ }
+
+ private static ClusterNodeInfo createInstance(DocumentStore store, String machineId, String instanceId) {
+ long now = System.currentTimeMillis();
+ // keys between "0" and "a" includes all possible numbers
+ List<Map<String, Object>> list = store.query(Collection.CLUSTER_NODES,
+ "0", "a", Integer.MAX_VALUE);
+ int clusterNodeId = 0;
+ int maxId = 0;
+ for (Map<String, Object> doc : list) {
+ String key = "" + doc.get(ID_KEY);
+ int id;
+ try {
+ id = Integer.parseInt(key);
+ } catch (Exception e) {
+ // not an integer - ignore
+ continue;
+ }
+ maxId = Math.max(maxId, id);
+ Long leaseEnd = (Long) doc.get(LEASE_END_KEY);
+ if (leaseEnd != null && leaseEnd > now) {
+ continue;
+ }
+ String mId = "" + doc.get(MACHINE_ID_KEY);
+ String iId = "" + doc.get(INSTANCE_ID_KEY);
+ if (machineId.startsWith(RANDOM_PREFIX)) {
+ // remove expired entries with random keys
+ store.remove(Collection.CLUSTER_NODES, key);
+ continue;
+ }
+ if (!mId.equals(machineId) ||
+ !iId.equals(instanceId)) {
+ // a different machine or instance
+ continue;
+ }
+ // remove expired matching entries
+ store.remove(Collection.CLUSTER_NODES, key);
+ if (clusterNodeId == 0 || id < clusterNodeId) {
+ // if there are multiple, use the smallest value
+ clusterNodeId = id;
+ }
+ }
+ if (clusterNodeId == 0) {
+ clusterNodeId = maxId + 1;
+ }
+ return new ClusterNodeInfo(clusterNodeId, store, machineId, instanceId);
+ }
+
+ /**
+ * Renew the cluster id lease. This method needs to be called once in a while,
+ * to ensure the same cluster id is not re-used by a different instance.
+ *
+ * @param nextCheckMillis the millisecond offset
+ */
+ public void renewLease(long nextCheckMillis) {
+ long now = System.currentTimeMillis();
+ if (now + nextCheckMillis + nextCheckMillis < leaseEndTime) {
+ return;
+ }
+ UpdateOp update = new UpdateOp(null, "" + id, true);
+ leaseEndTime = now + nextCheckMillis;
+ update.set(LEASE_END_KEY, leaseEndTime);
+ store.createOrUpdate(Collection.CLUSTER_NODES, update);
+ }
+
+ public void dispose() {
+ UpdateOp update = new UpdateOp(null, "" + id, true);
+ update.set(LEASE_END_KEY, null);
+ store.createOrUpdate(Collection.CLUSTER_NODES, update);
+ }
+
+ public String toString() {
+ return "id: " + id + ",\n" +
+ "startTime: " + startTime + ",\n" +
+ "machineId: " + machineId + ",\n" +
+ "instanceId: " + instanceId + ",\n" +
+ "pid: " + PROCESS_ID + ",\n" +
+ "uuid: " + uuid +"\n";
+ }
+
+ private static long getProcessId() {
+ try {
+ String name = ManagementFactory.getRuntimeMXBean().getName();
+ return Long.parseLong(name.substring(0, name.indexOf('@')));
+ } catch (Exception e) {
+ LOG.warn("Could not get process id", e);
+ return 0;
+ }
+ }
+
+ /**
+ * Calculate the unique machine id. This is the lowest MAC address if
+ * available. As an alternative, a randomly generated UUID is used.
+ *
+ * @return the unique id
+ */
+ private static String getMachineId() {
+ try {
+ ArrayList<String> list = new ArrayList<String>();
+ Enumeration<NetworkInterface> e = NetworkInterface
+ .getNetworkInterfaces();
+ while (e.hasMoreElements()) {
+ NetworkInterface ni = e.nextElement();
+ byte[] mac = ni.getHardwareAddress();
+ if (mac != null) {
+ String x = StringUtils.convertBytesToHex(mac);
+ list.add(x);
+ }
+ }
+ if (list.size() > 0) {
+ // use the lowest value, such that if the order changes,
+ // the same one is used
+ Collections.sort(list);
+ return "mac:" + list.get(0);
+ }
+ } catch (Exception e) {
+ LOG.error("Error calculating the machine id", e);
+ }
+ return RANDOM_PREFIX + UUID.randomUUID().toString();
+ }
+
+}
Modified: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/DocumentStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/DocumentStore.java?rev=1466363&r1=1466362&r2=1466363&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/DocumentStore.java (original)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/DocumentStore.java Wed Apr 10 08:05:17 2013
@@ -32,7 +32,38 @@ public interface DocumentStore {
/**
* The list of collections.
*/
- enum Collection { NODES }
+ enum Collection {
+
+ /**
+ * The 'nodes' collection. It contains all the node data, with one document
+ * per node, and the path as the primary key. Each document possibly
+ * contains multiple revisions.
+ * <p>
+ * Key: the path, value: the node data (possibly multiple revisions)
+ * <p>
+ * Old revisions are removed after some time, either by the process that
+ * removed or updated the node, lazily when reading, or in a background
+ * process.
+ */
+ NODES("nodes"),
+
+ /**
+ * The 'clusterNodes' collection contains the list of currently running
+ * cluster nodes. The key is the clusterNodeId (0, 1, 2,...).
+ */
+ CLUSTER_NODES("clusterNodes");
+
+ final String name;
+
+ Collection(String name) {
+ this.name = name;
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ }
/**
* Get a document.
@@ -62,6 +93,16 @@ public interface DocumentStore {
@CheckForNull
Map<String, Object> find(Collection collection, String key, int maxCacheAge);
+ /**
+ * Get a list of documents where the key is greater than a start value and
+ * less than an end value.
+ *
+ * @param collection the collection
+ * @param fromKey the start value (excluding)
+ * @param toKey the end value (excluding)
+ * @param limit the maximum number of entries to return
+ * @return the list (possibly empty)
+ */
@Nonnull
List<Map<String, Object>> query(Collection collection, String fromKey, String toKey, int limit);
@@ -95,8 +136,14 @@ public interface DocumentStore {
Map<String, Object> createOrUpdate(Collection collection, UpdateOp update)
throws MicroKernelException;
+ /**
+ * Invalidate the document cache.
+ */
void invalidateCache();
+ /**
+ * Dispose this instance.
+ */
void dispose();
}
Modified: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MemoryDocumentStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MemoryDocumentStore.java?rev=1466363&r1=1466362&r2=1466363&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MemoryDocumentStore.java (original)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MemoryDocumentStore.java Wed Apr 10 08:05:17 2013
@@ -35,18 +35,16 @@ import org.apache.jackrabbit.mongomk.pro
public class MemoryDocumentStore implements DocumentStore {
/**
- * The 'nodes' collection. It contains all the node data, with one document
- * per node, and the path as the primary key. Each document possibly
- * contains multiple revisions.
- * <p>
- * Key: the path, value: the node data (possibly multiple revisions)
- * <p>
- * Old revisions are removed after some time, either by the process that
- * removed or updated the node, lazily when reading, or in a background
- * process.
+ * The 'nodes' collection.
*/
private ConcurrentSkipListMap<String, Map<String, Object>> nodes =
new ConcurrentSkipListMap<String, Map<String, Object>>();
+
+ /**
+ * The 'clusterNodes' collection.
+ */
+ private ConcurrentSkipListMap<String, Map<String, Object>> clusterNodes =
+ new ConcurrentSkipListMap<String, Map<String, Object>>();
public Map<String, Object> find(Collection collection, String key, int maxCacheAge) {
return find(collection, key);
@@ -97,6 +95,8 @@ public class MemoryDocumentStore impleme
switch (collection) {
case NODES:
return nodes;
+ case CLUSTER_NODES:
+ return clusterNodes;
default:
throw new IllegalArgumentException(collection.name());
}
@@ -139,6 +139,12 @@ public class MemoryDocumentStore impleme
return oldNode;
}
+ /**
+ * Apply the changes to the in-memory map.
+ *
+ * @param target the target map
+ * @param update the changes to apply
+ */
public static void applyChanges(Map<String, Object> target, UpdateOp update) {
for (Entry<String, Operation> e : update.changes.entrySet()) {
String k = e.getKey();
Modified: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoDocumentStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoDocumentStore.java?rev=1466363&r1=1466362&r2=1466363&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoDocumentStore.java (original)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoDocumentStore.java Wed Apr 10 08:05:17 2013
@@ -52,14 +52,21 @@ public class MongoDocumentStore implemen
private static final boolean LOG_TIME = false;
- private final DBCollection nodesCollection;
+ private final DBCollection nodes;
+ private final DBCollection clusterNodes;
- private long time;
+ /**
+ * The sum of all milliseconds this class waited for MongoDB.
+ */
+ private long timeSum;
private final Cache<String, CachedDocument> cache;
public MongoDocumentStore(DB db) {
- nodesCollection = db.getCollection(Collection.NODES.toString());
+ nodes = db.getCollection(
+ Collection.NODES.toString());
+ clusterNodes = db.getCollection(
+ Collection.CLUSTER_NODES.toString());
// the _id field is the primary key, so we don't need to define it
// the following code is just a template in case we need more indexes
@@ -82,7 +89,7 @@ public class MongoDocumentStore implemen
private void end(long start) {
if (LOG_TIME) {
- time += System.currentTimeMillis() - start;
+ timeSum += System.currentTimeMillis() - start;
}
}
@@ -98,19 +105,19 @@ public class MongoDocumentStore implemen
cache.invalidateAll();
}
- public Map<String, Object> find(Collection collection, String path) {
- return find(collection, path, Integer.MAX_VALUE);
+ public Map<String, Object> find(Collection collection, String key) {
+ return find(collection, key, Integer.MAX_VALUE);
}
@Override
- public Map<String, Object> find(final Collection collection, final String path, int maxCacheAge) {
+ public Map<String, Object> find(final Collection collection, final String key, int maxCacheAge) {
try {
CachedDocument doc;
while (true) {
- doc = cache.get(path, new Callable<CachedDocument>() {
+ doc = cache.get(key, new Callable<CachedDocument>() {
@Override
public CachedDocument call() throws Exception {
- Map<String, Object> map = findUncached(collection, path);
+ Map<String, Object> map = findUncached(collection, key);
return new CachedDocument(map);
}
});
@@ -121,19 +128,19 @@ public class MongoDocumentStore implemen
break;
}
// too old: invalidate, try again
- cache.invalidate(path);
+ cache.invalidate(key);
}
return doc.value;
} catch (ExecutionException e) {
- throw new IllegalStateException("Failed to load node " + path, e);
+ throw new IllegalStateException("Failed to load document with " + key, e);
}
}
- public Map<String, Object> findUncached(Collection collection, String path) {
+ Map<String, Object> findUncached(Collection collection, String key) {
DBCollection dbCollection = getDBCollection(collection);
long start = start();
try {
- DBObject doc = dbCollection.findOne(getByPathQuery(path));
+ DBObject doc = dbCollection.findOne(getByKeyQuery(key));
if (doc == null) {
return null;
}
@@ -171,13 +178,13 @@ public class MongoDocumentStore implemen
}
@Override
- public void remove(Collection collection, String path) {
- log("remove", path);
+ public void remove(Collection collection, String key) {
+ log("remove", key);
DBCollection dbCollection = getDBCollection(collection);
long start = start();
try {
- cache.invalidate(path);
- WriteResult writeResult = dbCollection.remove(getByPathQuery(path), WriteConcern.SAFE);
+ cache.invalidate(key);
+ WriteResult writeResult = dbCollection.remove(getByKeyQuery(key), WriteConcern.SAFE);
if (writeResult.getError() != null) {
throw new MicroKernelException("Remove failed: " + writeResult.getError());
}
@@ -230,7 +237,7 @@ public class MongoDocumentStore implemen
}
}
- DBObject query = getByPathQuery(updateOp.key);
+ DBObject query = getByKeyQuery(updateOp.key);
BasicDBObject update = new BasicDBObject();
if (!setUpdates.isEmpty()) {
update.append("$set", setUpdates);
@@ -348,22 +355,24 @@ public class MongoDocumentStore implemen
private DBCollection getDBCollection(Collection collection) {
switch (collection) {
case NODES:
- return nodesCollection;
+ return nodes;
+ case CLUSTER_NODES:
+ return clusterNodes;
default:
throw new IllegalArgumentException(collection.name());
}
}
- private static DBObject getByPathQuery(String path) {
- return QueryBuilder.start(UpdateOp.ID).is(path).get();
+ private static DBObject getByKeyQuery(String key) {
+ return QueryBuilder.start(UpdateOp.ID).is(key).get();
}
@Override
public void dispose() {
if (LOG.isDebugEnabled()) {
- LOG.debug("MongoDB time: " + time);
+ LOG.debug("MongoDB time: " + timeSum);
}
- nodesCollection.getDB().getMongo().close();
+ nodes.getDB().getMongo().close();
}
private static void log(String message, Object... args) {
Modified: jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoMK.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoMK.java?rev=1466363&r1=1466362&r2=1466363&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoMK.java (original)
+++ jackrabbit/oak/trunk/oak-mongomk/src/main/java/org/apache/jackrabbit/mongomk/prototype/MongoMK.java Wed Apr 10 08:05:17 2013
@@ -109,7 +109,7 @@ public class MongoMK implements MicroKer
/**
* The unique cluster id, similar to the unique machine id in MongoDB.
*/
- private final int clusterId;
+ private int clusterId;
/**
* The node cache.
@@ -188,6 +188,7 @@ public class MongoMK implements MicroKer
this.store = builder.getDocumentStore();
this.blobStore = builder.getBlobStore();
this.clusterId = builder.getClusterId();
+ clusterId = Integer.getInteger("oak.mongoMK.clusterId", clusterId);
this.asyncDelay = builder.getAsyncDelay();
//TODO Use size based weigher
Modified: jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/prototype/ClusterTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/prototype/ClusterTest.java?rev=1466363&r1=1466362&r2=1466363&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/prototype/ClusterTest.java (original)
+++ jackrabbit/oak/trunk/oak-mongomk/src/test/java/org/apache/jackrabbit/mongomk/prototype/ClusterTest.java Wed Apr 10 08:05:17 2013
@@ -36,6 +36,56 @@ public class ClusterTest {
private MemoryDocumentStore ds;
private MemoryBlobStore bs;
+
+ @Test
+ public void clusterNodeInfoLease() throws InterruptedException {
+ MemoryDocumentStore store = new MemoryDocumentStore();
+ ClusterNodeInfo c1, c2;
+ c1 = ClusterNodeInfo.getInstance(store, "m1", null);
+ assertEquals(1, c1.getId());
+ // this will quickly expire
+ c1.renewLease(1);
+ Thread.sleep(10);
+ c2 = ClusterNodeInfo.getInstance(store, "m1", null);
+ assertEquals(1, c2.getId());
+ }
+
+ @Test
+ public void clusterNodeInfo() {
+ MemoryDocumentStore store = new MemoryDocumentStore();
+ ClusterNodeInfo c1, c2, c3, c4;
+
+ c1 = ClusterNodeInfo.getInstance(store, "m1", null);
+ assertEquals(1, c1.getId());
+ c1.dispose();
+
+ // get the same id
+ c1 = ClusterNodeInfo.getInstance(store, "m1", null);
+ assertEquals(1, c1.getId());
+ c1.dispose();
+
+ // now try to add another one:
+ // must get a new id
+ c2 = ClusterNodeInfo.getInstance(store, "m2", null);
+ assertEquals(2, c2.getId());
+
+ // a different machine
+ c3 = ClusterNodeInfo.getInstance(store, "m3", "/a");
+ assertEquals(3, c3.getId());
+
+ c2.dispose();
+ c3.dispose();
+
+ c3 = ClusterNodeInfo.getInstance(store, "m3", "/a");
+ assertEquals(3, c3.getId());
+
+ c3.dispose();
+
+ c4 = ClusterNodeInfo.getInstance(store, "m3", "/b");
+ assertEquals(4, c4.getId());
+
+ c1.dispose();
+ }
@Test
public void conflict() {