Permalink
Browse files

Merge pull request #169 from stalehd/consolidate_interface

Simplify core interface
  • Loading branch information...
2 parents f332408 + 0497f45 commit 61fd8d5790d9be894a87b463fad4e69cd94f76e0 @stalehd stalehd committed Jan 20, 2016
View
6 a3/src/main/java/org/cloudname/a3/A3Client.java
@@ -77,8 +77,9 @@ private void ensureOpened() {
*
* @param in an InputStream which contains valid JSON user database.
* @return an A3Client instance backed by MemoryStorage
- * @deprecated does not specify how to convert bytes to
- * characters, use a Reader instead of InputStream.
+ * @deprecated does not specify how to convert bytes to characters, use a Reader instead of
+ * InputStream.
+ * @throws IOException if there's an error reading from the memory storage
*/
public static A3Client newMemoryOnlyClient(InputStream in) throws IOException {
return new A3Client(MemoryStorage.fromInputStream(in));
@@ -90,6 +91,7 @@ public static A3Client newMemoryOnlyClient(InputStream in) throws IOException {
*
* @param in a Reader which contains a valid JSON user database.
* @return an A3Client instance backed by MemoryStorage
+ * @throws IOException if there's an error reading from the storage
*/
public static A3Client newMemoryOnlyClient(Reader in) throws IOException {
return new A3Client(MemoryStorage.fromReader(in));
View
4 a3/src/main/java/org/cloudname/a3/domain/ServiceCoordinate.java
@@ -103,8 +103,8 @@ public String getPathPrefix(String namespace, boolean includeInstance) {
/**
* Get the path prefix for a Service coordinate.
*
- * @param namespace {@see getPathPrefix(String,boolean)}
- * @return
+ * @param namespace THe namespace for the path prefix
+ * @return a ZooKeeper path prefix
*/
public String getPathPrefix(String namespace) {
return getPathPrefix(namespace, false);
View
17 a3/src/main/java/org/cloudname/a3/domain/User.java
@@ -45,7 +45,16 @@ public User() {
* This constructor is mainly used by Jackson in order to create
* instances of the User object from JSON.
*
- * The Set<String> of roles should be lowercase role names.
+ * The Set[string] of roles should be lowercase role names.
+ *
+ * @param username user name
+ * @param password user's password
+ * @param oldPassword old password for user
+ * @param oldPasswordExpiry expiry date for old password
+ * @param realName User's real name
+ * @param email User's email
+ * @param roles User roles
+ * @param properties User properties
*/
@JsonCreator
public User(@JsonProperty("username") String username,
@@ -177,7 +186,9 @@ public String toJson() {
}
/**
- * Create a User instance from a JSON string.
+ * @param json JSON String
+ * @return User instance from a JSON string.
+ * @throws IOException if there's an error reading from JSON
*/
public static User fromJson(String json) throws IOException {
final ObjectMapper mapper = new ObjectMapper();
@@ -189,4 +200,4 @@ public static User fromJson(String json) throws IOException {
public String toString() {
return toJson();
}
-}
+}
View
6 a3/src/main/java/org/cloudname/a3/jaxrs/JerseyRequestFilter.java
@@ -31,12 +31,12 @@
* (the user might me trying to access a publicly available resource). However if
* the user is specified but the username or password is wrong then we let the user know.
*
- * <h4>Dependencies</h4>
+ * <strong>Dependencies</strong>
* The filter expects an {@link A3Client} instance to be provided by Jersey. For that to work
- * you need to have a @{@link javax.ws.rs.ext.Provider.Provider} creating it available somewhere where Jersey can
+ * you need to have a @{@link javax.ws.rs.ext.Provider} creating it available somewhere where Jersey can
* find it.
*
- * <h4>Configuration</h4>
+ * <strong>Configuration</strong>
* <p>
* You need to configure Jersey to use this filter - set its init parameter
* <code>com.sun.jersey.spi.container.ContainerRequestFilters</code>
View
140 cn-consul/src/main/java/org/cloudname/backends/consul/ConsulBackend.java
@@ -4,6 +4,7 @@
import org.cloudname.core.CloudnamePath;
import org.cloudname.core.LeaseHandle;
import org.cloudname.core.LeaseListener;
+import org.cloudname.core.LeaseType;
import java.util.Arrays;
import java.util.HashSet;
@@ -12,6 +13,7 @@
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Logger;
/**
* This is a basic implementation of a CloudName backend. It uses the KV store for all data since
@@ -22,6 +24,8 @@
* @author stalehd@gmail.com
*/
public class ConsulBackend implements CloudnameBackend {
+ private static final Logger LOG = Logger.getLogger(ConsulBackend.class.getName());
+
final Consul consul;
private static final int SESSION_TTL = 10;
@@ -31,8 +35,6 @@
private final Map<LeaseListener, ConsulWatch> watches = new ConcurrentHashMap<>();
private static final char SEPARATOR = '/';
private static final String CN_PREFIX = "cn";
- private static final String EPHEMERAL_PREFIX = "ephemeral";
- private static final String PERMANENT_PREFIX = "permanent";
/**
* Convert a cloudname path to a session name.
@@ -42,17 +44,10 @@ private String pathToSession(final CloudnamePath path) {
}
/**
- * Convert a cloudname path to an ephemeral KV key name.
+ * Convert a cloudname path to a KV key name.
*/
- private String pathToEphemeralKv(final CloudnamePath path) {
- return CN_PREFIX + SEPARATOR + EPHEMERAL_PREFIX + SEPARATOR + path.join(SEPARATOR);
- }
-
- /**
- * Convert cloudname path to permanent KV key.
- */
- private String pathToPermanentKv(final CloudnamePath path) {
- return CN_PREFIX + SEPARATOR + PERMANENT_PREFIX + SEPARATOR + path.join(SEPARATOR);
+ private String pathToKv(final CloudnamePath path) {
+ return CN_PREFIX + SEPARATOR + SEPARATOR + path.join(SEPARATOR);
}
/**
@@ -102,8 +97,7 @@ private String getRandomInstanceId() {
}
}
- @Override
- public LeaseHandle createTemporaryLease(final CloudnamePath path, final String data) {
+ private LeaseHandle createTemporary(final CloudnamePath path, final String data) {
// Create session with TTL set to <something> and Behavior=delete. The session isn't
// used to uniquely identify the client but to create ephemeral values in the KV store.
final ConsulSession session
@@ -115,7 +109,7 @@ public LeaseHandle createTemporaryLease(final CloudnamePath path, final String d
while (!leaseAcquired) {
instancePath.set(new CloudnamePath(path, getRandomInstanceId()));
leaseAcquired = consul.writeSessionData(
- pathToEphemeralKv(instancePath.get()), data, session.getId());
+ pathToKv(instancePath.get()), data, session.getId());
}
sessions.put(instancePath.get(), session);
@@ -124,12 +118,12 @@ public LeaseHandle createTemporaryLease(final CloudnamePath path, final String d
return new LeaseHandle() {
@Override
- public boolean writeLeaseData(final String data) {
+ public boolean writeData(final String data) {
if (session.isClosed()) {
return false;
}
return consul.writeSessionData(
- pathToEphemeralKv(instancePath.get()), data, session.getId());
+ pathToKv(instancePath.get()), data, session.getId());
}
@Override
@@ -150,82 +144,102 @@ public void close() throws Exception {
}
@Override
- public boolean writeTemporaryLeaseData(final CloudnamePath path, final String data) {
+ public boolean writeLeaseData(final CloudnamePath path, final String data) {
final ConsulSession session = sessions.get(path);
if (session == null) {
return false;
}
- return consul.writeSessionData(pathToEphemeralKv(path), data, session.getId());
+ return consul.writeSessionData(pathToKv(path), data, session.getId());
}
@Override
- public String readTemporaryLeaseData(final CloudnamePath path) {
+ public String readLeaseData(final CloudnamePath path) {
if (path == null) {
return null;
}
- return consul.readData(pathToEphemeralKv(path));
+ return consul.readData(pathToKv(path));
}
@Override
- public void addTemporaryLeaseListener(
- final CloudnamePath pathToWatch, final LeaseListener listener) {
- final ConsulWatch watch = consul.createWatch(pathToEphemeralKv(pathToWatch));
- watches.put(listener, watch);
- watch.startWatching(new ConsulWatch.ConsulWatchListener() {
- @Override
- public void created(final String valueName, final String value) {
- listener.leaseCreated(kvNameToCloudnamePath(valueName), value);
- }
-
- @Override
- public void changed(final String valueName, final String value) {
- listener.dataChanged(kvNameToCloudnamePath(valueName), value);
- }
+ public LeaseHandle createLease(
+ final LeaseType type, final CloudnamePath path, final String data) {
+ switch (type) {
+ case PERMANENT:
+ if (consul.createPermanentData(pathToKv(path), data)) {
+ return new LeaseHandle() {
+ @Override
+ public boolean writeData(final String data) {
+ return writeLeaseData(path, data);
+ }
+
+ @Override
+ public CloudnamePath getLeasePath() {
+ return path;
+ }
+
+ @Override
+ public void close() throws Exception {
+ // nothing to do
+ }
+ };
+ }
+ return null;
- @Override
- public void removed(final String valueName) {
- listener.leaseRemoved(kvNameToCloudnamePath(valueName));
- }
- });
- }
+ case TEMPORARY:
+ return createTemporary(path, data);
- @Override
- public void removeTemporaryLeaseListener(final LeaseListener listener) {
- // Remove watcher
- final ConsulWatch watch = watches.get(listener);
- if (watch != null) {
- watch.stop();
+ default:
+ LOG.severe("Uknown lease type: " + type
+ + " - don't know how to create that kind of lease"
+ + " (path = " + path + ", data = " + data + ")");
+ return null;
}
}
@Override
- public boolean createPermanantLease(final CloudnamePath path, final String data) {
- return consul.createPermanentData(pathToPermanentKv(path), data);
- }
-
- @Override
- public boolean removePermanentLease(final CloudnamePath path) {
- final String consulPath = pathToPermanentKv(path);
+ public boolean removeLease(final CloudnamePath path) {
+ final String consulPath = pathToKv(path);
if (consul.readData(consulPath) == null) {
return false;
}
return consul.removePermanentData(consulPath);
}
@Override
- public boolean writePermanentLeaseData(final CloudnamePath path, final String data) {
- return consul.writePermanentData(pathToPermanentKv(path), data);
- }
+ public void addLeaseListener(final CloudnamePath leaseToObserve, final LeaseListener listener) {
+ final ConsulWatch watch = consul.createWatch(pathToKv(leaseToObserve));
+ watches.put(listener, watch);
+ watch.startWatching(new ConsulWatch.ConsulWatchListener() {
+ @Override
+ public void created(final String valueName, final String value) {
+ final CloudnamePath path = kvNameToCloudnamePath(valueName);
+ if (path.equals(leaseToObserve)) {
+ listener.leaseCreated(path, value);
+ }
+ }
- @Override
- public String readPermanentLeaseData(final CloudnamePath path) {
- return consul.readData(pathToPermanentKv(path));
+ @Override
+ public void changed(final String valueName, final String value) {
+ final CloudnamePath path = kvNameToCloudnamePath(valueName);
+ if (path.equals(leaseToObserve)) {
+ listener.dataChanged(path, value);
+ }
+ }
+
+ @Override
+ public void removed(final String valueName) {
+ final CloudnamePath path = kvNameToCloudnamePath(valueName);
+ if (path.equals(leaseToObserve)) {
+ listener.leaseRemoved(path);
+ }
+ }
+ });
}
@Override
- public void addPermanentLeaseListener(
+ public void addLeaseCollectionListener(
final CloudnamePath pathToObserve, final LeaseListener listener) {
- final ConsulWatch watch = consul.createWatch(pathToPermanentKv(pathToObserve));
+ final ConsulWatch watch = consul.createWatch(pathToKv(pathToObserve));
watches.put(listener, watch);
watch.startWatching(new ConsulWatch.ConsulWatchListener() {
@Override
@@ -246,7 +260,7 @@ public void removed(final String valueName) {
}
@Override
- public void removePermanentLeaseListener(final LeaseListener listener) {
+ public void removeLeaseListener(final LeaseListener listener) {
final ConsulWatch watch = watches.get(listener);
if (watch != null) {
watch.stop();
View
101 cn-core/src/main/java/org/cloudname/core/CloudnameBackend.java
@@ -19,6 +19,37 @@
*/
public interface CloudnameBackend extends AutoCloseable {
/**
+ * Create a lease.The temporary lease is limited by the client's connection and will
+ * be available for as long as the client is connected to the backend. Once the client
+ * disconnects (either through the LeaseHandle instance that is returned or just vanishing
+ * from the face of the earth) the lease is removed by the backend. The backend should support
+ * an unlimited number of leases (FSVO "unlimited").
+ *
+ * @param type Type of lease. This value cannot be null.
+ *
+ * @param path The full path to lease. This value cannot be null.
+ *
+ * @param data Lease data. This is an arbitrary string supplied by the client. It
+ * carries no particular semantics for the backend and the backend only has to return the
+ * same string to the client. This value cannot be null.
+ *
+ * @return A LeaseHandle instance that the client can use to manipulate its data or release
+ * the lease (i.e. close it).
+ */
+ LeaseHandle createLease(LeaseType type, CloudnamePath path, String data);
+
+ /**
+ * Remove a lease. The lease will be removed and clients listening on the lease
+ * will be notified. Leases with the {@link LeaseType} set to PERMANENT can be removed by any
+ * client. Leases with the {@link LeaseType} set to TEMPORARY can only be removed by the owner
+ * (ie creator) of the lease.
+ *
+ * @param path the path to the lease
+ * @return true if lease is removed
+ */
+ boolean removeLease(final CloudnamePath path);
+
+ /**
* Create a temporary lease. The temporary lease is limited by the client's connection and will
* be available for as long as the client is connected to the backend. Once the client
* disconnects (either through the LeaseHandle instance that is returned or just vanishing
@@ -38,17 +69,15 @@
* @return A LeaseHandle instance that the client can use to manipulate its data or release
* the lease (i.e. close it). The path to the lease can be accessed through this.
*/
- LeaseHandle createTemporaryLease(final CloudnamePath path, final String data);
/**
- * Update a client's lease. Normally this is something the client does itself but libraries
- * built on top of the backends might use it to set additional properties.
+ * Update a client's lease.
*
- * @param path path to the temporary lease
+ * @param path path to the lease
* @param data the updated lease data
* @return true if successful, false otherwise
*/
- boolean writeTemporaryLeaseData(final CloudnamePath path, final String data);
+ boolean writeLeaseData(final CloudnamePath path, final String data);
/**
* Read temporary lease data. Clients won't use this in regular use but rather monitor changes
@@ -57,76 +86,28 @@
* @param path path to the client lease
* @return the data stored in the client lease
*/
- String readTemporaryLeaseData(final CloudnamePath path);
+ String readLeaseData(final CloudnamePath path);
/**
- * Add a listener to a set of temporary leases identified by a path. The temporary leases
- * doesn't have to exist but as soon as someone creates a lease matching the given path a
- * notification must be sent by the backend implementation.
+ * Add a listener to a set of leases identified by a path. As soon as someone creates a lease
+ * matching the given path a notification is be sent by the backend.
*
* @param pathToWatch the path to observe for changes
* @param listener client's listener. Callbacks on this listener will be invoked by the backend
*/
- void addTemporaryLeaseListener(final CloudnamePath pathToWatch, final LeaseListener listener);
-
- /**
- * Remove a previously attached listener. The backend will ignore leases that don't exist.
- *
- * @param listener the listener to remove
- */
- void removeTemporaryLeaseListener(final LeaseListener listener);
-
- /**
- * Create a permanent lease. A permanent lease persists even if the client that created it
- * terminates or closes the connection. Other clients will still see the lease. Permanent leases
- * must persist until they are explicitly removed.
- *
- * <p>All permanent leases must be unique. Duplicate permanent leases yield errors.
- *
- * @param path path to the permanent lease
- * @param data data to store in the permanent lease when it is created
- * @return true if successful
- */
- boolean createPermanantLease(final CloudnamePath path, final String data);
-
- /**
- * Remove a permanent lease. The lease will be removed and clients listening on the lease
- * will be notified.
- *
- * @param path the path to the lease
- * @return true if lease is removed
- */
- boolean removePermanentLease(final CloudnamePath path);
-
- /**
- * Update data on permanent lease.
- *
- * @param path path to the permanent lease
- * @param data data to write to the lease
- * @return true if successful
- */
- boolean writePermanentLeaseData(final CloudnamePath path, final String data);
-
- /**
- * Read data from permanent lease.
- *
- * @param path path to permanent lease
- * @return data stored in lease or null if the lease doesn't exist
- */
- String readPermanentLeaseData(final CloudnamePath path);
+ void addLeaseCollectionListener(final CloudnamePath pathToWatch, final LeaseListener listener);
/**
- * Add a listener to a permanent lease. The listener is attached to just one lease, as opposed
- * to the termporary lease listener.
+ * Listen to a single lease.
*
* @param pathToObserve path to lease
* @param listener callbacks on this listener is invoked by the backend
*/
- void addPermanentLeaseListener(final CloudnamePath pathToObserve, final LeaseListener listener);
+ void addLeaseListener(final CloudnamePath pathToObserve, final LeaseListener listener);
/**
* Remove listener on permanent lease. Unknown listeners are ignored by the backend.
* @param listener the listener to remove
*/
- void removePermanentLeaseListener(final LeaseListener listener);
+ void removeLeaseListener(final LeaseListener listener);
}
View
2 cn-core/src/main/java/org/cloudname/core/LeaseHandle.java
@@ -12,7 +12,7 @@
* @param data data to write. Cannot be null.
* @return true if data is written
*/
- boolean writeLeaseData(final String data);
+ boolean writeData(final String data);
/**
* The full path of the lease.
View
14 cn-core/src/main/java/org/cloudname/core/LeaseType.java
@@ -0,0 +1,14 @@
+package org.cloudname.core;
+
+/**
+ * Lease type. There are two kinds of leases:
+ * <ul>
+ * <li>PERMANENT leases which will linger around forever until removed by some client.</li>
+ * <li>TEMPORARY leases which will only exist as long as the client is connected to the
+ * backend.</li>
+ * </ul>
+ */
+public enum LeaseType {
+ PERMANENT,
+ TEMPORARY
+}
View
35 cn-core/src/test/java/org/cloudname/core/BackendManagerTest.java
@@ -28,57 +28,38 @@ public void nullDriverUrl() {
private CloudnameBackend createBackend() {
return new CloudnameBackend() {
@Override
- public LeaseHandle createTemporaryLease(CloudnamePath path, String data) {
+ public LeaseHandle createLease(LeaseType type, CloudnamePath path, String data) {
return null;
}
@Override
- public boolean writeTemporaryLeaseData(CloudnamePath path, String data) {
+ public boolean writeLeaseData(CloudnamePath path, String data) {
return false;
}
@Override
- public String readTemporaryLeaseData(CloudnamePath path) {
+ public String readLeaseData(CloudnamePath path) {
return null;
}
@Override
- public void addTemporaryLeaseListener(CloudnamePath pathToWatch, LeaseListener listener) {
-
- }
-
- @Override
- public void removeTemporaryLeaseListener(LeaseListener listener) {
-
- }
-
- @Override
- public boolean createPermanantLease(CloudnamePath path, String data) {
+ public boolean removeLease(CloudnamePath path) {
return false;
}
@Override
- public boolean removePermanentLease(CloudnamePath path) {
- return false;
- }
-
- @Override
- public boolean writePermanentLeaseData(CloudnamePath path, String data) {
- return false;
- }
+ public void addLeaseCollectionListener(
+ CloudnamePath pathToObserve, LeaseListener listener) {
- @Override
- public String readPermanentLeaseData(CloudnamePath path) {
- return null;
}
@Override
- public void addPermanentLeaseListener(CloudnamePath pathToObserver, LeaseListener listener) {
+ public void addLeaseListener(CloudnamePath pathToObserve, LeaseListener listener) {
}
@Override
- public void removePermanentLeaseListener(LeaseListener listener) {
+ public void removeLeaseListener(LeaseListener listener) {
}
View
202 cn-memory/src/main/java/org/cloudname/backends/memory/MemoryBackend.java
@@ -4,11 +4,11 @@
import org.cloudname.core.CloudnamePath;
import org.cloudname.core.LeaseHandle;
import org.cloudname.core.LeaseListener;
+import org.cloudname.core.LeaseType;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
-import java.util.Random;
import java.util.Set;
/**
@@ -26,60 +26,20 @@
DATA
}
- private final Map<CloudnamePath,String> temporaryLeases = new HashMap<>();
- private final Map<CloudnamePath,String> permanentLeases = new HashMap<>();
- private final Map<CloudnamePath, Set<LeaseListener>> observedTemporaryPaths = new HashMap<>();
- private final Map<CloudnamePath, Set<LeaseListener>> observedPermanentPaths = new HashMap<>();
- private final Object syncObject = new Object();
- private final Random random = new Random();
-
- /* package-private */ void removeTemporaryLease(final CloudnamePath leasePath) {
- synchronized (syncObject) {
- if (temporaryLeases.containsKey(leasePath)) {
- temporaryLeases.remove(leasePath);
- notifyTemporaryObservers(leasePath, LeaseEvent.REMOVED, null);
- }
- }
- }
-
- private String createRandomInstanceName() {
- return Long.toHexString(random.nextLong());
- }
+ private final Map<CloudnamePath, String> leases = new HashMap<>();
- /**
- * Notify observers of changes.
- */
- private void notifyTemporaryObservers(
- final CloudnamePath path, final LeaseEvent event, final String data) {
- for (final CloudnamePath observedPath : observedTemporaryPaths.keySet()) {
- if (observedPath.isSubpathOf(path)) {
- for (final LeaseListener listener : observedTemporaryPaths.get(observedPath)) {
- switch (event) {
- case CREATED:
- listener.leaseCreated(path, data);
- break;
- case REMOVED:
- listener.leaseRemoved(path);
- break;
- case DATA:
- listener.dataChanged(path, data);
- break;
- default:
- throw new RuntimeException("Don't know how to handle " + event);
- }
- }
- }
- }
- }
+ private final Map<CloudnamePath, Set<LeaseListener>> observedPaths = new HashMap<>();
+ private final Object syncObject = new Object();
/**
* Notify observers of changes.
*/
- private void notifyPermanentObservers(
+ private void notifyObservers(
final CloudnamePath path, final LeaseEvent event, final String data) {
- for (final CloudnamePath observedPath : observedPermanentPaths.keySet()) {
+ observedPaths.keySet().forEach((observedPath) -> {
if (observedPath.isSubpathOf(path)) {
- for (final LeaseListener listener : observedPermanentPaths.get(observedPath)) {
+ // The path matches; notify listeners
+ observedPaths.get(observedPath).forEach((listener) -> {
switch (event) {
case CREATED:
listener.leaseCreated(path, data);
@@ -93,163 +53,107 @@ private void notifyPermanentObservers(
default:
throw new RuntimeException("Don't know how to handle " + event);
}
- }
+ });
}
- }
+ });
}
@Override
- public boolean createPermanantLease(final CloudnamePath path, final String data) {
- assert path != null : "Path to lease must be set!";
- assert data != null : "Lease data is required";
- synchronized (syncObject) {
- if (permanentLeases.containsKey(path)) {
- return false;
- }
- permanentLeases.put(path, data);
- notifyPermanentObservers(path, LeaseEvent.CREATED, data);
+ public LeaseHandle createLease(
+ final LeaseType type, final CloudnamePath path, final String data) {
+ if (type == null) {
+ return null;
}
- return true;
- }
-
- @Override
- public boolean removePermanentLease(final CloudnamePath path) {
- synchronized (syncObject) {
- if (!permanentLeases.containsKey(path)) {
- return false;
- }
- permanentLeases.remove(path);
- notifyPermanentObservers(path, LeaseEvent.REMOVED, null);
+ if (path == null) {
+ return null;
}
- return true;
- }
-
- @Override
- public boolean writePermanentLeaseData(final CloudnamePath path, final String data) {
- synchronized (syncObject) {
- if (!permanentLeases.containsKey(path)) {
- return false;
- }
- permanentLeases.put(path, data);
- notifyPermanentObservers(path, LeaseEvent.DATA, data);
+ if (data == null) {
+ return null;
}
- return true;
- }
- @Override
- public String readPermanentLeaseData(final CloudnamePath path) {
synchronized (syncObject) {
- if (!permanentLeases.containsKey(path)) {
+ if (leases.containsKey(path)) {
return null;
}
- return permanentLeases.get(path);
+ leases.put(path, data);
+ notifyObservers(path, LeaseEvent.CREATED, data);
}
+ return new MemoryLeaseHandle(this, path);
}
@Override
- public boolean writeTemporaryLeaseData(final CloudnamePath path, final String data) {
+ public boolean removeLease(final CloudnamePath path) {
synchronized (syncObject) {
- if (!temporaryLeases.containsKey(path)) {
+ if (!leases.containsKey(path)) {
return false;
}
- temporaryLeases.put(path, data);
- notifyTemporaryObservers(path, LeaseEvent.DATA, data);
+ leases.remove(path);
+ notifyObservers(path, LeaseEvent.REMOVED, null);
}
return true;
}
@Override
- public String readTemporaryLeaseData(final CloudnamePath path) {
+ public boolean writeLeaseData(final CloudnamePath path, final String data) {
synchronized (syncObject) {
- if (!temporaryLeases.containsKey(path)) {
- return null;
+ if (!leases.containsKey(path)) {
+ return false;
}
- return temporaryLeases.get(path);
+ leases.put(path, data);
+ notifyObservers(path, LeaseEvent.DATA, data);
}
+ return true;
}
@Override
- public LeaseHandle createTemporaryLease(final CloudnamePath path, final String data) {
+ public String readLeaseData(final CloudnamePath path) {
synchronized (syncObject) {
- final String instanceName = createRandomInstanceName();
- CloudnamePath instancePath = new CloudnamePath(path, instanceName);
- while (temporaryLeases.containsKey(instancePath)) {
- instancePath = new CloudnamePath(path, instanceName);
+ if (!leases.containsKey(path)) {
+ return null;
}
- temporaryLeases.put(instancePath, data);
- notifyTemporaryObservers(instancePath, LeaseEvent.CREATED, data);
- return new MemoryLeaseHandle(this, instancePath);
+ return leases.get(path);
}
}
/**
* Generate created events for temporary leases for newly attached listeners.
*/
- private void regenerateEventsForTemporaryListener(
+ private void regenerateEventsForListeners(
final CloudnamePath path, final LeaseListener listener) {
- temporaryLeases.keySet().forEach((temporaryPath) -> {
+ leases.keySet().forEach((temporaryPath) -> {
if (path.isSubpathOf(temporaryPath)) {
- listener.leaseCreated(temporaryPath, temporaryLeases.get(temporaryPath));
+ listener.leaseCreated(temporaryPath, leases.get(temporaryPath));
}
});
}
- /**
- * Generate created events on permanent leases for newly attached listeners.
- */
- private void regenerateEventsForPermanentListener(
- final CloudnamePath path, final LeaseListener listener) {
- permanentLeases.keySet().forEach((permanentPath) -> {
- if (path.isSubpathOf(permanentPath)) {
- listener.leaseCreated(permanentPath, permanentLeases.get(permanentPath));
- }
- });
- }
-
@Override
- public void addTemporaryLeaseListener(
- final CloudnamePath pathToObserve, final LeaseListener listener) {
+ public void addLeaseListener(final CloudnamePath leaseToObserve, final LeaseListener listener) {
synchronized (syncObject) {
- Set<LeaseListener> listeners = observedTemporaryPaths.get(pathToObserve);
- if (listeners == null) {
- listeners = new HashSet<>();
- }
+ final Set<LeaseListener> listeners
+ = observedPaths.getOrDefault(leaseToObserve, new HashSet<>());
listeners.add(listener);
- observedTemporaryPaths.put(pathToObserve, listeners);
- regenerateEventsForTemporaryListener(pathToObserve, listener);
+ observedPaths.put(leaseToObserve, listeners);
+ regenerateEventsForListeners(leaseToObserve, listener);
}
}
@Override
- public void removeTemporaryLeaseListener(final LeaseListener listener) {
- synchronized (syncObject) {
- for (final Set<LeaseListener> listeners : observedTemporaryPaths.values()) {
- if (listeners.contains(listener)) {
- listeners.remove(listener);
- return;
- }
- }
- }
- }
-
- @Override
- public void addPermanentLeaseListener(
+ public void addLeaseCollectionListener(
final CloudnamePath pathToObserve, final LeaseListener listener) {
synchronized (syncObject) {
- Set<LeaseListener> listeners = observedPermanentPaths.get(pathToObserve);
- if (listeners == null) {
- listeners = new HashSet<>();
- }
+ final Set<LeaseListener> listeners
+ = observedPaths.getOrDefault(pathToObserve, new HashSet<>());
listeners.add(listener);
- observedPermanentPaths.put(pathToObserve, listeners);
- regenerateEventsForPermanentListener(pathToObserve, listener);
+ observedPaths.put(pathToObserve, listeners);
+ regenerateEventsForListeners(pathToObserve, listener);
}
}
@Override
- public void removePermanentLeaseListener(final LeaseListener listener) {
+ public void removeLeaseListener(final LeaseListener listener) {
synchronized (syncObject) {
- for (final Set<LeaseListener> listeners : observedPermanentPaths.values()) {
+ for (final Set<LeaseListener> listeners : observedPaths.values()) {
if (listeners.contains(listener)) {
listeners.remove(listener);
return;
@@ -261,8 +165,8 @@ public void removePermanentLeaseListener(final LeaseListener listener) {
@Override
public void close() {
synchronized (syncObject) {
- observedTemporaryPaths.clear();
- observedPermanentPaths.clear();
+ observedPaths.clear();
+ observedPaths.clear();
}
}
}
View
6 cn-memory/src/main/java/org/cloudname/backends/memory/MemoryLeaseHandle.java
@@ -29,8 +29,8 @@ public MemoryLeaseHandle(final MemoryBackend backend, final CloudnamePath client
}
@Override
- public boolean writeLeaseData(final String data) {
- return backend.writeTemporaryLeaseData(clientLeasePath, data);
+ public boolean writeData(final String data) {
+ return backend.writeLeaseData(clientLeasePath, data);
}
@Override
@@ -43,7 +43,7 @@ public CloudnamePath getLeasePath() {
@Override
public void close() throws IOException {
- backend.removeTemporaryLease(clientLeasePath);
+ backend.removeLease(clientLeasePath);
expired.set(true);
}
}
View
48 cn-service/src/main/java/org/cloudname/service/CloudnameService.java
@@ -4,9 +4,11 @@
import org.cloudname.core.CloudnamePath;
import org.cloudname.core.LeaseHandle;
import org.cloudname.core.LeaseListener;
+import org.cloudname.core.LeaseType;
import java.util.ArrayList;
import java.util.List;
+import java.util.Random;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.logging.Level;
@@ -27,11 +29,13 @@
private final List<LeaseListener> permanentListeners = new ArrayList<>();
private final Set<ServiceCoordinate> permanentUpdatesInProgress = new CopyOnWriteArraySet<>();
private final Object syncObject = new Object();
+ private final Random random = new Random();
+ private static final int MAX_COORDINATE_RETRIES = 10;
/**
* Create the service interface.
*
- * @oaram backend backend implementation to use
+ * @param backend backend implementation to use
* @throws IllegalArgumentException if parameter is invalid
*/
public CloudnameService(final CloudnameBackend backend) {
@@ -60,8 +64,22 @@ public ServiceHandle registerService(
if (serviceData == null) {
throw new IllegalArgumentException("Service Data cannot be null");
}
- final LeaseHandle leaseHandle = backend.createTemporaryLease(
- serviceCoordinate.toCloudnamePath(), serviceData.toJsonString());
+ // Create unique coordinate; the coordinate is just a random number.
+ int numRetries = 0;
+ LeaseHandle leaseHandle = null;
+ while (numRetries < MAX_COORDINATE_RETRIES && leaseHandle == null) {
+ final CloudnamePath newCoordinate = new CloudnamePath(
+ serviceCoordinate.toCloudnamePath(), Long.toHexString(random.nextLong()));
+ leaseHandle = backend.createLease(LeaseType.TEMPORARY,
+ newCoordinate, serviceData.toJsonString());
+ numRetries++;
+ }
+
+ if (numRetries == MAX_COORDINATE_RETRIES && leaseHandle == null) {
+ LOG.severe("Could not find available coordinate after " + MAX_COORDINATE_RETRIES
+ + " for service " + serviceCoordinate);
+ return null;
+ }
final ServiceHandle serviceHandle = new ServiceHandle(
new InstanceCoordinate(leaseHandle.getLeasePath()), serviceData, leaseHandle);
@@ -113,12 +131,15 @@ public void dataChanged(final CloudnamePath path, final String data) {
synchronized (syncObject) {
temporaryListeners.add(leaseListener);
}
- backend.addTemporaryLeaseListener(coordinate.toCloudnamePath(), leaseListener);
+ backend.addLeaseCollectionListener(coordinate.toCloudnamePath(), leaseListener);
}
/**
* Create a permanent service. The service registration will be kept when the client exits. The
* service will have a single endpoint.
+ *
+ * @param coordinate The service's coordinate
+ * @param endpoint Endpoint for service * @return true if service is created
*/
public boolean createPermanentService(
final ServiceCoordinate coordinate, final Endpoint endpoint) {
@@ -129,14 +150,19 @@ public boolean createPermanentService(
throw new IllegalArgumentException("Endpoint can't be null");
}
- return backend.createPermanantLease(coordinate.toCloudnamePath(), endpoint.toJsonString());
+ return (backend.createLease(
+ LeaseType.PERMANENT, coordinate.toCloudnamePath(), endpoint.toJsonString())
+ != null);
}
/**
* Update permanent service coordinate. Note that this is a non-atomic operation with multiple
* trips to the backend system. The update is done in two operations; one delete and one
* create. If the delete operation fail and the create operation succeeds it might end up
* removing the permanent service coordinate. Clients will not be notified of the removal.
+ *
+ * @param coordinate The service's coordinate
+ * @param endpoint The service's endpoint
*/
public boolean updatePermanentService(
final ServiceCoordinate coordinate, final Endpoint endpoint) {
@@ -153,7 +179,7 @@ public boolean updatePermanentService(
return false;
}
// Check if the endpoint name still matches.
- final String data = backend.readPermanentLeaseData(coordinate.toCloudnamePath());
+ final String data = backend.readLeaseData(coordinate.toCloudnamePath());
if (data == null) {
return false;
}
@@ -165,7 +191,7 @@ public boolean updatePermanentService(
}
permanentUpdatesInProgress.add(coordinate);
try {
- return backend.writePermanentLeaseData(
+ return backend.writeLeaseData(
coordinate.toCloudnamePath(), endpoint.toJsonString());
} catch (final RuntimeException ex) {
LOG.log(Level.WARNING, "Got exception updating permanent lease. The system might be in"
@@ -183,7 +209,7 @@ public boolean removePermanentService(final ServiceCoordinate coordinate) {
if (coordinate == null) {
throw new IllegalArgumentException("Coordinate can not be null");
}
- return backend.removePermanentLease(coordinate.toCloudnamePath());
+ return backend.removeLease(coordinate.toCloudnamePath());
}
/**
@@ -217,7 +243,7 @@ public void dataChanged(final CloudnamePath path, final String data) {
synchronized (syncObject) {
permanentListeners.add(leaseListener);
}
- backend.addPermanentLeaseListener(coordinate.toCloudnamePath(), leaseListener);
+ backend.addLeaseListener(coordinate.toCloudnamePath(), leaseListener);
}
@Override
@@ -227,10 +253,10 @@ public void close() {
handle.close();
}
for (final LeaseListener listener : temporaryListeners) {
- backend.removeTemporaryLeaseListener(listener);
+ backend.removeLeaseListener(listener);
}
for (final LeaseListener listener : permanentListeners) {
- backend.removePermanentLeaseListener(listener);
+ backend.removeLeaseListener(listener);
}
}
}
View
4 cn-service/src/main/java/org/cloudname/service/ServiceHandle.java
@@ -51,7 +51,7 @@ public boolean registerEndpoint(final Endpoint endpoint) {
if (!serviceData.addEndpoint(endpoint)) {
return false;
}
- return this.leaseHandle.writeLeaseData(serviceData.toJsonString());
+ return this.leaseHandle.writeData(serviceData.toJsonString());
}
/**
@@ -63,7 +63,7 @@ public boolean removeEndpoint(final Endpoint endpoint) {
if (!serviceData.removeEndpoint(endpoint)) {
return false;
}
- return this.leaseHandle.writeLeaseData(serviceData.toJsonString());
+ return this.leaseHandle.writeData(serviceData.toJsonString());
}
@Override
View
4 cn-service/src/test/java/org/cloudname/service/ServiceHandleTest.java
@@ -20,7 +20,7 @@ public void testCreation() {
final ServiceData serviceData = new ServiceData(new ArrayList<Endpoint>());
final LeaseHandle handle = new LeaseHandle() {
@Override
- public boolean writeLeaseData(String data) {
+ public boolean writeData(String data) {
return true;
}
@@ -57,7 +57,7 @@ public void testFailingHandle() {
final ServiceData serviceData = new ServiceData(Arrays.asList(ep1));
final LeaseHandle handle = new LeaseHandle() {
@Override
- public boolean writeLeaseData(String data) {
+ public boolean writeData(String data) {
return false;
}
View
6 cn-zookeeper/pom.xml
@@ -41,12 +41,6 @@
<dependency>
<groupId>org.apache.curator</groupId>
- <artifactId>curator-recipes</artifactId>
- <version>2.9.0</version>
- </dependency>
-
- <dependency>
- <groupId>org.apache.curator</groupId>
<artifactId>curator-test</artifactId>
<version>2.9.0</version>
<scope>test</scope>
View
313 cn-zookeeper/src/main/java/org/cloudname/backends/zookeeper/ZooKeeperBackend.java
@@ -7,17 +7,16 @@
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
-import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.data.Stat;
import org.cloudname.core.CloudnameBackend;
import org.cloudname.core.CloudnamePath;
import org.cloudname.core.LeaseHandle;
import org.cloudname.core.LeaseListener;
+import org.cloudname.core.LeaseType;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
-import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
@@ -31,15 +30,12 @@
*/
public class ZooKeeperBackend implements CloudnameBackend {
private static final Logger LOG = Logger.getLogger(ZooKeeperBackend.class.getName());
- private static final String TEMPORARY_ROOT = "/cn/temporary/";
- private static final String PERMANENT_ROOT = "/cn/permanent/";
+ private static final String ZK_ROOT = "/cn/";
private static final int CONNECTION_TIMEOUT_SECONDS = 30;
- // PRNG for instance names. These will be "random enough" for instance identifiers
- private final Random random = new Random();
private final CuratorFramework curator;
- private final Map<LeaseListener, NodeCollectionWatcher> clientListeners = new HashMap<>();
- private final Map<LeaseListener, NodeCollectionWatcher> permanentListeners = new HashMap<>();
+ private final Map<LeaseListener, NodeCollectionWatcher> collectionListeners = new HashMap<>();
+ private final Map<LeaseListener, NodeCollectionWatcher> leaseListeners = new HashMap<>();
private final Object syncObject = new Object();
/**
@@ -60,65 +56,8 @@ public ZooKeeperBackend(final String connectionString) {
}
@Override
- public LeaseHandle createTemporaryLease(final CloudnamePath path, final String data) {
- boolean created = false;
- CloudnamePath tempInstancePath = null;
- String tempZkPath = null;
- while (!created) {
- final long instanceId = random.nextLong();
- tempInstancePath = new CloudnamePath(path, Long.toHexString(instanceId));
- tempZkPath = TEMPORARY_ROOT + tempInstancePath.join('/');
- try {
-
- curator.create()
- .creatingParentContainersIfNeeded()
- .withMode(CreateMode.EPHEMERAL)
- .forPath(tempZkPath, data.getBytes(Charsets.UTF_8));
- created = true;
- } catch (final Exception ex) {
- LOG.log(Level.WARNING, "Could not create client node at " + tempInstancePath, ex);
- }
- }
- final CloudnamePath instancePath = tempInstancePath;
- final String zkInstancePath = tempZkPath;
- return new LeaseHandle() {
- private AtomicBoolean closed = new AtomicBoolean(false);
-
- @Override
- public boolean writeLeaseData(final String data) {
- if (closed.get()) {
- LOG.info("Attempt to write data to closed leased handle " + data);
- return false;
- }
- return writeTemporaryLeaseData(instancePath, data);
- }
-
- @Override
- public CloudnamePath getLeasePath() {
- if (closed.get()) {
- return null;
- }
- return instancePath;
- }
-
- @Override
- public void close() throws IOException {
- if (closed.get()) {
- return;
- }
- try {
- curator.delete().forPath(zkInstancePath);
- closed.set(true);
- } catch (final Exception ex) {
- throw new IOException(ex);
- }
- }
- };
- }
-
- @Override
- public boolean writeTemporaryLeaseData(final CloudnamePath path, final String data) {
- final String zkPath = TEMPORARY_ROOT + path.join('/');
+ public boolean writeLeaseData(final CloudnamePath path, final String data) {
+ final String zkPath = ZK_ROOT + path.join('/');
try {
final Stat nodeStat = curator.checkExists().forPath(zkPath);
if (nodeStat == null) {
@@ -135,11 +74,11 @@ public boolean writeTemporaryLeaseData(final CloudnamePath path, final String da
}
@Override
- public String readTemporaryLeaseData(final CloudnamePath path) {
+ public String readLeaseData(final CloudnamePath path) {
if (path == null) {
return null;
}
- final String zkPath = TEMPORARY_ROOT + path.join('/');
+ final String zkPath = ZK_ROOT + path.join('/');
try {
curator.sync().forPath(zkPath);
final byte[] bytes = curator.getData().forPath(zkPath);
@@ -150,20 +89,20 @@ public String readTemporaryLeaseData(final CloudnamePath path) {
return null;
}
- private CloudnamePath toCloudnamePath(final String zkPath, final String pathPrefix) {
- final String clientPath = zkPath.substring(pathPrefix.length());
+ private CloudnamePath toCloudnamePath(final String zkPath) {
+ final String clientPath = zkPath.substring(ZK_ROOT.length());
final String[] elements = clientPath.split("/");
return new CloudnamePath(elements);
}
@Override
- public void addTemporaryLeaseListener(
+ public void addLeaseCollectionListener(
final CloudnamePath pathToObserve, final LeaseListener listener) {
// Ideally the PathChildrenCache class in Curator would be used here to keep track of the
// changes but it is ever so slightly broken and misses most of the watches that ZooKeeper
// triggers, ignores the mzxid on the nodes and generally makes a mess of things. Enter
// custom code.
- final String zkPath = TEMPORARY_ROOT + pathToObserve.join('/');
+ final String zkPath = ZK_ROOT + pathToObserve.join('/');
try {
curator.createContainers(zkPath);
final NodeCollectionWatcher watcher = new NodeCollectionWatcher(
@@ -173,124 +112,33 @@ public void addTemporaryLeaseListener(
@Override
public void nodeCreated(final String path, final String data) {
- listener.leaseCreated(toCloudnamePath(path, TEMPORARY_ROOT), data);
+ listener.leaseCreated(toCloudnamePath(path), data);
}
@Override
public void dataChanged(final String path, final String data) {
- listener.dataChanged(toCloudnamePath(path, TEMPORARY_ROOT), data);
+ listener.dataChanged(toCloudnamePath(path), data);
}
@Override
public void nodeRemoved(final String path) {
- listener.leaseRemoved(toCloudnamePath(path, TEMPORARY_ROOT));
+ listener.leaseRemoved(toCloudnamePath(path));
}
});
synchronized (syncObject) {
- clientListeners.put(listener, watcher);
+ collectionListeners.put(listener, watcher);
}
} catch (final Exception exception) {
LOG.log(Level.WARNING, "Got exception when creating node watcher", exception);
}
}
@Override
- public void removeTemporaryLeaseListener(final LeaseListener listener) {
- synchronized (syncObject) {
- final NodeCollectionWatcher watcher = clientListeners.get(listener);
- if (watcher != null) {
- clientListeners.remove(listener);
- watcher.shutdown();
- }
- }
- }
-
- @Override
- public boolean createPermanantLease(final CloudnamePath path, final String data) {
- final String zkPath = PERMANENT_ROOT + path.join('/');
- try {
- curator.sync().forPath(zkPath);
- final Stat nodeStat = curator.checkExists().forPath(zkPath);
- if (nodeStat == null) {
- curator.create()
- .creatingParentContainersIfNeeded()
- .forPath(zkPath, data.getBytes(Charsets.UTF_8));
- return true;
- }
- LOG.log(Level.INFO, "Attempt to create permanent node at " + path
- + " with data " + data + " but it already exists");
- } catch (final Exception ex) {
- LOG.log(Level.WARNING, "Got exception creating parent container for permanent lease"
- + " for lease " + path + " with data " + data, ex);
- }
- return false;
- }
-
- @Override
- public boolean removePermanentLease(final CloudnamePath path) {
- final String zkPath = PERMANENT_ROOT + path.join('/');
- try {
- final Stat nodeStat = curator.checkExists().forPath(zkPath);
- if (nodeStat != null) {
- curator.delete()
- .withVersion(nodeStat.getVersion())
- .forPath(zkPath);
- return true;
- }
- return false;
- } catch (final Exception ex) {
- LOG.log(Level.WARNING, "Got error removing permanent lease for lease " + path, ex);
- return false;
- }
- }
-
- @Override
- public boolean writePermanentLeaseData(final CloudnamePath path, final String data) {
- final String zkPath = PERMANENT_ROOT + path.join('/');
- try {
- curator.sync().forPath(zkPath);
- final Stat nodeStat = curator.checkExists().forPath(zkPath);
- if (nodeStat == null) {
- LOG.log(Level.WARNING, "Can't write permanent lease data for lease " + path
- + " with data " + data + " since the lease doesn't exist");
- return false;
- }
- curator.setData()
- .withVersion(nodeStat.getVersion())
- .forPath(zkPath, data.getBytes(Charsets.UTF_8));
- } catch (final Exception ex) {
- LOG.log(Level.WARNING, "Got exception writing permanent lease data for " + path
- + " with data " + data, ex);
- return false;
- }
- return true;
- }
-
- @Override
- public String readPermanentLeaseData(final CloudnamePath path) {
- final String zkPath = PERMANENT_ROOT + path.join('/');
- try {
- curator.sync().forPath(zkPath);
- final byte[] bytes = curator.getData().forPath(zkPath);
- return new String(bytes, Charsets.UTF_8);
- } catch (final Exception ex) {
- if (ex instanceof KeeperException.NoNodeException) {
- // OK - nothing to worry about
- return null;
- }
- LOG.log(Level.WARNING, "Got exception reading permanent lease data for " + path, ex);
- return null;
- }
- }
-
- @Override
- public void addPermanentLeaseListener(
- final CloudnamePath pathToObserve, final LeaseListener listener) {
+ public void addLeaseListener(final CloudnamePath leaseToObserve, final LeaseListener listener) {
try {
-
- final String parentPath = PERMANENT_ROOT + pathToObserve.getParent().join('/');
- final String fullPath = PERMANENT_ROOT + pathToObserve.join('/');
+ final String parentPath = ZK_ROOT + leaseToObserve.getParent().join('/');
+ final String fullPath = ZK_ROOT + leaseToObserve.join('/');
curator.createContainers(parentPath);
final NodeCollectionWatcher watcher = new NodeCollectionWatcher(
curator.getZookeeperClient().getZooKeeper(),
@@ -300,55 +148,144 @@ public void addPermanentLeaseListener(
@Override
public void nodeCreated(final String path, final String data) {
if (path.equals(fullPath)) {
- listener.leaseCreated(toCloudnamePath(path, PERMANENT_ROOT), data);
+ listener.leaseCreated(toCloudnamePath(path), data);
}
}
@Override
public void dataChanged(final String path, final String data) {
if (path.equals(fullPath)) {
- listener.dataChanged(toCloudnamePath(path, PERMANENT_ROOT), data);
+ listener.dataChanged(toCloudnamePath(path), data);
}
}
@Override
public void nodeRemoved(final String path) {
if (path.equals(fullPath)) {
- listener.leaseRemoved(toCloudnamePath(path, PERMANENT_ROOT));
+ listener.leaseRemoved(toCloudnamePath(path));
}
}
});
synchronized (syncObject) {
- permanentListeners.put(listener, watcher);
+ leaseListeners.put(listener, watcher);
}
} catch (final Exception exception) {
LOG.log(Level.WARNING, "Got exception when creating node watcher", exception);
}
}
@Override
- public void removePermanentLeaseListener(final LeaseListener listener) {
+ public void removeLeaseListener(final LeaseListener listener) {
synchronized (syncObject) {
- final NodeCollectionWatcher watcher = permanentListeners.get(listener);
- if (watcher != null) {
- permanentListeners.remove(listener);
- watcher.shutdown();
+ final NodeCollectionWatcher collectionWatcher = collectionListeners.get(listener);
+ if (collectionWatcher != null) {
+ collectionListeners.remove(listener);
+ collectionWatcher.shutdown();
+ }
+ final NodeCollectionWatcher leaseWatcher = leaseListeners.get(listener);
+ if (leaseWatcher != null) {
+ leaseListeners.remove(listener);
+ leaseWatcher.shutdown();
}
}
}
@Override
- public void close() {
- synchronized (syncObject) {
- for (final NodeCollectionWatcher watcher : clientListeners.values()) {
- watcher.shutdown();
+ public LeaseHandle createLease(
+ final LeaseType type, final CloudnamePath path, final String data) {
+ if (type == null || path == null || data == null) {
+ return null;
+ }
+
+ final String zkPath = ZK_ROOT + path.join('/');
+ try {
+ curator.sync().forPath(zkPath);
+ final Stat nodeStat = curator.checkExists().forPath(zkPath);
+ if (nodeStat == null) {
+ final CreateMode mode = (type == LeaseType.PERMANENT
+ ? CreateMode.PERSISTENT : CreateMode.EPHEMERAL);
+
+ final String returnedPath = curator.create()
+ .creatingParentContainersIfNeeded()
+ .withMode(mode)
+ .forPath(zkPath, data.getBytes(Charsets.UTF_8));
+
+ if (returnedPath == null) {
+ LOG.warning("Could not create node for path " + path
+ + " - Curator returned null on create()");
+ return null;
+ }
+ return new LeaseHandle() {
+ private AtomicBoolean closed = new AtomicBoolean(false);
+
+ @Override
+ public boolean writeData(final String data) {
+ if (closed.get()) {
+ LOG.info("Attempt to write data to closed leased handle " + data);
+ return false;
+ }
+ return writeLeaseData(path, data);
+ }
+
+ @Override
+ public CloudnamePath getLeasePath() {
+ if (closed.get()) {
+ return null;
+ }
+ return path;
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (type == LeaseType.PERMANENT || closed.get()) {
+ return;
+ }
+ try {
+ curator.delete().forPath(zkPath);
+ closed.set(true);
+ } catch (final Exception ex) {
+ throw new IOException(ex);
+ }
+ }
+ };
}
- clientListeners.clear();
- for (final NodeCollectionWatcher watcher : permanentListeners.values()) {
- watcher.shutdown();
+
+ LOG.log(Level.INFO, "Attempt to create node at " + path
+ + " with data " + data + " but it already exists");
+
+ } catch (final Exception ex) {
+ LOG.log(Level.WARNING, "Got exception creating parent container for lease"
+ + " for lease " + path + " with data " + data, ex);
+ }
+ return null;
+ }
+
+ @Override
+ public boolean removeLease(final CloudnamePath path) {
+ final String zkPath = ZK_ROOT + path.join('/');
+ try {
+ final Stat nodeStat = curator.checkExists().forPath(zkPath);
+ if (nodeStat != null) {
+ curator.delete()
+ .withVersion(nodeStat.getVersion())
+ .forPath(zkPath);
+ return true;
}
- permanentListeners.clear();
+ return false;
+ } catch (final Exception ex) {
+ LOG.log(Level.WARNING, "Got error removing node for lease " + path, ex);
+ return false;
+ }
+ }
+
+ @Override
+ public void close() {
+ synchronized (syncObject) {
+ collectionListeners.values().forEach(NodeCollectionWatcher::shutdown);
+ collectionListeners.clear();
+ leaseListeners.values().forEach(NodeCollectionWatcher::shutdown);
+ leaseListeners.clear();
}
}
}
View
29 pom.xml
@@ -22,6 +22,11 @@
<name>Bjorn Borud</name>
<email>bborud@gmail.com</email>
</developer>
+ <developer>
+ <id>stalehd</id>
+ <name>Staale Dahl</name>
+ <email>stalehd@gmail.com</email>
+ </developer>
</developers>
<scm>
@@ -64,6 +69,18 @@
<module>idgen</module>
</modules>
+ <profiles>
+ <profile>
+ <id>disable-java8-doclint</id>
+ <activation>
+ <jdk>[1.8,)</jdk>
+ </activation>
+ <properties>
+ <additionalparam>-Xdoclint:none</additionalparam>
+ </properties>
+ </profile>
+ </profiles>
+
<build>
<plugins>
<plugin>
@@ -111,6 +128,18 @@
</plugins>
</build>
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>2.10.3</version>
+ <configuration>
+ </configuration>
+ </plugin>
+ </plugins>
+ </reporting>
+
<pluginRepositories>
<pluginRepository>
<id>onejar-maven-plugin.googlecode.com</id>
View
291 testtools/src/main/java/org/cloudname/testtools/backend/CoreBackendTest.java
@@ -13,6 +13,7 @@
import org.cloudname.core.CloudnamePath;
import org.cloudname.core.LeaseHandle;
import org.cloudname.core.LeaseListener;
+import org.cloudname.core.LeaseType;
import org.junit.Test;
import java.util.ArrayList;
@@ -47,45 +48,71 @@
protected int getBackendPropagationTime() {
return 100;
}
+
+ @Test
+ public void leaseCreationNullValues() throws Exception {
+ final CloudnamePath permPath = new CloudnamePath(new String[] {"local", "lease", "perm"});
+ try (final CloudnameBackend backend = getBackend()) {
+ assertThat(backend.createLease(null, null, null), is(nullValue()));
+
+ assertThat(backend.createLease(LeaseType.PERMANENT, null, null),
+ is(nullValue()));
+
+ assertThat(backend.createLease(LeaseType.PERMANENT, permPath, null),
+ is(nullValue()));
+
+ }
+ }
+
+ final AtomicInteger nodeCounter = new AtomicInteger(0);
+
+ private CloudnamePath appendUniqueName(final CloudnamePath path) {
+ return new CloudnamePath(path, "node" + Integer.toHexString(nodeCounter.incrementAndGet()));
+ }
+
/**
* Ensure multiple clients can connect and that leases get an unique path for each client.
*/
@Test
public void temporaryLeaseCreation() throws Exception {
try (final CloudnameBackend backend = getBackend()) {
final String data = Long.toHexString(random.nextLong());
- final LeaseHandle lease = backend.createTemporaryLease(serviceA, data);
+ final LeaseHandle lease = backend.createLease(
+ LeaseType.TEMPORARY, appendUniqueName(serviceA), data);
+
assertThat("Expected lease to be not null", lease, is(notNullValue()));
assertTrue("Expected lease path to be a subpath of the supplied lease (" + serviceA
+ ") but it is " + lease.getLeasePath(),
serviceA.isSubpathOf(lease.getLeasePath()));
assertThat("The temporary lease data can be read",
- backend.readTemporaryLeaseData(lease.getLeasePath()), is(data));
+ backend.readLeaseData(lease.getLeasePath()), is(data));
final String newData = Long.toHexString(random.nextLong());
assertThat("Expected to be able to write lease data but didn't",
- lease.writeLeaseData(newData), is(true));
+ lease.writeData(newData), is(true));
assertThat("Expected to be able to read data back but didn't",
- backend.readTemporaryLeaseData(lease.getLeasePath()), is(newData));
+ backend.readLeaseData(lease.getLeasePath()), is(newData));
lease.close();
assertThat("Expect the lease path to be null", lease.getLeasePath(), is(nullValue()));
assertFalse("Did not expect to be able to write lease data for a closed lease",
- lease.writeLeaseData(Long.toHexString(random.nextLong())));
+ lease.writeData(Long.toHexString(random.nextLong())));
+
assertThat("The temporary lease data can not be read",
- backend.readTemporaryLeaseData(lease.getLeasePath()), is(nullValue()));
+ backend.readLeaseData(lease.getLeasePath()), is(nullValue()));
final int numberOfLeases = 50;
final Set<String> leasePaths = new HashSet<>();
for (int i = 0; i < numberOfLeases; i++) {
final String randomData = Long.toHexString(random.nextLong());
- final LeaseHandle handle = backend.createTemporaryLease(serviceB, randomData);
+ final LeaseHandle handle = backend.createLease(
+ LeaseType.TEMPORARY, appendUniqueName(serviceB), randomData);
leasePaths.add(handle.getLeasePath().join(':'));
handle.close();
}
@@ -131,24 +158,24 @@ public void dataChanged(final CloudnamePath path, final String data) {
dataCounter.countDown();
}
};
- backend.addTemporaryLeaseListener(rootPath, listener);
- final LeaseHandle handle = backend.createTemporaryLease(rootPath, firstData);
+ backend.addLeaseCollectionListener(rootPath, listener);
+ final LeaseHandle handle = backend.createLease(
+ LeaseType.TEMPORARY, appendUniqueName(rootPath), firstData);
assertThat(handle, is(notNullValue()));
Thread.sleep(getBackendPropagationTime());
+ assertTrue("Expected create notification but didn't get one",
+ createCounter.await(getBackendPropagationTime(), TimeUnit.MILLISECONDS));
- handle.writeLeaseData(lastData);
+ handle.writeData(lastData);
Thread.sleep(getBackendPropagationTime());
+ assertTrue("Expected data notification but didn't get one",
+ dataCounter.await(getBackendPropagationTime(), TimeUnit.MILLISECONDS));
handle.close();
-
- assertTrue("Expected create notification but didn't get one",
- createCounter.await(getBackendPropagationTime(), TimeUnit.MILLISECONDS));
+ Thread.sleep(getBackendPropagationTime());
assertTrue("Expected remove notification but didn't get one",
removeCounter.await(getBackendPropagationTime(), TimeUnit.MILLISECONDS));
- assertTrue("Expected data notification but didn't get one",
- dataCounter.await(getBackendPropagationTime(), TimeUnit.MILLISECONDS));
-
- backend.removeTemporaryLeaseListener(listener);
+ backend.removeLeaseListener(listener);
}
}
@@ -164,33 +191,35 @@ public void permanentLeaseCreation() throws Exception {
try (final CloudnameBackend backend = getBackend()) {
- backend.removePermanentLease(leasePath);
+ backend.removeLease(leasePath);
- assertThat("Permanent lease can be created",
- backend.createPermanantLease(leasePath, dataString), is(true));
+ assertThat("Expect to be able to create permanent lease",
+ backend.createLease(LeaseType.PERMANENT, leasePath, dataString),
+ is(notNullValue()));
- assertThat("Permanent lease data can be read",
- backend.readPermanentLeaseData(leasePath), is(dataString));
+ assertThat("Expect to be able to read lease's data",
+ backend.readLeaseData(leasePath), is(dataString));
- assertThat("Permanent lease can't be created twice",
- backend.createPermanantLease(leasePath, dataString), is(false));
+ assertThat("Expect permanent lease to be created only once",
+ backend.createLease(LeaseType.PERMANENT, leasePath, dataString),
+ is(nullValue()));
- assertThat("Permanent lease can be updated",
- backend.writePermanentLeaseData(leasePath, newDataString), is(true));
+ assertThat("Expect to be able to write lease data",
+ backend.writeLeaseData(leasePath, newDataString), is(true));
- assertThat("Permanent lease data can be read after update",
- backend.readPermanentLeaseData(leasePath), is(newDataString));
+ assertThat("Expect to be able to read after write",
+ backend.readLeaseData(leasePath), is(newDataString));
}
try (final CloudnameBackend backend = getBackend()) {
assertThat("Permanent lease data can be read from another backend",
- backend.readPermanentLeaseData(leasePath), is(newDataString));
+ backend.readLeaseData(leasePath), is(newDataString));
assertThat("Permanent lease can be removed",
- backend.removePermanentLease(leasePath), is(true));
+ backend.removeLease(leasePath), is(true));
assertThat("Lease can't be removed twice",
- backend.removePermanentLease(leasePath), is(false));
+ backend.removeLease(leasePath), is(false));
assertThat("Lease data can't be read from deleted lease",
- backend.readPermanentLeaseData(leasePath), is(nullValue()));
+ backend.readLeaseData(leasePath), is(nullValue()));
}
}
@@ -203,7 +232,8 @@ public void multipleTemporaryNotifications() throws Exception {
final CloudnamePath rootPath = new CloudnamePath(new String[]{"root", "lease"});
final String clientData = "client data here";
- final LeaseHandle lease = backend.createTemporaryLease(rootPath, clientData);
+ final LeaseHandle lease = backend.createLease(
+ LeaseType.TEMPORARY, appendUniqueName(rootPath), clientData);
assertThat("Handle to lease is returned", lease, is(notNullValue()));
assertThat("Lease is a child of the root lease",
rootPath.isSubpathOf(lease.getLeasePath()), is(true));
@@ -239,13 +269,13 @@ public void dataChanged(final CloudnamePath path, final String data) {
}
};
listeners.add(listener);
- backend.addTemporaryLeaseListener(rootPath, listener);
+ backend.addLeaseCollectionListener(rootPath, listener);
}
// Change the data a few times. Every change should be propagated to the listeners
// in the same order they have changed
for (int i = 0; i < numUpdates; i++) {
- lease.writeLeaseData(Integer.toString(i));
+ lease.writeData(Integer.toString(i));
Thread.sleep(getBackendPropagationTime());
}
@@ -270,7 +300,7 @@ public void dataChanged(final CloudnamePath path, final String data) {
// Remove the listeners
for (final LeaseListener listener : listeners) {
lease.close();
- backend.removeTemporaryLeaseListener(listener);
+ backend.removeLeaseListener(listener);
}
}
}
@@ -296,62 +326,60 @@ public void multipleServicesWithMultipleClients() throws Exception {
final int n = numberOfClients - 1;
final CountDownLatch removeNotifications = new CountDownLatch(n * (n + 1) / 2);
- final Runnable clientProcess = new Runnable() {
- @Override
- public void run() {
- final String myData = Long.toHexString(random.nextLong());
- final LeaseHandle handle = backend.createTemporaryLease(rootLease, myData);
- assertThat("Got a valid handle back", handle, is(notNullValue()));
- backend.addTemporaryLeaseListener(rootLease, new LeaseListener() {
- @Override
- public void leaseCreated(final CloudnamePath path, final String data) {
- assertThat("Notification belongs to root path",
- rootLease.isSubpathOf(path), is(true));
- createNotifications.countDown();
- }
-
- @Override
- public void leaseRemoved(final CloudnamePath path) {
- removeNotifications.countDown();
- }
+ final Runnable clientProcess = () -> {
+ final String myData = Long.toHexString(random.nextLong());
+ final LeaseHandle handle = backend.createLease(
+ LeaseType.TEMPORARY, appendUniqueName(rootLease), myData);
+ assertThat("Got a valid handle back", handle, is(notNullValue()));
+ backend.addLeaseCollectionListener(rootLease, new LeaseListener() {
+ @Override
+ public void leaseCreated(final CloudnamePath path, final String data) {
+ assertThat("Notification belongs to root path",
+ rootLease.isSubpathOf(path), is(true));
+ createNotifications.countDown();
+ }
- @Override
- public void dataChanged(final CloudnamePath path, final String data) {
- dataNotifications.countDown();
- }
- });
+ @Override
+ public void leaseRemoved(final CloudnamePath path) {
+ removeNotifications.countDown();
+ }
- try {
- assertThat(createNotifications.await(
- getBackendPropagationTime(), TimeUnit.MILLISECONDS),
- is(true));
- } catch (InterruptedException ie) {
- throw new RuntimeException(ie);
+ @Override
+ public void dataChanged(final CloudnamePath path, final String data) {
+ dataNotifications.countDown();
}
+ });
- // Change the data for my own lease, wait for it to propagate
- assertThat(handle.writeLeaseData(Long.toHexString(random.nextLong())),
+ try {
+ assertThat(createNotifications.await(
+ getBackendPropagationTime(), TimeUnit.MILLISECONDS),
is(true));
- try {
- Thread.sleep(getBackendPropagationTime());
- } catch (final InterruptedException ie) {
- throw new RuntimeException(ie);
- }
+ } catch (InterruptedException ie) {
+ throw new RuntimeException(ie);
+ }
- try {
- assertThat(dataNotifications.await(
- getBackendPropagationTime(), TimeUnit.MILLISECONDS),
- is(true));
- } catch (InterruptedException ie) {
- throw new RuntimeException(ie);
- }
+ // Change the data for my own lease, wait for it to propagate
+ assertThat(handle.writeData(Long.toHexString(random.nextLong())),
+ is(true));
+ try {
+ Thread.sleep(getBackendPropagationTime());
+ } catch (final InterruptedException ie) {
+ throw new RuntimeException(ie);
+ }
- // ..and close my lease
- try {
- handle.close();
- } catch (Exception ex) {
- throw new RuntimeException(ex);
- }
+ try {
+ assertThat(dataNotifications.await(
+ getBackendPropagationTime(), TimeUnit.MILLISECONDS),
+ is(true));
+ } catch (InterruptedException ie) {
+ throw new RuntimeException(ie);
+ }
+
+ // ..and close my lease
+ try {
+ handle.close();
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
}
};
@@ -383,7 +411,7 @@ public void leaseRemoved(final CloudnamePath path) {
public void dataChanged(final CloudnamePath path, final String data) {
}
};
- backend.removeTemporaryLeaseListener(unknownnListener);
+ backend.removeLeaseListener(unknownnListener);
}
}
@@ -427,21 +455,24 @@ public void dataChanged(final CloudnamePath path, final String data) {
}
public void createLease() {
- backend.addTemporaryLeaseListener(rootPath, listener);
+ backend.addLeaseCollectionListener(rootPath, listener);
try {
Thread.sleep(getBackendPropagationTime());
} catch (final InterruptedException ie) {
throw new RuntimeException(ie);
}
- handle = backend.createTemporaryLease(rootPath, id);
+ handle = backend.createLease(
+ LeaseType.TEMPORARY, appendUniqueName(rootPath), id);
}
public void writeData() {
- handle.writeLeaseData(id);
+ handle.writeData(id);
}
public void checkNumberOfNotifications() {
- // There will be two notifications; one for this lease, one for the other
+ // There will be either two or three notifications; one for this lease and one
+ // for the other with possibly one more for the root path if the backend uses
+ // a tree structure
assertThat("Expected 2 create notifications", createNotifications.get(), is(2));
// There will be two notifications; one for this lease, one for the other
assertThat("Expected 2 data notifications", dataNotifications.get(), is(2));
@@ -468,16 +499,10 @@ public void closeLease() {
workers.add(leaseWorker2);
}
- for (final LeaseWorker worker : workers) {
- worker.writeData();
- }
+ workers.forEach(LeaseWorker::writeData);
Thread.sleep(getBackendPropagationTime());
- for (final LeaseWorker worker : workers) {
- worker.checkNumberOfNotifications();
- }
- for (final LeaseWorker worker : workers) {
- worker.closeLease();
- }
+ workers.forEach(LeaseWorker::checkNumberOfNotifications);
+ workers.forEach(LeaseWorker::closeLease);
}
}
@@ -491,9 +516,10 @@ public void permanentLeaseNotifications() throws Exception {
final String newLeaseData = "popcultural reference";
try (final CloudnameBackend backend = getBackend()) {
- backend.removePermanentLease(rootLease);
+ backend.removeLease(rootLease);
assertThat("Can create permanent node",
- backend.createPermanantLease(rootLease, leaseData), is(true));
+ backend.createLease(LeaseType.PERMANENT, rootLease, leaseData),
+ is(notNullValue()));
}
final AtomicInteger numberOfNotifications = new AtomicInteger(0);
@@ -529,19 +555,19 @@ public void dataChanged(final CloudnamePath path, final String data) {
try (final CloudnameBackend backend = getBackend()) {
assertThat("Lease still exists",
- backend.readPermanentLeaseData(rootLease), is(leaseData));
+ backend.readLeaseData(rootLease), is(leaseData));
// Add the lease back
- backend.addPermanentLeaseListener(rootLease, listener);
+ backend.addLeaseListener(rootLease, listener);
Thread.sleep(getBackendPropagationTime());
assertThat("New data can be written",
- backend.writePermanentLeaseData(rootLease, newLeaseData), is(true));
+ backend.writeLeaseData(rootLease, newLeaseData), is(true));
Thread.sleep(getBackendPropagationTime());
// Write new data
- assertThat("Lease can be removed", backend.removePermanentLease(rootLease), is(true));
+ assertThat("Lease can be removed", backend.removeLease(rootLease), is(true));
assertTrue(createLatch.await(getBackendPropagationTime(), TimeUnit.MILLISECONDS));
assertTrue(dataLatch.await(getBackendPropagationTime(), TimeUnit.MILLISECONDS));
@@ -550,9 +576,9 @@ public void dataChanged(final CloudnamePath path, final String data) {
assertThat("One notifications is expected but only got "
+ numberOfNotifications.get(), numberOfNotifications.get(), is(3));
- backend.removePermanentLeaseListener(listener);
+ backend.removeLeaseListener(listener);
// just to be sure - this won't upset anything
- backend.removePermanentLeaseListener(listener);
+ backend.removeLeaseListener(listener);
}
}