/*
* Copyright (c) 2008-2012 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.coordinator.client.service.impl;
import java.nio.charset.Charset;
import java.util.List;
import java.util.UUID;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.coordinator.client.service.DistributedPersistentLock;
import com.emc.storageos.coordinator.common.impl.ZkConnection;
import com.emc.storageos.coordinator.exceptions.CoordinatorException;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.utils.EnsurePath;
import org.apache.curator.utils.ZKPaths;
/**
* ZK backed distributed persistent lock implementation.
*
* - It survives client reboots.
* - It has to be explicitly released.
* - It is not re-entrant ... use getLockOwner(), to determine current owner.
*
* Implementation details: A persistent lock comprises a root (parent) ZNode and a child ZNode.
*
* - RootZNode is created with the requested persistent lock name.
* => Since the node has a fixed name, only one thread wins the acquireLock race, others get NodeExistsException.
* => This node alone does not suffice to handle releaseLock races.
* - ChildZNode is created with a randomUUID and is used to store owner information.
* => Created as a child of RootZNode
* => A randomUUID is used to be able to provide a given ZNode unique identification, which enables
* better handling of releaseLock races.
* => Owner information is used to verify ownership during releaseLock operations.
* - Attempt is made to make AcquireLock and ReleaseLock atomic.
*/
public class DistributedPersistentLockImpl implements DistributedPersistentLock {
private static final Logger _log = LoggerFactory.getLogger(DistributedPersistentLockImpl.class);
private final CuratorFramework _zkClient;
private final String _persistentLockPath;
private final String _persistentLockName;
/**
* Constructor
*
* @param conn ZK connection
* @param path ZK path under which persistent locks are created
* @param name Name of the persistent lock
*/
public DistributedPersistentLockImpl(final ZkConnection conn, final String path, final String name) {
_zkClient = conn.curator();
_persistentLockPath = path;
_persistentLockName = name;
}
@Override
public synchronized void start() throws Exception {
EnsurePath path = new EnsurePath(_persistentLockPath);
path.ensure(_zkClient.getZookeeperClient());
}
@Override
public synchronized void stop() {
}
@Override
public boolean acquireLock(final String clientName) throws Exception {
boolean bLockAcquired;
_log.debug("acquireLock(): Client: {} wants to acquire lock: {}", clientName, _persistentLockName);
if (clientName == null) {
throw CoordinatorException.fatals.clientNameCannotBeNull();
}
_log.debug("acquireLock(): Creating ZNodes...");
bLockAcquired = createZNodes(clientName);
_log.debug("acquireLock(): Completed: {}", bLockAcquired);
return bLockAcquired;
}
@Override
public boolean releaseLock(final String clientName) throws Exception {
boolean bLockReleased;
_log.debug("releaseLock(): Client: {} wants to releaseLock lock: {}", clientName, _persistentLockName);
if (clientName == null) {
throw CoordinatorException.fatals.clientNameCannotBeNull();
}
_log.debug("releaseLock(): Deleting ZNodes...");
bLockReleased = deleteZNodes(clientName);
_log.debug("releaseLock(): Completed: {}", bLockReleased);
return bLockReleased;
}
@Override
public String getLockOwner() throws Exception {
String currOwnerName = null;
try {
_log.debug("getLockOwner(): For lock: {}", _persistentLockName);
String lockRootPath = ZKPaths.makePath(_persistentLockPath, _persistentLockName);
List<String> children = _zkClient.getChildren().forPath(lockRootPath);
String versionId = children.get(0);
String lockPath = ZKPaths.makePath(lockRootPath, versionId);
byte[] currOwnerNameInBytes = _zkClient.getData().forPath(lockPath);
currOwnerName = new String(currOwnerNameInBytes, Charset.forName("UTF-8"));
} catch (KeeperException.NoNodeException e) {
_log.debug("getLockOwner(): lock {} doesn't exist", _persistentLockName);
} catch (Exception e) {
_log.debug("getLockOwner(): Problem getting ZNodes for Lock {} ... could not determine owner",
_persistentLockName, e);
}
return currOwnerName;
}
/**
* Creates the ZNodes
*
* @param clientName
* @return true, if success; false otherwise
*/
private boolean createZNodes(final String clientName) {
boolean bZNodesCreated = false;
try {
byte[] clientNameInBytes = clientName.getBytes(Charset.forName("UTF-8"));
String versionId = UUID.randomUUID().toString();
String lockRootPath = ZKPaths.makePath(_persistentLockPath, _persistentLockName);
String lockPath = ZKPaths.makePath(lockRootPath, versionId);
_zkClient.inTransaction().create().withMode(CreateMode.PERSISTENT).forPath(lockRootPath).and()
.create().withMode(CreateMode.PERSISTENT).forPath(lockPath, clientNameInBytes).and()
.commit();
bZNodesCreated = true;
} catch (KeeperException.NodeExistsException nee) {
_log.debug("createZNodes(): For lock: {}, ZNodes already exist", _persistentLockName, nee);
try {
if (clientName.equals(getLockOwner())) {
bZNodesCreated = true;
_log.debug("createZNodes(): owner is trying to create {} again.", _persistentLockName);
}
} catch (Exception e) {
_log.warn("createZNodes(): Problem while getting ZNodes: {}", _persistentLockName, e);
}
} catch (Exception e) {
_log.warn("createZNodes(): Problem while creating ZNodes: {}", _persistentLockName, e);
}
_log.debug("createZNodes(): Result: {}", bZNodesCreated);
return bZNodesCreated;
}
/**
* Deletes the ZNodes
*
* @param clientName
* @return true, if success; false otherwise
*/
private boolean deleteZNodes(final String clientName) {
boolean bZNodesDeleted = false;
try {
String lockRootPath = ZKPaths.makePath(_persistentLockPath, _persistentLockName);
List<String> children = _zkClient.getChildren().forPath(lockRootPath);
String versionId = children.get(0);
_log.debug("deleteZNodes(): For lock: {}, Found ChildZNode.", _persistentLockName);
String lockPath = ZKPaths.makePath(lockRootPath, versionId);
byte[] currOwnerNameInBytes = _zkClient.getData().forPath(lockPath);
String currOwnerName = new String(currOwnerNameInBytes, Charset.forName("UTF-8"));
if (currOwnerName.equals(clientName)) {
_log.debug("deleteZNodes(): For lock: {}, Verified owner. Deleting ZNodes", _persistentLockName);
_zkClient.inTransaction().delete().forPath(lockPath).and()
.delete().forPath(lockRootPath).and().commit();
bZNodesDeleted = true;
} else {
_log.debug("deleteZNodes(): For lock: {}, Cannot delete ZNodes ... Invalid owner.",
_persistentLockName);
}
} catch (KeeperException.NoNodeException nne) {
_log.debug("deleteZNodes(): For lock: {}, ZNodes not found.", _persistentLockName);
bZNodesDeleted = true;
} catch (Exception e) {
_log.debug("deleteZNodes(): For lock: {}, Problem while deleting ZNodes", _persistentLockName);
}
_log.debug("deleteZNodes(): Result: {}", bZNodesDeleted);
return bZNodesDeleted;
}
}