/**
* 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): Edward Archibald
* Contributor(s): Robert Hodges
*/
package com.continuent.tungsten.common.config.cluster;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.apache.log4j.Logger;
import com.continuent.tungsten.common.cluster.resource.ResourceType;
import com.continuent.tungsten.common.config.TungstenProperties;
import com.continuent.tungsten.manager.router.gateway.RouterGatewayConstants;
public class ClusterConfiguration
{
/**
* Logger
*/
private static Logger logger = Logger.getLogger(ClusterConfiguration.class);
public static String clusterHomeName = null;
private String clusterName;
/**
* The source of the properties for this configuration. getClusterHome
*/
public TungstenProperties props = null;
private File clusterConfigDir = null;
private File clusterConfigRootDir = null;
private static String configFileNameInUse = null;
public ClusterConfiguration(String clusterName)
{
this.clusterName = clusterName;
}
/**
* Creates a new <code>ClusterConfiguration</code> object
*
* @param configFileName
* @throws ConfigurationException
*/
public ClusterConfiguration(String clusterName, String configFileName)
throws ConfigurationException
{
this(clusterName);
load(configFileName);
}
/**
* Loads a set of resource configurations from the appropriate directory
* according to Tungsten resource conventions. The cluster directory
* hierarchy looks like this:
* {clusterHome}/cluster/{clusterName}/{resourceType}
*
* @param resourceType
* @throws ConfigurationException
*/
public synchronized Map<String, Map<String, TungstenProperties>> loadClusterConfiguration(
ResourceType resourceType) throws ConfigurationException
{
if (getClusterHome() == null)
{
throw new ConfigurationException(
"No cluster.home found from which to configure cluster resources.");
}
Map<String, Map<String, TungstenProperties>> clusterConfigurations = new HashMap<String, Map<String, TungstenProperties>>();
File cluster = getDir(getClusterConfigRootDirName(getClusterHome()));
for (File foundFile : cluster.listFiles())
{
if (foundFile.isDirectory())
{
Map<String, TungstenProperties> clusterConfig = loadConfiguration(
foundFile.getName(), resourceType);
clusterConfigurations.put(foundFile.getName(), clusterConfig);
}
}
return clusterConfigurations;
}
/**
* Returns configurations for a set of resources of a given resourceType for
* a given clusterName.
*
* @param clusterName
* @param resourceType
* @throws ConfigurationException
*/
public synchronized Map<String, TungstenProperties> loadConfiguration(
String clusterName, ResourceType resourceType)
throws ConfigurationException
{
if (getClusterHome() == null)
{
throw new ConfigurationException(
"No cluster.home found from which to configure cluster resources.");
}
File resources = getDir(getResourceConfigDirName(getClusterHome(),
clusterName, resourceType));
FilenameFilter propFilter = new FilenameFilter()
{
public boolean accept(File dir, String name)
{
return name.endsWith(".properties");
}
};
Map<String, TungstenProperties> resourceMap = new HashMap<String, TungstenProperties>();
// Load resource information
try
{
for (File resourceConf : resources.listFiles(propFilter))
{
TungstenProperties resourceProps = null;
RandomAccessFile resourceFile = null;
InputStream byteStream = null;
try
{
resourceFile = new RandomAccessFile(
resourceConf.getAbsolutePath(), "r");
resourceFile.seek(0);
byte[] bytes = new byte[(int) resourceConf.length()];
resourceFile.read(bytes);
byteStream = new ByteArrayInputStream(bytes);
resourceFile.close();
resourceFile = null;
resourceProps = new TungstenProperties();
resourceProps.load(byteStream);
byteStream.close();
byteStream = null;
}
catch (IOException i)
{
throw new ConfigurationException(String.format(
"Unable to load resource %s\n%s",
resourceConf.getAbsolutePath(), i.getMessage()));
}
finally
{
if (resourceFile != null)
{
resourceFile.close();
resourceFile = null;
}
if (byteStream != null)
{
byteStream.close();
byteStream = null;
}
}
if (resourceProps.getString("name") == null)
{
throw new ConfigurationException(String.format(
"The file %s appears to be corrupt or empty",
resourceConf.getPath()));
}
resourceMap.put(resourceProps.getString("name"), resourceProps);
}
}
catch (FileNotFoundException f)
{
throw new ConfigurationException(
"Unable to process a file when configuring resources:" + f);
}
catch (IOException i)
{
throw new ConfigurationException(
"Error while loading datastore properties:" + i);
}
return resourceMap;
}
/**
* Store a properties file as a resource configuration using the resource
* configuration standards for Tungsten
*
* @param resourceType
* @param resourceProps
* @throws ConfigurationException
*/
public synchronized void storeResourceConfig(String clusterName,
ResourceType resourceType, TungstenProperties resourceProps)
throws ConfigurationException
{
if (getClusterHome() == null)
{
throw new ConfigurationException(
"The 'clusterHome' property was not found in the configuration file:"
+ getModulePropertiesFileName(
ConfigurationConstants.TR_PROPERTIES,
getClusterHome()));
}
String resourceDir = getResourceConfigDirName(getClusterHome(),
clusterName, resourceType);
File resources = new File(resourceDir);
if (!resources.isDirectory())
{
if (resources.mkdirs())
{
logger.info(String
.format("Created directory '%s'", resourceDir));
}
else
{
String msg = String
.format("The path indicated by the name %s must be a directory.",
getResourceConfigDirName(getClusterHome(),
clusterName, resourceType));
logger.error(msg);
throw new ConfigurationException(msg);
}
}
String outFileName = resources.getAbsolutePath() + File.separator
+ resourceProps.getString("name") + ".properties";
store(resourceProps, outFileName);
}
/**
* Store a list of resources in individual properties files
*
* @param resourceType
* @param resourceList
* @throws ConfigurationException
*/
public synchronized void storeResourcesConfig(String clusterName,
ResourceType resourceType,
Map<String, TungstenProperties> resourceList)
throws ConfigurationException
{
if (getClusterHome() == null)
{
throw new ConfigurationException(
"No cluster.home found from which to configure cluster resources.");
}
for (TungstenProperties props : resourceList.values())
{
storeResourceConfig(clusterName, resourceType, props);
}
}
/**
* Delete a specific resource configuration.
*
* @param clusterName
* @param resourceType
* @param dsName
* @throws ConfigurationException
*/
public void deleteResourceConfig(String clusterName,
ResourceType resourceType, String dsName)
throws ConfigurationException
{
if (getClusterHome() == null)
{
throw new ConfigurationException(
"No home directory found from which to configure resources.");
}
File resources = getDir(getResourceConfigDirName(getClusterHome(),
clusterName, resourceType));
String delFileName = resources.getAbsolutePath() + File.separator
+ dsName + ".properties";
delFile(delFileName);
}
/**
* Return the full pathname of a resource directory for a given cluster.
*
* @param clusterName
* @param resourceType
*/
public static String getResourceConfigDirName(String clusterHome,
String clusterName, ResourceType resourceType)
{
return getClusterConfigRootDirName(clusterHome) + File.separator
+ clusterName + File.separator
+ resourceType.toString().toLowerCase();
}
public static String getClusterConfigDirName(String clusterHome,
String clusterName)
{
return getGlobalConfigDirName(clusterHome) + File.separator
+ ConfigurationConstants.CLUSTER_DIR + File.separator
+ clusterName;
}
public static String getGlobalConfigDirName(String clusterHome)
{
return clusterHome + File.separator
+ ConfigurationConstants.CLUSTER_CONF_DIR;
}
public static String getClusterConfigRootDirName(String clusterHome)
{
return clusterHome + File.separator
+ ConfigurationConstants.CLUSTER_CONF_DIR + File.separator
+ ConfigurationConstants.CLUSTER_DIR;
}
/**
* Determines the filename of a given module properties file by either
* getting the path from the system variable named with the same label as
* the properties file (eg. $router.properties) or by getting it from the
* default location in cluster-home/conf
*
* @param moduleProps the module properties file name
* @param clusterHome location of cluster home
*/
public static String getModulePropertiesFileName(String moduleProps,
String clusterHome)
{
String moduleProperties = System.getProperty(moduleProps);
if (moduleProperties == null)
{
logger.debug("Seeking " + moduleProps + " using cluster.home");
return getGlobalConfigDirName(clusterHome) + File.separator
+ moduleProps;
}
else
{
logger.debug("Seeking " + moduleProps + " using " + moduleProps);
return moduleProperties;
}
}
public void createDefaultConfiguration(String clusterName)
throws ConfigurationException
{
if (getClusterHome() == null)
{
throw new ConfigurationException(
"No cluster.home found from which to configure cluster resources.");
}
createClusterConfigRootDirs();
createConfigDirs(clusterName);
createRouterConfiguration(null);
createPolicyManagerConfiguration(clusterName);
}
/**
* Creates the directory hierarchy for the root of a cluster configuration
*
* @throws ConfigurationException
*/
public void createClusterConfigRootDirs() throws ConfigurationException
{
clusterConfigDir = new File(
ClusterConfiguration
.getClusterConfigRootDirName(clusterHomeName));
clusterConfigRootDir = new File(
getClusterConfigRootDirName(clusterHomeName));
// Ensure the generic 'cluster' exists.
if (!clusterConfigDir.exists())
{
logger.debug("Creating new 'cluster' directory: "
+ clusterConfigDir.getAbsolutePath());
clusterConfigDir.mkdirs();
}
if (!clusterConfigDir.isDirectory() || !clusterConfigDir.canWrite())
{
throw new ConfigurationException(
"'cluster' directory invalid or unreadable: "
+ clusterConfigDir.getAbsolutePath());
}
// Ensure the root level 'cluster' directory exists.
if (!clusterConfigRootDir.exists())
{
logger.debug("Creating new cluster configuration directory: "
+ clusterConfigRootDir.getAbsolutePath());
clusterConfigRootDir.mkdirs();
}
if (!clusterConfigRootDir.isDirectory()
|| !clusterConfigRootDir.canWrite())
{
throw new ConfigurationException(
"cluster configuration directory invalid or unreadable: "
+ clusterConfigRootDir.getAbsolutePath());
}
}
public void createConfigDirs(String clusterName)
throws ConfigurationException
{
createClusterConfigRootDirs();
File clusterConfigDir = new File(clusterConfigRootDir, clusterName);
File dataSourceConfigDir = new File(clusterConfigDir,
ResourceType.DATASOURCE.toString().toLowerCase());
File serviceConfigDir = new File(clusterConfigDir, "service");
File extensionConfigDir = new File(clusterConfigDir, "extension");
// Ensure the datasource directory exists.
if (!dataSourceConfigDir.exists())
{
logger.debug("Creating new datasource directory: "
+ dataSourceConfigDir.getAbsolutePath());
dataSourceConfigDir.mkdirs();
}
if (!dataSourceConfigDir.isDirectory()
|| !dataSourceConfigDir.canWrite())
{
throw new ConfigurationException(
"DataSource config directory invalid or unreadable: "
+ dataSourceConfigDir.getAbsolutePath());
}
// Ensure the datasource directory exists.
if (!serviceConfigDir.exists())
{
logger.debug("Creating new service directory: "
+ serviceConfigDir.getAbsolutePath());
serviceConfigDir.mkdirs();
}
// Ensure the datasource directory exists.
if (!extensionConfigDir.exists())
{
logger.debug("Creating new extension directory: "
+ extensionConfigDir.getAbsolutePath());
extensionConfigDir.mkdirs();
}
}
/**
* Creates a new router configuration file in the correct location.
*
* @param clusterName
* @throws ConfigurationException
*/
public void createRouterConfiguration(String clusterName)
throws ConfigurationException
{
String routerConfigFileName = getModulePropertiesFileName(
ConfigurationConstants.TR_PROPERTIES, getClusterHome());
File routerConfigFile = new File(routerConfigFileName);
// Only create the file if it doesn't already exist.
if (routerConfigFile.exists())
{
logger.debug(String.format(
"SQLRouter configuration already exists at '%s'",
routerConfigFileName));
return;
}
RouterConfiguration config = new RouterConfiguration(null);
config.setClusterHome(getClusterHome());
config.setHost(ConfigurationConstants.TR_RMI_DEFAULT_HOST);
ArrayList<String> al = new ArrayList<String>();
al.add("localhost:9998");
config.setManagerList(al);
TungstenProperties configProps = new TungstenProperties();
configProps.extractProperties(config, true);
logger.debug("Writing out a router configuration to '"
+ routerConfigFileName + "'");
logger.debug("router.properties contains:" + configProps);
config.store(configProps, routerConfigFileName);
}
/**
* Creates a default policy manager configuration in the correct location
*
* @param clusterName
* @throws ConfigurationException
*/
public void createPolicyManagerConfiguration(String clusterName)
throws ConfigurationException
{
String policyMgrConfigFileName = getModulePropertiesFileName(
ConfigurationConstants.PM_PROPERTIES, getClusterHome());
File policyMgrConfigFile = new File(policyMgrConfigFileName);
// Only create the file if it doesn't already exist.
if (policyMgrConfigFile.exists())
{
logger.debug(String.format(
"Policy manager configuration already exists at '%s'",
policyMgrConfigFileName));
return;
}
ClusterPolicyManagerConfiguration config = new ClusterPolicyManagerConfiguration(
clusterName);
config.setClusterHome(getClusterHome());
config.setHost(ConfigurationConstants.PM_RMI_DEFAULT_HOST);
TungstenProperties configProps = new TungstenProperties();
configProps.extractProperties(config, true);
logger.debug("Writing out a policy manager configuration to '"
+ policyMgrConfigFileName + "'");
logger.debug("policymgr.properties contains:" + configProps);
config.store(configProps, policyMgrConfigFileName);
}
/**
* Creates a default data services configuration in the correct location
*
* @param clusterName
* @throws ConfigurationException
*/
public void createDataServicesConfiguration(String clusterName)
throws ConfigurationException
{
String dataServicesConfigFileName = getModulePropertiesFileName(
ConfigurationConstants.TR_SERVICES_PROPS, getClusterHome());
File dataServicesConfigFile = new File(dataServicesConfigFileName);
TungstenProperties configProps = new TungstenProperties();
// Only create the file if it doesn't already exist.
if (dataServicesConfigFile.exists())
{
logger.debug(String.format(
"DataServices configuration already exists at '%s'",
dataServicesConfigFileName));
InputStream is = null;
try
{
is = new FileInputStream(dataServicesConfigFile);
configProps.load(is);
}
catch (FileNotFoundException e)
{
// not likely to happen, we just checked .exists() above
throw new ConfigurationException(e.getLocalizedMessage());
}
catch (IOException e)
{
throw new ConfigurationException("Error while loading "
+ dataServicesConfigFileName + ": "
+ e.getLocalizedMessage());
}
finally
{
if (is != null)
{
try
{
is.close();
}
catch (Exception ignored)
{
}
}
}
}
if (configProps.getString(clusterName) == null
|| configProps.getString(clusterName).isEmpty())
{
configProps.setString(clusterName, "localhost:"
+ RouterGatewayConstants.DEFAULT_GATEWAY_PORT);
}
logger.info("Writing out data services configuration to '"
+ dataServicesConfigFileName + "'");
logger.info("dataservices.properties contains:" + configProps);
OutputStream os = null;
try
{
os = new FileOutputStream(dataServicesConfigFile);
configProps.store(os);
}
catch (IOException i)
{
throw new ConfigurationException("Error while storing properties:"
+ i);
}
}
/**
* Gets a cluster configuration from a file located on the classpath and
* returns it.
*
* @param configFileName
* @throws ConfigurationException
*/
public static TungstenProperties getConfiguration(String configFileName)
throws ConfigurationException
{
TungstenProperties props = new TungstenProperties();
InputStream is = null;
File configFile = null;
// Ensure cluster home name is properly set.
clusterHomeName = System
.getProperty(ConfigurationConstants.CLUSTER_HOME);
if (clusterHomeName == null)
{
throw new ConfigurationException(
"You must set the system property cluster.home");
}
// See if we have a system property corresponding to this name. If so,
// we use that.
String configFileNameFromProperty = System.getProperty(configFileName);
if (configFileNameFromProperty == null)
{
logger.debug("Creating configuration file path from cluster.home: "
+ getGlobalConfigDirName(clusterHomeName));
configFile = new File(getGlobalConfigDirName(clusterHomeName),
configFileName);
}
else
{
logger.debug("Reading configuration file path from property: "
+ configFileName);
configFile = new File(configFileNameFromProperty);
}
// Record the full path
configFileNameInUse = configFile.getAbsolutePath();
// Ensure file exists and is readable.
if (logger.isDebugEnabled())
logger.debug("Loading config file from file: "
+ configFileNameInUse);
if (!configFile.exists() || !configFile.canRead())
{
throw new ConfigurationException(
"Configuration file does not exist or is not readable: "
+ configFileNameInUse);
}
// Read the properties.
try
{
is = new FileInputStream(configFile);
}
catch (FileNotFoundException f)
{
throw new ConfigurationException(String.format(
"Cannot create an input stream for file '%s', reason=%s",
configFileNameInUse, f));
}
try
{
props.load(is);
}
catch (IOException e)
{
throw new ConfigurationException(
"Unable to load configuration file:" + configFileName
+ ", reason=" + e);
}
finally
{
try
{
is.close();
}
catch (IOException e)
{
}
}
return props;
}
/**
* Loads a cluster configuration from a file located on the classpath and
* applies the properties found to the current instance.
*
* @param configFileName
* @throws ConfigurationException
*/
public void load(String configFileName) throws ConfigurationException
{
props = getConfiguration(configFileName);
if (props != null)
{
props.applyProperties(this, true);
}
}
static public void store(String configFileName, TungstenProperties props)
throws ConfigurationException
{
// Ensure cluster home name is properly set.
clusterHomeName = System
.getProperty(ConfigurationConstants.CLUSTER_HOME);
String configFilePath = null;
if (clusterHomeName == null)
{
throw new ConfigurationException(
"You must set the system property cluster.home");
}
// See if we have a system property corresponding to this name. If so,
// we use that.
String configFileNameFromProperty = System.getProperty(configFileName);
if (configFileNameFromProperty == null)
{
logger.info("Creating configuration file path from cluster.home: "
+ getGlobalConfigDirName(clusterHomeName));
configFilePath = getGlobalConfigDirName(clusterHomeName)
+ File.pathSeparator + configFileName;
}
else
{
logger.info("Getting configuration file path from property: "
+ configFileName);
configFilePath = configFileNameFromProperty;
}
try
{
File checkFile = new File(configFilePath);
File backupFile = new File(configFilePath + ".bak");
if (checkFile.exists())
{
if (backupFile.exists())
{
backupFile.delete();
}
checkFile.renameTo(new File(configFilePath + ".bak"));
}
FileOutputStream fout = new FileOutputStream(configFilePath);
props.store(fout);
fout.flush();
fout.getFD().sync();
fout.close();
}
catch (FileNotFoundException f)
{
throw new ConfigurationException(String.format(
"Unable to process a file when writing %s: %s",
configFilePath, f));
}
catch (IOException i)
{
throw new ConfigurationException(String.format(
"Error while storing properties for %s: %s",
configFilePath, i));
}
}
static public void delete(String configFileName)
throws ConfigurationException
{
// Ensure cluster home name is properly set.
clusterHomeName = System
.getProperty(ConfigurationConstants.CLUSTER_HOME);
String configFilePath = null;
if (clusterHomeName == null)
{
throw new ConfigurationException(
"You must set the system property cluster.home");
}
// See if we have a system property corresponding to this name. If so,
// we use that.
String configFileNameFromProperty = System.getProperty(configFileName);
if (configFileNameFromProperty == null)
{
logger.info("Creating configuration file path from cluster.home: "
+ getGlobalConfigDirName(clusterHomeName));
configFilePath = getGlobalConfigDirName(clusterHomeName)
+ File.pathSeparator + configFileName;
}
else
{
logger.info("Getting configuration file path from property: "
+ configFileName);
configFilePath = configFileNameFromProperty;
}
File checkFile = new File(configFilePath);
File backupFile = new File(configFilePath + ".bak");
if (checkFile.exists())
{
if (backupFile.exists())
{
backupFile.delete();
}
checkFile.renameTo(new File(configFilePath + ".bak"));
}
delFile(configFilePath);
}
/**
* deletes a specific file
*
* @param delFileName
* @throws ConfigurationException
*/
public static void delFile(String delFileName)
throws ConfigurationException
{
File delFile = new File(delFileName);
if (delFile.exists() && delFile.canWrite())
{
delFile.delete();
}
else
{
throw new ConfigurationException(
"Can't delete file because it is not writeable. File="
+ delFileName);
}
}
/**
* Validates that a directory exists.
*
* @param dirName
* @throws ConfigurationException
*/
public static File getDir(String dirName) throws ConfigurationException
{
File dir = new File(dirName);
if (!dir.isDirectory())
{
throw new ConfigurationException(String.format(
"The path indicated by %s must be a directory.",
dir.getAbsolutePath()));
}
return dir;
}
/**
* Stores a configuration file in a specific output file.
*
* @param props
* @param outFileName
* @throws ConfigurationException
*/
public void store(TungstenProperties props, String outFileName)
throws ConfigurationException
{
try
{
File checkFile = new File(outFileName);
File backupFile = new File(outFileName + ".bak");
if (checkFile.exists())
{
if (backupFile.exists())
{
backupFile.delete();
}
checkFile.renameTo(new File(outFileName + ".bak"));
}
FileOutputStream fout = new FileOutputStream(outFileName);
props.store(fout);
fout.flush();
fout.getFD().sync();
fout.close();
}
catch (FileNotFoundException f)
{
throw new ConfigurationException(
"Unable to process a file when configuring resources:" + f);
}
catch (IOException i)
{
throw new ConfigurationException("Error while storing properties:"
+ i);
}
}
/**
* Apply the properties from this configuration to another instance.
*
* @param o
*/
public void applyProperties(Object o)
{
this.props.applyProperties(o);
}
public static String getClusterHome() throws ConfigurationException
{
if (clusterHomeName == null)
{
// Try to resolve it from a system property
clusterHomeName = System
.getProperty(ConfigurationConstants.CLUSTER_HOME);
}
if (clusterHomeName == null)
{
throw new ConfigurationException(
"You must have the system property cluster.home set.");
}
return clusterHomeName;
}
public static void setClusterHome(String chome)
{
clusterHomeName = chome;
}
/**
* Returns the cluster properties
*
* @return Returns the cluster properties.
*/
public TungstenProperties getProps()
{
return props;
}
public String getClusterName()
{
return clusterName;
}
public void setClusterName(String clusterName)
{
this.clusterName = clusterName;
}
public String getConfigFileNameInUse()
{
return (configFileNameInUse == null) ? "<unset>" : configFileNameInUse;
}
}