/**
* VMware Continuent Tungsten Replicator
* Copyright (C) 2015 VMware, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Initial developer(s): Ed Archibald
* Contributor(s): Linas Virbalas
*/
package com.continuent.tungsten.common.directory;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import java.util.Vector;
import org.apache.log4j.Logger;
import com.continuent.tungsten.common.cluster.resource.Resource;
import com.continuent.tungsten.common.cluster.resource.ResourceState;
import com.continuent.tungsten.common.cluster.resource.ResourceType;
import com.continuent.tungsten.common.cluster.resource.notification.ClusterResourceNotification;
import com.continuent.tungsten.common.cluster.resource.notification.DirectoryNotification;
import com.continuent.tungsten.common.config.TungstenProperties;
import com.continuent.tungsten.common.exception.DirectoryException;
import com.continuent.tungsten.common.exception.DirectoryNotFoundException;
import com.continuent.tungsten.common.exception.ResourceException;
import com.continuent.tungsten.common.exec.ProcessExecutor;
import com.continuent.tungsten.common.patterns.notification.NotificationGroupMember;
import com.continuent.tungsten.common.patterns.notification.ResourceNotificationException;
import com.continuent.tungsten.common.patterns.notification.ResourceNotificationListener;
import com.continuent.tungsten.common.patterns.notification.ResourceNotifier;
import com.continuent.tungsten.common.utils.CLUtils;
import com.continuent.tungsten.common.utils.Command;
import com.continuent.tungsten.common.utils.CommandLineParser;
import com.continuent.tungsten.manager.resource.physical.Process;
import com.continuent.tungsten.manager.resource.physical.Operation;
import com.continuent.tungsten.manager.resource.physical.ResourceFactory;
import com.continuent.tungsten.manager.resource.shared.ResourceConfiguration;
import com.continuent.tungsten.manager.resource.shared.RootResource;
/**
* This class provides a means to organize cluster resources in an intuitive,
* hierarchical form. It allows us to, effectively, extend the resources that
* can be referred to, directly, in the Tungsten ResourceManager.
*
* @author <a href="mailto:edward.archibald@continuent.com">Ed Archibald</a>
* @version 1.0
*/
public class Directory extends ResourceTree
implements
Serializable,
ResourceNotifier
{
/**
*
*/
private static Logger logger = Logger.getLogger(Directory.class);
private static final long serialVersionUID = 1L;
public final static String DEFAULT_CLUSTER_NAME = "default";
public final static String PROTOCOL = "resource:";
public final static String ROOT_ELEMENT = "/";
public final static String CURRENT_ELEMENT = ".";
public final static String PARENT_ELEMENT = "..";
public final static String ANY_ELEMENT = "*";
public final static String AMPERSAND = "&";
public final static String PATH_SEPARATOR = "/";
public static final String DIRECTORY = "directory";
public static final String EXECUTE = "execute";
public static final String LIST = "ls";
public static final String CD = "cd";
public static final String CP = "cp";
public static final String RM = "rm";
public static final String CREATE = "create";
public static final String PWD = "pwd";
public static final String CHKEXEC = "chkexec";
public static final String WHICH = "which";
public static final String CONNECT = "connect";
public static final String DISCONNECT = "disconnect";
public static final String DISCONNECT_ALL = "disconnectAll";
public static final String MERGE = "merge";
public static final String SERVICE = "service";
public static final String EXTENSION = "extension";
public static final String LOCATE_SERVICE = "locateServices";
public static final char FLAG_LONG = 'l';
public static final char FLAG_RECURSIVE = 'R';
public static final char FLAG_ABSOLUTE = 'A';
public static final char FLAG_PARENTS = 'p';
public static final String KEY_COMMAND = "command";
public static final String[] directoryCommands = {
LIST, CD, CP, RM, CREATE, PWD, EXECUTE, CHKEXEC, WHICH, CONNECT,
MERGE, SERVICE, DISCONNECT, DISCONNECT_ALL, LOCATE_SERVICE };
protected String clusterName = null;
protected String memberName = null;
protected boolean recursive = false;
protected boolean absolute = false;
protected boolean detailed = false;
protected boolean createParents = false;
protected static ArrayList<ResourceNotificationListener> listeners = new ArrayList<ResourceNotificationListener>();
protected Map<String, DirectorySession> sessionsByID = new HashMap<String, DirectorySession>();
// member <connectionID, list<sessions>>
protected Map<String, Map<Long, Vector<String>>> sessionsByDomain = new HashMap<String, Map<Long, Vector<String>>>();
protected String systemSessionID = null;
// Services folders for each node, with the key being the node name.
protected Map<String, ResourceNode> servicesFolders = new TreeMap<String, ResourceNode>();
// This gets incremented for any changes to this instance.
protected Long currentVersion = 01L;
protected Long lastMergedVersion = 0L;
protected boolean merging = false;
private static Directory _instance = null;
private static CommandLineParser parser = new CommandLineParser();
public Directory()
{
}
/**
* Returns a global instance. This is the standard method to access a single
* process-wide directory.
*
* @param clusterName Name of the cluster
* @param memberName Name of the cluster member
* @return Returns the global directory
* @throws ResourceException
* @throws DirectoryNotFoundException
*/
public static Directory getInstance(String clusterName, String memberName)
throws ResourceException, DirectoryNotFoundException
{
if (_instance == null)
{
_instance = new Directory(clusterName, memberName);
}
return _instance;
}
/**
* Creates a local directory instance that caller is responsible for
* managing. This method allows creation of directories for unit tests.
*
* @param clusterName Name of the cluster
* @param memberName Name of the cluster member
* @return Returns A new directory instance
* @throws ResourceException
* @throws DirectoryNotFoundException
*/
public static Directory createLocalInstance(String clusterName,
String memberName) throws ResourceException,
DirectoryNotFoundException
{
return new Directory(clusterName, memberName);
}
/**
* Creates an instance of Directory with some base-level resources.
*
* @param memberName
* @throws ResourceException
*/
private Directory(String clusterName, String memberName)
throws ResourceException, DirectoryNotFoundException
{
this.clusterName = clusterName;
this.memberName = memberName;
rootNode = new ResourceNode(new RootResource());
setRootNode(rootNode);
String sessionID = UUID.randomUUID().toString();
systemSessionID = connect(memberName, 0L, sessionID);
systemSessionID = sessionID;
ResourceNode defaultService = ResourceFactory.addInstance(
ResourceType.CLUSTER, clusterName, getRootNode(), this,
systemSessionID);
ResourceNode host = ResourceFactory.addInstance(ResourceType.MANAGER,
memberName, defaultService, this, systemSessionID);
ResourceNode confFolder = ResourceFactory.addInstance(
ResourceType.FOLDER, "conf", host, this, systemSessionID);
// Hold config of executable services (replicator, connector, etc.)
ResourceNode serviceFolder = ResourceFactory.addInstance(
ResourceType.FOLDER, ResourceType.SERVICE.toString()
.toLowerCase(), confFolder, this, systemSessionID);
servicesFolders.put(memberName, serviceFolder);
}
public synchronized String connect(String domain, long handle,
String sessionID) throws DirectoryNotFoundException
{
synchronized (sessionsByDomain)
{
Map<Long, Vector<String>> domainSessions = sessionsByDomain
.get(domain);
if (domainSessions == null)
{
domainSessions = new HashMap<Long, Vector<String>>();
sessionsByDomain.put(domain, domainSessions);
}
Vector<String> sessions = domainSessions.get(handle);
if (sessions == null)
{
sessions = new Vector<String>();
domainSessions.put(handle, sessions);
}
DirectorySession session = null;
try
{
session = getSession(sessionID);
}
catch (DirectoryNotFoundException ignored)
{
}
if (session == null)
{
session = newSession(sessionID);
sessions.add(session.getSessionID());
if (logger.isDebugEnabled())
{
logger.debug(String
.format("Created new session for domain %s %s for connectionID=%d",
domain, sessionID, handle));
}
return session.getSessionID();
}
else
{
logger.warn(String.format(
"Directory session %s %s already exists", domain,
sessionID));
return session.getSessionID();
}
}
}
public synchronized void disconnect(String domain, long handle,
String sessionID)
{
Map<Long, Vector<String>> domainSessions = sessionsByDomain.get(domain);
if (domainSessions == null)
{
return;
}
Vector<String> sessions = domainSessions.get(handle);
if (sessions == null)
return;
disconnect(sessionID);
if (sessions.remove(sessionID))
{
if (logger.isDebugEnabled())
{
logger.debug(String
.format("Removed active session for domain %s %s for connection %d",
domain, sessionID, handle));
}
}
if (sessions.isEmpty())
{
if (logger.isDebugEnabled())
{
logger.debug(String.format(
"Clearing session storage storage for connection %d",
handle));
}
sessions.remove(handle);
}
if (domainSessions.isEmpty())
{
if (logger.isInfoEnabled())
logger.info(String.format(
"Clearing session storage for domain %s", domain));
sessionsByDomain.remove(domain);
}
}
public synchronized void disconnectAll(String domain, long handle)
{
Map<Long, Vector<String>> domainSessions = sessionsByDomain.get(domain);
if (domainSessions == null)
{
return;
}
Vector<String> sessions = domainSessions.get(handle);
if (sessions == null)
return;
Vector<String> sessionsToRemove = new Vector<String>(sessions);
for (String sessionID : sessionsToRemove)
{
disconnect(domain, handle, sessionID);
}
}
public synchronized void disconnectAll(String domain)
{
Map<Long, Vector<String>> domainSessions = sessionsByDomain.get(domain);
if (domainSessions == null)
{
return;
}
logger.info(String.format("Removing all sessions for domain '%s'",
domain));
synchronized (sessionsByDomain)
{
Vector<Long> handlesToRemove = new Vector<Long>(
domainSessions.keySet());
for (Long handle : handlesToRemove)
{
disconnectAll(domain, handle);
}
}
if (logger.isInfoEnabled())
logger.info(String.format("Clearing session storage for domain %s",
domain));
sessionsByDomain.remove(domain);
}
/**
* Creates a new Directory session. This session becomes a context for a
* given directory user and is used, primarily, to track the current working
* resource. In most cases, this functionality will be used for interactive
* applications where a user uses 'cd' to set a context.
*
* @param sessionID
* @return returns a string identifier for a new session
*/
private DirectorySession newSession(String sessionID)
{
DirectorySession session = null;
synchronized (sessionsByID)
{
session = new DirectorySession(this, sessionID, getRootNode());
sessionsByID.put(session.getSessionID(), session);
}
return session;
}
/**
* Returns an existing session or throws an exception.
*
* @param sessionID
* @return a context for a given session
* @throws DirectoryNotFoundException
*/
public DirectorySession getSession(String sessionID)
throws DirectoryNotFoundException
{
DirectorySession session = null;
if (sessionID == null)
{
throw new DirectoryNotFoundException(
"null sessionID is not allowed");
}
synchronized (sessionsByID)
{
session = sessionsByID.get(sessionID);
if (session == null)
{
throw new DirectoryNotFoundException(String.format(
"Session %s not found", sessionID));
}
}
session.setLastTimeAccessed(System.currentTimeMillis());
return session;
}
/**
* Removes an existing Directory session.
*
* @param sessionID
* @throws DirectoryException
*/
public synchronized void removeSession(String sessionID)
throws DirectoryException
{
DirectorySession session = null;
synchronized (sessionsByID)
{
session = sessionsByID.get(sessionID);
if (session == null)
{
throw new DirectoryException(String.format(
"Directory session '%s' not found", sessionID));
}
sessionsByID.remove(sessionID);
}
}
/**
* This is the main interface for the directory with respect to text-based
* operations.
*
* @param sessionID
* @return the output for a given command
* @throws Exception
*/
public Serializable processCommand(String sessionID, String commandLine)
throws Exception
{
Command cmd = parser.parseOne(commandLine);
if (cmd == null)
{
throw new Exception("Cannot execute null command");
}
String command = cmd.getTokens()[0];
String[] params = getParams(cmd.getTokens(), true);
String result = null;
if (command.equals(CD))
{
String path = (params != null ? params[0] : null);
cd(sessionID, path);
}
else if (command.equals(LIST))
{
String path = (params != null ? params[0] : null);
ResourceNode startNode = getStartNode(sessionID, path);
List<ResourceNode> entries = ls(sessionID, path, cmd.isRecursive());
result = formatEntries(entries, startNode, cmd.isLong(),
cmd.isAbsolute());
}
else if (command.equals(CREATE))
{
String path = (params != null ? params[0] : null);
create(sessionID, path, cmd.includeParents());
}
else if (command.equals(RM))
{
String path = (params != null ? params[0] : null);
rm(sessionID, path);
}
else if (command.equals(CP))
{
String source = (params != null ? params[0] : null);
String destination = (source != null && params.length == 2)
? params[1]
: null;
cp(sessionID, source, destination);
}
else if (command.equals(PWD))
{
result = pwd(sessionID);
}
else if (command.equals(CHKEXEC))
{
String path = (params != null ? params[0] : null);
return String.format("%s", isExecutable(sessionID, path));
}
else if (command.equals(WHICH))
{
String path = (params != null ? params[0] : null);
return String.format("%s", which(sessionID, path));
}
else if (command.equals(CONNECT))
{
throw new Exception(
"This interface to CONNECT is no longer supported");
}
else if (command.equals(SERVICE))
{
String serviceSpec = (params != null ? params[0] : null);
String serviceCmd = (params != null && params.length > 1
? params[1]
: null);
return executeExtension(ResourceType.SERVICE, serviceSpec,
"command", serviceCmd, null);
}
else if (command.equals(LOCATE_SERVICE))
{
String serviceSpec = (params != null ? params[0] : null);
String serviceCmd = (params != null && params.length > 1
? params[1]
: null);
return executeExtension(ResourceType.SERVICE, serviceSpec,
"command", serviceCmd, null);
}
else if (command.equals(EXECUTE))
{
// Re-parse to ignore flags etc. We pass everthing
// along to the extension as needed.
cmd = parser.parseOne(commandLine, false);
params = getParams(cmd.getTokens(), false);
// Execute a procedure
String type = (params != null ? params[0] : null);
String extensionName = (params != null ? params[1] : null);
String theCommand = (params != null && params.length > 2
? params[2]
: null);
if (type == null || extensionName == null || theCommand == null)
{
throw new DirectoryException(
String.format(
"Incorrectly formed command for execute:'%s'.",
command));
}
ResourceType extensionType = ResourceType.valueOf(type
.toUpperCase());
String argList[] = null;
if (params != null && params.length > 3)
{
argList = new String[params.length - 3];
for (int i = 3; i < params.length; i++)
argList[i - 3] = params[i];
}
return executeExtension(extensionType, extensionName, KEY_COMMAND,
theCommand, argList);
}
else
{
if (isExecutable(sessionID, command))
{
throw new DirectoryException(String.format(
"Cannot execute '%s' in this context.", command));
}
}
return result;
}
public synchronized void disconnect(String sessionID)
{
try
{
removeSession(sessionID);
}
catch (DirectoryException d)
{
logger.warn(String.format(
"Attempt to remove non-existent session %s", sessionID), d);
}
}
/**
* This command returns the full path to a given resource, as long as the
* resource exists
*
* @param sessionID
* @param path
* @return a string representing the full, absolute path for a given path
* element
* @throws DirectoryNotFoundException
*/
public String which(String sessionID, String path)
throws DirectoryNotFoundException
{
ResourceNode node = null;
try
{
node = locate(sessionID, path);
}
catch (DirectoryNotFoundException d)
{
return null;
}
String thePath = formatPath(getAbsolutePath(getRootNode(), node, true),
true);
int paramStart = thePath.indexOf("(");
if (paramStart != -1)
{
return thePath.substring(0, paramStart);
}
return thePath;
}
/**
* A primitive that indicates whether or not the resource associated with a
* node is executable.
*
* @param sessionID
* @param path
* @return true if the path is executable, otherwise false
* @throws DirectoryNotFoundException
*/
public boolean isExecutable(String sessionID, String path)
throws DirectoryNotFoundException
{
ResourceNode execNode = locate(sessionID, path);
if (execNode.getResource() == null)
{
throw new DirectoryNotFoundException(String.format(
"Cannot execute '%s'", path));
}
if (execNode.getResource() instanceof Operation)
{
return true;
}
return false;
}
/**
* Returns services folder which is created upon construction of this
* directory and which holds services to execute commands upon.
*/
public ResourceNode getServiceFolder(String hostName)
{
return servicesFolders.get(hostName);
}
/**
* Returns one or more service configurations as property instances.
*
* @param sessionID Session ID
* @param hostName Name of host on which service abides
* @param name Name of the service or null to select all available services
*/
public List<TungstenProperties> getServiceConfig(String sessionID,
String hostName, String name)
{
List<TungstenProperties> serviceList = new ArrayList<TungstenProperties>();
// Look up service folder for member.
String servicePath = String.format("/%s/%s/conf/%s",
this.getClusterName(), hostName, "service");
ResourceNode serviceFolder = null;
try
{
serviceFolder = this.locate(sessionID, servicePath);
}
catch (DirectoryNotFoundException e)
{
logger.warn("Unable to find service directory: " + serviceFolder);
if (logger.isDebugEnabled())
logger.debug(e);
}
if (serviceFolder == null)
return serviceList;
// Construct list of services by scanning children.
for (String serviceName : serviceFolder.getChildren().keySet())
{
if (name == null || name.equals(serviceName))
{
ResourceNode serviceNode = serviceFolder.getChildren().get(
serviceName);
ResourceConfiguration config = (ResourceConfiguration) serviceNode
.getResource();
TungstenProperties serviceProps = config.getProperties();
serviceList.add(serviceProps);
}
}
return serviceList;
}
/**
* Traverse back the indicated number of levels, starting at the current
* node, and return the node found there.
*
* @param levels
* @return the resource that is relative to the current node, proceeding via
* the parents, by the indicated levels
*/
public ResourceNode locateRelative(String sessionID, int levels)
throws DirectoryException, DirectoryNotFoundException
{
return locateRelative(sessionID, levels, getCurrentNode(sessionID));
}
/**
* Traverse back the indicated number of levels, starting at the current
* node, and return the node found there.
*
* @param levels
* @return the resource that is relative to the current node, proceeding via
* the parents, by the indicated levels, starting at the indicated
* node.
*/
public ResourceNode locateRelative(String sessionID, int levels,
ResourceNode startNode) throws DirectoryException,
DirectoryNotFoundException
{
if (levels == 0)
return getCurrentNode(sessionID);
ResourceNode foundNode = null;
ResourceNode nodeToSearch = startNode;
for (int level = 0; level < levels; level++)
{
if ((foundNode = nodeToSearch.getParent()) != null)
{
nodeToSearch = nodeToSearch.getParent();
}
else
{
throw new DirectoryException(String.format(
"No parent element found for '%s'",
formatPath(
getAbsolutePath(nodeToSearch, nodeToSearch,
true), true)));
}
}
return foundNode;
}
/**
* Locates a given resource node, given a path. This method takes into
* account the 'current' working node.
*
* @param sessionID
* @param path
* @return the resource specified by path
* @throws DirectoryNotFoundException if the resource cannot be found
*/
public ResourceNode locate(String sessionID, String path)
throws DirectoryNotFoundException
{
return locate(sessionID, path, getCurrentNode(sessionID));
}
/**
* Locates a given resource give a path and a starting node.
*
* @param path
* @param startNode
* @return the resource specified by path
* @throws DirectoryNotFoundException if the resource cannot be found
*/
public ResourceNode locate(String sessionID, String path,
ResourceNode startNode) throws DirectoryNotFoundException
{
if (path == null)
{
return getCurrentNode(sessionID);
}
else if (path.startsWith(PROTOCOL))
{
path = path.substring(PROTOCOL.length());
}
else if (path.startsWith(ROOT_ELEMENT)
&& path.length() > ROOT_ELEMENT.length())
{
startNode = getRootNode();
path = path.substring(ROOT_ELEMENT.length());
}
if (path.equals(CURRENT_ELEMENT))
return getCurrentNode(sessionID);
else if (path.equals(ROOT_ELEMENT))
return getRootNode();
ResourceNode foundNode = null;
ResourceNode nodeToSearch = startNode;
String pathElements[] = path.split(PATH_SEPARATOR);
if (pathElements.length == 0)
{
return getRootNode();
}
for (String element : pathElements)
{
// Just skip blanks which result from extra slashes
if (element.length() == 0)
continue;
if (element.equals(PARENT_ELEMENT))
{
if ((foundNode = nodeToSearch.getParent()) != null)
{
nodeToSearch = nodeToSearch.getParent();
}
else
{
throw new DirectoryNotFoundException(String.format(
"element '%s' not found", path));
}
}
else
{
Map<String, ResourceNode> children = nodeToSearch.getChildren();
// For the present, we just handle the wildcard as if it
// means to return 'any' element rather than all elements.
if (element.equals(ANY_ELEMENT))
{
if (nodeToSearch.getType() == ResourceType.CLUSTER)
{
foundNode = children.get(memberName);
}
else if (children.size() > 0)
{
foundNode = getFirst(children);
}
else
{
throw new DirectoryNotFoundException(
String.format(
"the element '%s' of path '%s' resolves to more than one element",
element, path));
}
}
else
{
foundNode = children.get(element);
}
if (foundNode == null)
{
throw new DirectoryNotFoundException(
String.format(
"element '%s' not found in path '%s' while searching for entry '%s'",
element,
formatPath(
getAbsolutePath(getRootNode(),
nodeToSearch, true), true),
path));
}
nodeToSearch = foundNode;
}
}
return foundNode;
}
/**
* @param map
* @throws DirectoryNotFoundException
*/
private ResourceNode getFirst(Map<String, ResourceNode> map)
throws DirectoryNotFoundException
{
for (ResourceNode node : map.values())
return node;
return null;
}
/**
* @param map
* @return
* @throws DirectoryNotFoundException
*/
// private ResourceNode getLast(Map<String, ResourceNode> map)
// throws DirectoryNotFoundException
// {
// ResourceNode lastNode = null;
// for (ResourceNode node : map.values())
// {
// lastNode = node;
// }
//
// return lastNode;
// }
/**
* @param path
* @throws DirectoryNotFoundException
*/
private ResourceNode getStartNode(String sessionID, String path)
throws DirectoryNotFoundException
{
ResourceNode startNode = null;
startNode = locate(sessionID, path);
return startNode;
}
/**
* Returns a list of resource nodes according to the path passed in.
*
* @param path
* @return a list of resources as indicated by the path.
* @throws DirectoryNotFoundException if the specified resources cannot be
* found
*/
public List<ResourceNode> ls(String sessionID, String path,
boolean doRecurse) throws DirectoryNotFoundException
{
ResourceNode startNode = null;
startNode = getStartNode(sessionID, path);
List<ResourceNode> entries = new LinkedList<ResourceNode>();
if (!startNode.isContainer())
{
entries.add(startNode);
return entries;
}
getEntries(startNode, entries, doRecurse);
return entries;
}
/**
* Makes a copy of a specific resource node in the destination.
*
* @param sourcePath
* @param destinationPath
* @throws DirectoryException
* @throws DirectoryNotFoundException
*/
public synchronized void cp(String sessionID, String sourcePath,
String destinationPath) throws DirectoryException,
DirectoryNotFoundException
{
ResourceNode destination = null;
ResourceNode source = null;
if (sourcePath == null || destinationPath == null)
{
throw new DirectoryException(
"cp: <source> <destination>: missing operand");
}
try
{
source = locate(sessionID, sourcePath);
}
catch (DirectoryNotFoundException c)
{
throw new DirectoryException(String.format(
"cp: the source element '%s' does not exist", sourcePath));
}
try
{
destination = locate(sessionID, destinationPath);
// If the destination path exists and is not a container,
// or it is of the same type, don't allow the copy.
if (!destination.getResource().isContainer()
|| destination.getResource().getType() == source
.getResource().getType())
{
throw new DirectoryException(String.format(
"cp: cannot copy over an existing element '%s'",
destinationPath));
}
}
catch (DirectoryNotFoundException c)
{
// We have more checking to do.....
}
if (destination == null)
{
// If the destination path refers only to the new name,
// the destination is the current node.
if (lastElement(destinationPath).equals(
elementPrefix(destinationPath)))
{
destination = getCurrentNode(sessionID);
}
else
{
destination = locate(sessionID, elementPrefix(destinationPath));
}
}
Resource copy = null;
try
{
copy = ResourceFactory.copyInstance(source,
lastElement(destinationPath), destination, this);
}
catch (ResourceException c)
{
throw new DirectoryException(String.format(
"unable to create a copy of '%s', reason='%s'", sourcePath,
c.getMessage()));
}
destination.addChild(copy);
flush();
}
/**
* @param nodeToSearch
* @param entries
* @return all of the entries in the nodes below the node to search
*/
public List<ResourceNode> getEntries(ResourceNode nodeToSearch,
List<ResourceNode> entries, boolean doRecurse)
{
for (ResourceNode entry : nodeToSearch.getChildren().values())
{
entries.add(entry);
if (doRecurse)
{
getEntries(entry, entries, doRecurse);
}
}
return entries;
}
/**
* @param path
* @throws DirectoryNotFoundException
*/
public synchronized ResourceNode cd(String sessionID, String path)
throws DirectoryNotFoundException, DirectoryException
{
ResourceNode node = null;
if (path == null)
{
node = getRootNode();
}
else
{
node = locate(sessionID, path);
}
if (!node.isContainer())
{
throw new DirectoryException(String.format(
"the element referenced by '%s' is not a container", path));
}
getSession(sessionID).setCurrentNode(node);
flush();
return node;
}
/**
* Executes a command on a service. The command must be defined in the
* service configuration like this for eg.:<br/>
* command.start=../../tungsten-replicator/bin/replicator start
*
* @param serviceSpec Name of the service.
* @param serviceCmd Command name to execute on a service.
* @return Stdout of the process have been executed.
* @throws Exception If service node not found, or service does not support
* the requested command.
*/
public String service(String serviceSpec, String serviceCmd)
throws Exception
{
return executeExtension(ResourceType.SERVICE, serviceSpec, KEY_COMMAND,
serviceCmd, null);
}
/**
* Executes a command on a service. The command must be defined in the
* service configuration like this for eg.:<br/>
* command.start=../../tungsten-replicator/bin/replicator start
*
* @param procedureSpec Name of the service.
* @param command Command name to execute on a service.
* @return standard output of the process have been executed.
* @throws Exception If service node not found, or service does not support
* the requested command.
*/
public String procedure(String procedureSpec, String command)
throws Exception
{
return executeExtension(ResourceType.EXTENSION, procedureSpec, "run",
command, null);
}
/**
* Execute an 'extension' by looking it up in the appropriate cluster
* configuration directory. This is a generic facility that can execute any
* previously configured command at the OS level and includes the ability to
* execute concurrently across a set of nodes. The arguments passed in
* determine where we will look for the 'extension' and what we will
* execute.
*
* @param extensionType - the type of the extension. This determines the
* directory that is searched for the extension.
* @param extensionName - the name of the .properties file to look for in
* the extension directory.
* @param commandPrefix - the string to look for, in the extension
* properties file as an 'introducer' to a specific operation.
* This may vary depending on the function of the extension.
* @param command - the command 'key' to use to find the command to execute.
* @param args - any args required by the command. These are treated
* positionally
*/
public String executeExtension(ResourceType extensionType,
String extensionName, String commandPrefix, String command,
String[] args) throws Exception
{
logger.debug(String.format("executeExtension(%s %s %s %s %s)",
extensionType, extensionName, commandPrefix, command,
args != null ? CLUtils.printArgs(args) : ""));
if (extensionName == null)
{
return String
.format("You must provide the component name for this command");
}
String cmdPrefix = String.format("%s.", commandPrefix);
String cmdProp = String.format("%s.%s", commandPrefix, command);
String extensionPath = String.format("/%s/%s/conf/%s/%s",
getClusterName(), getMemberName(), extensionType.toString()
.toLowerCase(), extensionName);
ResourceNode extensionNode = locate(getSystemSessionID(), extensionPath);
if (extensionNode == null)
{
return String
.format("Could not find an extension of type %s named %s on member %s",
extensionType, extensionName, memberName);
}
ResourceConfiguration config = (ResourceConfiguration) extensionNode
.getResource();
TungstenProperties tp = config.getProperties();
if (command == null)
{
return String
.format("%s extension for %s takes one of the following commands:\n%s",
extensionType, extensionName,
tp.subset(cmdPrefix, true).keyNames());
}
String execPath = tp.getString(cmdProp);
if (execPath == null)
{
return String.format(
"The %s extension for component %s does not support the command %s\n"
+ "It takes one of the following commands\n:%s",
extensionType, extensionName, command,
tp.subset(cmdPrefix, true).keyNames());
}
else
{
ArrayList<String> execList = new ArrayList<String>();
for (String arg : execPath.split(" +"))
{
execList.add(arg);
}
// original string from which args were taken
// may have included quoted strings but the
// basic command parser in cctrl doesn't leave them
// alone. Since I don't want to mess with that parser
// right now and break something else, just
// reconstruct the quoted strings here.
if (args != null)
{
boolean inQuotedString = false;
String quotedString = "";
for (String arg : args)
{
if (arg.startsWith("\""))
{
inQuotedString = true;
quotedString = "";
quotedString = quotedString + arg + " ";
continue;
}
else if (inQuotedString)
{
if (arg.endsWith("\""))
{
inQuotedString = false;
quotedString = quotedString + arg;
execList.add(quotedString);
}
else
{
quotedString = quotedString + arg + " ";
}
continue;
}
else
{
execList.add(arg);
}
}
}
// Log command execution so we can trace commands later in the log.
logger.info("Executing OS command: + " + execPath);
ProcessExecutor processExecutor = new ProcessExecutor();
processExecutor.setWorkDirectory(null); // Uses current working dir.
processExecutor.setCommands(execList.toArray(new String[execList
.size()]));
processExecutor.run();
if (processExecutor.isSuccessful())
{
logger.debug("**************RESULTS FROM COMMAND EXECUTION****************");
logger.debug("Exit value: " + processExecutor.getExitValue());
logger.debug("Stdout: " + processExecutor.getStdout());
logger.debug("Stderr: " + processExecutor.getStderr());
}
else
{
// Failed commands need to be logged for debugging purposes.
logger.warn("Command failed: " + execPath);
logger.info("Exit value: " + processExecutor.getExitValue());
logger.info("Stderr: " + processExecutor.getStderr());
logger.info("Stdout: " + processExecutor.getStdout());
}
String stdOut = processExecutor.getStdout();
String stdErr = processExecutor.getStderr();
return stdErr + stdOut;
}
}
public synchronized ResourceNode create(String sessionID, String name,
boolean createParents) throws DirectoryException,
DirectoryNotFoundException
{
return create(sessionID, name, getCurrentNode(sessionID), createParents);
}
/**
* @param name
* @param parent
* @return the resource that was created
* @throws DirectoryException if the parent cannot be found
*/
public synchronized ResourceNode create(String sessionID, String name,
ResourceNode parent) throws DirectoryException
{
ResourceNode newElement = null;
if (name == null)
{
throw new DirectoryException(
String.format("mkdir: missing name to create"));
}
try
{
newElement = ResourceFactory.addInstance(parent.getResource()
.getChildType(), name, parent, this, sessionID);
}
catch (ResourceException r)
{
throw new DirectoryException(r.getMessage());
}
flush();
return newElement;
}
/**
* @param path
* @param startNode
* @param createParents
* @return the resource that was created
* @throws DirectoryException if the startNode cannot be found
*/
public synchronized ResourceNode create(String sessionID, String path,
ResourceNode startNode, boolean createParents)
throws DirectoryException
{
if (path.startsWith(PROTOCOL))
{
path = path.substring(PROTOCOL.length());
}
String pathElements[] = path.split(PATH_SEPARATOR);
if (pathElements.length == 0)
{
throw new DirectoryException(
"missing operand: usage: create <path>");
}
ResourceNode foundNode = startNode;
ResourceNode createdNode = null;
// First look at all of the elements up to the final element.
// If createParents is set, create missing elements
for (int i = 0; i < pathElements.length - 1; i++)
{
if (pathElements[i].length() == 0)
continue;
try
{
foundNode = locate(sessionID, pathElements[i], startNode);
}
catch (DirectoryNotFoundException d)
{
if (createParents)
{
foundNode = create(sessionID, pathElements[i], startNode);
}
else
{
throw new DirectoryException(String.format(
"element '%s' does not exist in path '%s'",
pathElements[i], path));
}
}
startNode = foundNode;
}
createdNode = create(sessionID, pathElements[pathElements.length - 1],
startNode);
flush();
return createdNode;
}
/**
* @param sessionID
* @param path
* @throws DirectoryNotFoundException
*/
public synchronized void rm(String sessionID, String path,
ResourceNode targetNode) throws DirectoryNotFoundException,
DirectoryException
{
ResourceNode nodeToRemove = null;
nodeToRemove = getStartNode(sessionID, path);
if (nodeToRemove != targetNode)
{
throw new DirectoryException(String.format(
"Found node %s is not the same as the target node %s",
nodeToRemove, targetNode));
}
nodeToRemove.getParent().removeChild(nodeToRemove.getKey());
}
/**
* @param sessionID
* @param path
* @throws DirectoryNotFoundException
*/
public synchronized void rm(String sessionID, String path)
throws DirectoryNotFoundException, DirectoryException
{
ResourceNode nodeToRemove = null;
nodeToRemove = getStartNode(sessionID, path);
nodeToRemove.getParent().removeChild(nodeToRemove.getKey());
}
/**
* @param sessionID
* @return a string representing the working directory/resourceNode
* @throws DirectoryNotFoundException
*/
public String pwd(String sessionID) throws DirectoryNotFoundException
{
return formatPath(getCwd(sessionID), true);
}
/**
* @param pathElements
* @param reverseOrder
* @return a formatted representation of the path
*/
public static String formatPath(List<ResourceNode> pathElements,
boolean reverseOrder)
{
StringBuilder builder = new StringBuilder();
int elementCount = 0;
for (ResourceNode element : pathElements)
{
String elementString = null;
if (element.getResource() instanceof Operation)
{
elementString = (element.getResource() != null ? element
.getResource().toString() : element.toString());
}
else
{
elementString = element.getKey();
if (element.isContainer() && element.getParent() != null)
elementString += "/";
}
if (reverseOrder)
{
builder.insert(0, elementString);
}
else
{
if (element.getParent() == null)
{
builder.append("/");
}
builder.append(elementString);
if (elementCount++ > 0)
{
builder.append("/");
}
}
}
return builder.toString();
}
/**
* @param entries
* @param detailed
* @return string representation of the entries
*/
@SuppressWarnings("unchecked")
public static String formatEntries(List<ResourceNode> entries,
ResourceNode startNode, boolean detailed, boolean absolute)
{
StringBuilder builder = new StringBuilder();
int entryCount = 0;
Collections.sort(entries);
for (ResourceNode entry : entries)
{
// On entry we must ensure the start node is correct.
if (entryCount++ == 0)
{
// Default to the root if no start node is provided.
if (startNode == null)
startNode = entry.getRoot();
}
else
{
builder.append("\n");
}
if (detailed)
{
Resource res = entry.getResource();
String detail = (res != null ? res.describe(detailed) : entry
.toString());
builder.append(String.format("%s", detail));
}
else
{
boolean includeStartNode = (startNode.getParent() == null)
|| absolute;
if (absolute)
{
startNode = startNode.getRoot();
}
builder.append(String.format(
"%s",
formatPath(
getAbsolutePath(startNode, entry,
includeStartNode), true)));
}
}
return builder.toString();
}
public static List<ResourceNode> getAbsolutePath(ResourceNode fromNode,
ResourceNode toNode, boolean includeFromNode)
{
List<ResourceNode> absolutePath = new LinkedList<ResourceNode>();
absolutePath.add(toNode);
ResourceNode parent = toNode.getParent();
while (parent != null)
{
if (parent == fromNode)
{
if (includeFromNode)
absolutePath.add(parent);
break;
}
absolutePath.add(parent);
parent = parent.getParent();
}
return absolutePath;
}
public ResourceNode getCurrentNode(String sessionID)
throws DirectoryNotFoundException
{
return getSession(sessionID).getCurrentNode();
}
public void setCurrentNode(String sessionID, ResourceNode currentNode)
throws DirectoryNotFoundException
{
getSession(sessionID).setCurrentNode(currentNode);
}
public List<ResourceNode> getCwd(String sessionID)
throws DirectoryNotFoundException
{
return getAbsolutePath(getRootNode(), getSession(sessionID)
.getCurrentNode(), true);
}
/**
* This method merges the current directory with another directory such that
* the current directory has all elements, by name, which exist in both
* directories and any elements that have identical names remain in the
* current directory.
*
* @param source
* @param destination
* @return the count of the number of nodes merged
* @throws DirectoryException
*/
public synchronized Directory merge(Directory source, Directory destination)
throws DirectoryException
{
merging = true;
ResourceNode sourceNode = source.getRootNode();
ResourceNode destinationNode = destination.getRootNode();
Integer nodesMerged = new Integer(0);
System.out.println("###### START DIRECTORY MERGE ########");
_merge(sourceNode, destinationNode, nodesMerged);
System.out.println(String.format(
"###### END DIRECTORY MERGE, MERGED=%d ########", nodesMerged));
// While we are at it, make sure that we add any sessionsByID we do
// not already know about.
destination.mergeSessions(source);
lastMergedVersion = currentVersion;
merging = false;
return this;
}
private void _merge(ResourceNode sourceNode, ResourceNode destinationNode,
Integer nodesMerged)
{
Map<String, ResourceNode> sourceChildren = sourceNode.getChildren();
Map<String, ResourceNode> destinationChildren = destinationNode
.getChildren();
for (ResourceNode sourceChild : sourceChildren.values())
{
ResourceNode destinationChild = destinationChildren.get(sourceChild
.getKey());
if (destinationChild == null)
{
destinationChild = destinationNode.addChild(sourceChild
.getResource());
nodesMerged++;
}
_merge(sourceChild, destinationChild, nodesMerged);
}
}
/**
* paramStart
*
* @param args
*/
String[] getParams(String[] args, boolean parseFlags)
throws DirectoryException
{
if (args == null || args.length == 1)
return null;
int paramIndex = 1;
if (args[paramIndex].startsWith("-") && parseFlags)
{
byte chars[] = args[paramIndex].getBytes();
// Skip the flag
for (int i = 1; i < chars.length; i++)
{
if (chars[i] == FLAG_LONG)
detailed = true;
else if (chars[i] == FLAG_RECURSIVE)
recursive = true;
else if (chars[i] == FLAG_PARENTS)
createParents = true;
else if (chars[i] == FLAG_ABSOLUTE)
absolute = true;
else
throw new DirectoryException(String.format(
"Unrecognized option '%c'", chars[i]));
}
paramIndex++;
}
if (args.length == paramIndex)
return null;
String[] params = new String[args.length - paramIndex];
int countParams = args.length - paramIndex;
for (int i = 0; i < countParams; i++)
{
params[i] = args[paramIndex++];
}
return params;
}
public Map<String, DirectorySession> getSessionMap()
{
synchronized (sessionsByID)
{
return new HashMap<String, DirectorySession>(sessionsByID);
}
}
private void mergeSessions(Directory source)
{
synchronized (sessionsByID)
{
Map<String, DirectorySession> sessionMap = source.getSessionsByID();
for (String sessionId : sessionMap.keySet())
{
if (sessionsByID.get(sessionId) == null)
{
sessionsByID.put(sessionId, sessionMap.get(sessionId));
}
}
}
synchronized (sessionsByDomain)
{
Map<Long, Vector<String>> domainSessionsLocal = sessionsByDomain
.get(source.getMemberName());
// Create a place to store these remote domain sessions
if (domainSessionsLocal == null)
{
domainSessionsLocal = new HashMap<Long, Vector<String>>();
sessionsByDomain.put(source.getMemberName(),
domainSessionsLocal);
}
Map<String, Map<Long, Vector<String>>> sourceDomainMap = source
.getSessionsByDomain();
Map<Long, Vector<String>> domainSessionsRemote = sourceDomainMap
.get(source.getMemberName());
for (Long handle : domainSessionsRemote.keySet())
{
Vector<String> localSessions = domainSessionsLocal.get(handle);
if (localSessions == null)
{
localSessions = new Vector<String>();
domainSessionsLocal.put(handle, localSessions);
}
Vector<String> remoteSessions = domainSessionsRemote
.get(handle);
for (String sessionID : remoteSessions)
{
if (!localSessions.contains(sessionID))
{
logger.debug(String
.format("Merged session for domain %s %s for handle=%d",
source.getMemberName(), sessionID,
handle));
localSessions.add(sessionID);
}
}
}
}
}
public Map<String, Map<Long, Vector<String>>> getSessionsByDomain()
{
return sessionsByDomain;
}
@Override
public String toString()
{
String ret = null;
try
{
List<ResourceNode> listResult = ls(systemSessionID, ROOT_ELEMENT,
true);
Collections.sort(listResult, new ResourceNodeComparator());
ret = formatEntries(listResult, null, detailed, false);
}
catch (Exception e)
{
ret = "NOT AVAILABLE BECAUSE OF EXCEPTION=" + e;
}
return ret;
}
class ResourceNodeComparator
implements
Comparator<ResourceNode>,
Serializable
{
/**
*
*/
private static final long serialVersionUID = 1L;
public int compare(ResourceNode o1, ResourceNode o2)
{
return o1.getKey().compareTo(o2.getKey());
}
}
private String lastElement(String path)
{
return path.substring((path.lastIndexOf(PATH_SEPARATOR) + 1));
}
private String elementPrefix(String path)
{
int separatorLocation = path.lastIndexOf(PATH_SEPARATOR);
if (separatorLocation == -1)
{
return null;
}
return path.substring(0, separatorLocation);
}
public boolean exists(String sessionID, String path)
{
ResourceNode nodeToVerify = null;
try
{
nodeToVerify = locate(sessionID, path);
}
catch (DirectoryNotFoundException c)
{
return false;
}
return ((nodeToVerify == null ? false : true));
}
public static boolean isDirectoryCommand(String command)
{
for (String cmd : directoryCommands)
{
if (command.compareToIgnoreCase(cmd) == 0)
return true;
}
return false;
}
/**
* @return the systemSessionID
*/
public String getSystemSessionID()
{
return systemSessionID;
}
/**
* @param systemSessionID the systemSessionID to set
*/
public void setSystemSessionID(String systemSessionID)
{
this.systemSessionID = systemSessionID;
}
public void addListener(ResourceNotificationListener listener)
{
if (listener == null)
{
logger.warn("Attempting to add a null listener");
return;
}
listeners.add(listener);
}
public void notifyListeners(ClusterResourceNotification notification)
throws ResourceNotificationException
{
for (ResourceNotificationListener listener : listeners)
{
listener.notify(notification);
}
}
public void run()
{
}
public synchronized void flush()
{
if (merging)
return;
currentVersion++;
TungstenProperties resourceProps = new TungstenProperties();
resourceProps.setObject("directory", this);
DirectoryNotification notification = new DirectoryNotification(
clusterName, memberName, getClass().getSimpleName(), getClass()
.getSimpleName(), ResourceState.MODIFIED, resourceProps);
try
{
notifyListeners(notification);
}
catch (ResourceNotificationException r)
{
logger.error(
String.format(
"Could not send directory synchronization request, reason=%s",
r.getLocalizedMessage()), r);
}
}
public long getVersion()
{
return currentVersion;
}
public Map<String, NotificationGroupMember> getNotificationGroupMembers()
{
return new LinkedHashMap<String, NotificationGroupMember>();
}
public Map<String, DirectorySession> getSessionsByID()
{
return sessionsByID;
}
public void setSessionsByID(Map<String, DirectorySession> sessionsByID)
{
this.sessionsByID = sessionsByID;
}
public String getClusterName()
{
return clusterName;
}
public void setClusterName(String clusterName)
{
this.clusterName = clusterName;
}
public String getMemberName()
{
return memberName;
}
public void setMemberName(String memberName)
{
this.memberName = memberName;
}
public void prepare() throws Exception
{
}
/**
* @param siteName
* @param clusterName
* @param host
* @param beanServiceName
* @param port
* @param component
* @param managerName
* @throws Exception
*/
public ResourceNode getManagerNode(String sessionID, String siteName,
String clusterName, String host, String beanServiceName, int port,
String component, String managerName) throws Exception
{
// String path = String.format("%s/%s/%s:%d/%s", dataServiceName, host,
// beanServiceName, port, managerName);
String path = String.format("/%s/%s/%s/%s/%s", siteName, clusterName,
host, beanServiceName, managerName);
ResourceNode managerNode = null;
try
{
managerNode = locate(sessionID, path);
}
catch (DirectoryNotFoundException d)
{
try
{
managerNode = create(sessionID, path, true);
}
catch (DirectoryException di)
{
throw new Exception(String.format(
"Unable to create directory entry, reason=%s",
di.getMessage()));
}
}
return managerNode;
}
/**
* Processes are found at: /<site>/<cluster>/<member>/<component>/<process>
*
* @param sessionID
* @param clusterName
* @param memberName
*/
public ResourceNode getProcessNode(String sessionID, String clusterName,
String memberName, String processName)
{
String path = String.format("/%s/%s/%s", clusterName, memberName,
processName);
ResourceNode processNode = null;
try
{
processNode = locate(sessionID, path, getRootNode());
}
catch (DirectoryNotFoundException d)
{
return null;
}
return processNode;
}
/**
* @see com.continuent.tungsten.common.directory.Directory#rm(java.lang.String,
* com.continuent.tungsten.common.directory.ResourceNode)
*/
public synchronized void rm(String sessionID, ResourceNode node)
{
node.getParent().removeChild(node.getKey());
}
public synchronized ResourceNode createProcessNode(String sessionID,
String siteName, String clusterName, String memberName,
String componentName, String resourceManagerName, int port)
throws DirectoryException
{
String path = String.format("/%s/%s/%s/%s", siteName, clusterName,
memberName, resourceManagerName);
if (memberName == null || port == 0)
{
String message = String
.format("Attempting to create an invalid process entry for component '%s'",
resourceManagerName);
throw new DirectoryException(message);
}
try
{
ResourceNode processNode = create(sessionID, path, true);
Process process = (Process) processNode.getResource();
process.setMember(memberName);
process.setClusterName(clusterName);
process.setPort(port);
return processNode;
}
catch (Exception di)
{
throw new DirectoryException(String.format(
"Unable to create directory entry, reason=%s",
di.getMessage()), di);
}
}
public String formatPath(Directory directory, String sessionID,
ResourceNode node) throws DirectoryNotFoundException
{
return Directory.formatPath(
directory.getAbsolutePath(directory, sessionID, node), true);
}
public List<ResourceNode> getAbsolutePath(Directory directory,
String sessionID, ResourceNode node)
throws DirectoryNotFoundException
{
return getAbsolutePath(directory.getCurrentNode(sessionID), node, true);
}
}