/* * * * RHQ Management Platform * * Copyright (C) 2005-2012 Red Hat, Inc. * * All rights reserved. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License, version 2, as * * published by the Free Software Foundation, and/or the GNU Lesser * * General Public License, version 2.1, also as published by the Free * * Software Foundation. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License and the GNU Lesser General Public License * * for more details. * * * * You should have received a copy of the GNU General Public License * * and the GNU Lesser General Public License along with this program; * * if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ package org.rhq.cassandra; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.util.Map; import java.util.Properties; import java.util.TreeMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.rhq.core.util.TokenReplacingProperties; import org.rhq.core.util.file.FileUtil; /** * <p> * A container for deployment options and Cassandra configuration settings. A * DeploymentOptions object represents the merger of properties defined in * cassandra.properties or defined as system properties. System properties take precedence * over corresponding properties in cassandra.properties. * </p> * <p> * Properties are "sticky". Like Ant properties, once set a property's value cannot be * changed. This means that if you set a property by calling its setter method prior to * invoking {@link #load()}, that value will be retained even if that property is also * defined as a system property and in cassandra.properties. * </p> * * @author John Sanda */ public class DeploymentOptions { private final Log log = LogFactory.getLog(DeploymentOptions.class); private boolean loaded; // If you add a new field make sure that it is exposed as a "sticky" property. In // other words, once set the property's value does not change again. See // setClusterDir below for an example. private String clusterDir; private String basedir; private Integer numNodes; private Boolean embedded; private String loggingLevel; private Integer numTokens; private Integer cqlPort; private Boolean startRpc; private Integer rpcPort; private Integer nativeTransportMaxThreads; private String username; private String password; private String authenticator; private String authorizer; private String dataDir; private String commitLogDir; private String savedCachesDir; private String listenAddress; private String rpcAddress; private Integer jmxPort; private Integer gossipPort; private String seeds; private String heapSize; private String heapNewSize; private String logFileName; private String stackSize; DeploymentOptions() { } /** * Initializes any properties that are not already set. Values are assigned from * system properties and from the cassandra.properties file that is expected to * be on the classpath. System properties are given precedence over corresponding * properties in cassandra.properties. * * @throws IOException If an error occurs loading cassandra.properties */ public void load() throws IOException { if (loaded) { return; } InputStream stream = null; try { stream = getClass().getResourceAsStream("/cassandra.properties"); Properties props = new Properties(); props.load(stream); init(props); loaded = true; } catch (IOException e) { log.warn("Unable to load deployment options from cassandra.properties."); log.info("The following error occurred while trying to load options.", e); throw e; } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { String msg = "An error occurred while closing input stream on cassandra.properties"; log.info(msg, e); } } } } private void init(Properties properties) { setUsername(loadProperty("rhq.storage.username", properties)); setPassword(loadProperty("rhq.storage.password", properties)); setBasedir(loadProperty("rhq.storage.basedir", properties)); setClusterDir(loadProperty("rhq.storage.cluster.dir", properties)); setNumNodes(Integer.parseInt(loadProperty("rhq.storage.cluster.num-nodes", properties))); setEmbedded(Boolean.valueOf(loadProperty("rhq.storage.cluster.is-embedded", properties))); setLoggingLevel(loadProperty("rhq.storage.logging.level", properties)); setLogFileName(loadProperty("rhq.storage.log.file", properties)); setRpcPort(Integer.valueOf(loadProperty("rhq.storage.rpc-port", properties))); setCqlPort(Integer.valueOf(loadProperty("rhq.storage.cql-port", properties))); setJmxPort(Integer.valueOf(loadProperty("rhq.storage.jmx-port", properties))); setGossipPort(Integer.valueOf(loadProperty("rhq.storage.gossip-port", properties))); setNumTokens(Integer.valueOf(loadProperty("rhq.storage.num-tokens", properties))); setNativeTransportMaxThreads(Integer.valueOf(loadProperty("rhq.storage.native-transport-max-threads", properties))); setAuthenticator(loadProperty("rhq.storage.authenticator", properties)); setAuthorizer(loadProperty("rhq.storage.authorizer", properties)); setDataDir(loadProperty("rhq.storage.data", properties)); setCommitLogDir(loadProperty("rhq.storage.commitlog", properties)); setSavedCachesDir(loadProperty("rhq.storage.saved-caches", properties)); setSeeds(loadProperty("rhq.storage.seeds", properties)); setListenAddress(loadProperty("rhq.storage.listen.address", properties)); setStartRpc(Boolean.valueOf(loadProperty("rhq.storage.start_rpc", properties))); setRpcAddress(loadProperty("rhq.storage.rpc.address", properties)); setHeapSize(loadProperty("rhq.storage.heap-size", properties)); setHeapNewSize(loadProperty("rhq.storage.heap-new-size", properties)); setStackSize(loadProperty("rhq.storage.stack-size", properties)); } private String loadProperty(String key, Properties properties) { String value = System.getProperty(key); if (value == null || value.isEmpty()) { return properties.getProperty(key); } return value; } public void merge(DeploymentOptions other) { setClusterDir(other.clusterDir); setNumNodes(other.numNodes); setEmbedded(other.embedded); setLoggingLevel(other.loggingLevel); setNumTokens(other.numTokens); setCqlPort(cqlPort); setNativeTransportMaxThreads(other.nativeTransportMaxThreads); setUsername(other.username); setPassword(other.password); setAuthenticator(other.authenticator); setAuthorizer(other.authorizer); setDataDir(other.dataDir); setCommitLogDir(other.commitLogDir); setSavedCachesDir(other.savedCachesDir); setLogFileName(other.logFileName); setListenAddress(other.listenAddress); setRpcAddress(other.rpcAddress); setStartRpc(other.startRpc); setRpcPort(other.rpcPort); setJmxPort(other.jmxPort); setGossipPort(other.gossipPort); setSeeds(other.seeds); setBasedir(other.basedir); setHeapSize(other.heapSize); setHeapNewSize(other.heapNewSize); setStackSize(other.stackSize); } public TokenReplacingProperties toMap() { try { BeanInfo beanInfo = Introspector.getBeanInfo(DeploymentOptions.class); Map<String, String> properties = new TreeMap<String, String>(); for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) { if (pd.getReadMethod() == null) { throw new RuntimeException("The [" + pd.getName() + "] property must define a getter method"); } Method method = pd.getReadMethod(); DeploymentProperty deploymentProperty = method.getAnnotation(DeploymentProperty.class); if (deploymentProperty != null) { Object value = method.invoke(this, null); if (value != null) { properties.put(deploymentProperty.name(), value.toString()); } } } return new TokenReplacingProperties(properties); } catch (Exception e) { throw new RuntimeException("Failed to convert " + DeploymentOptions.class.getName() + " to a map", e); } } /** * @return The directory in which nodes will be installed. This only applies to * embedded clusters. */ @DeploymentProperty(name = "cluster.dir") public String getClusterDir() { return clusterDir; } /** * @param dir The directory in which nodes will be installed. This only applies to * embedded clusters. */ public void setClusterDir(String dir) { if (clusterDir == null) { clusterDir = FileUtil.useForwardSlash(dir); } } /** * @return The directory in which the node will be installed. */ @DeploymentProperty(name = "rhq.storage.basedir") public String getBasedir() { return basedir; } /** * @param dir The directory in which the node will be installed. */ public void setBasedir(String dir) { if (basedir == null) { basedir = FileUtil.useForwardSlash(dir); } } /** * @return The number of nodes in the cluster. This only applies to embedded clusters. */ @DeploymentProperty(name = "rhq.storage.cluster.num-nodes") public int getNumNodes() { return numNodes; } /** * @param numNodes The number of nodes in the cluster. This only applies to embedded * clusters. */ public void setNumNodes(int numNodes) { if (this.numNodes == null) { this.numNodes = numNodes; } } /** * @return true is this is an embedded deployment, false otherwise. Note that an * embedded cluster is one in which all nodes run on a single host and can only accept * requests from that same host. */ @DeploymentProperty(name = "rhq.storage.cluster.is-embedded") public boolean isEmbedded() { return embedded; } /** * @param embedded A flag that indicates whether or not this is an embedded deployment. * Note than embedded cluster is one in which all nodes run on a single host and can * only accept requests from that same host. */ public void setEmbedded(boolean embedded) { if (this.embedded == null) { this.embedded = embedded; } } /** * @return The log4j logging level that Cassandra uses */ @DeploymentProperty(name = "rhq.storage.logging.level") public String getLoggingLevel() { return loggingLevel; } /** * @param loggingLevel The log4j logging level that Cassandra uses */ public void setLoggingLevel(String loggingLevel) { if (this.loggingLevel == null) { this.loggingLevel = loggingLevel; } } /** * @return The number of tokens assigned to this the node on the ring. Defaults to 256. */ @DeploymentProperty(name = "rhq.storage.num_tokens") public Integer getNumTokens() { return numTokens; } /** * @param numTokens The number of tokens assigned to this node on the ring. Defaults to * 256. */ public void setNumTokens(int numTokens) { if (this.numTokens == null) { this.numTokens = numTokens; } } /** * @return The port on which Cassandra listens for client requests. */ @DeploymentProperty(name = "rhq.storage.cql-port") public Integer getCqlPort() { return cqlPort; } /** * @param port The port on which Cassandra listens for client requests. */ public void setCqlPort(Integer port) { if (cqlPort == null) { cqlPort = port; } } /** * @return true whether the Thrift-based RPC should be started */ @DeploymentProperty(name = "rhq.storage.start_rpc") public Boolean getStartRpc() { return startRpc; } /** * @param startRpc whether the Thrift-based RPC should be started */ public void setStartRpc(Boolean startRpc) { if (this.startRpc == null) { this.startRpc = startRpc; } } @DeploymentProperty(name = "rhq.storage.rpc_port") public Integer getRpcPort() { return rpcPort; } public void setRpcPort(Integer port) { if (rpcPort == null) { rpcPort = port; } } /** * @return The max number of threads to handle CQL requests */ @DeploymentProperty(name = "rhq.storage.native_transport_max_threads") public Integer getNativeTransportMaxThreads() { return nativeTransportMaxThreads; } /** * @param numThreads The max number of threads to handle CQL requests */ public void setNativeTransportMaxThreads(Integer numThreads) { if (nativeTransportMaxThreads == null) { nativeTransportMaxThreads = numThreads; } } /** * @return The username RHQ will use to make client connections to Cassandra. This is * <strong>not</strong> a Cassandra configuration property. This deployment property is * written to rhq-server.properties at build time by the rhq-container.build.xml script. */ @DeploymentProperty(name = "rhq.storage.username") public String getUsername() { return username; } /** * @param username The username RHQ will use to make client connections to Cassandra. * This is <strong>not</strong> a Cassandra configuration property. This deployment * property is written to rhq-server.properties at build time by the * rhq-container.build.xml script. */ public void setUsername(String username) { if (this.username == null) { this.username = username; } } /** * @return The password RHQ will use to make client connections to Cassandra. This is * <strong>not</strong> a Cassandra configuration property. This deployment property is * written to rhq-server.properties at build time by the rhq-container.build.xml script. */ @DeploymentProperty(name = "rhq.storage.password") public String getPassword() { return password; } /** * @param password The password RHQ will use to make client connections to Cassandra. * This is <strong>not</strong> a Cassandra configuration property. This deployment * property is written to rhq-server.properties at build time by the * rhq-container.build.xml script. */ public void setPassword(String password) { if (this.password == null) { this.password = password; } } /** * @return The FQCN of the class that handles Cassandra authentication */ @DeploymentProperty(name = "rhq.storage.authenticator") public String getAuthenticator() { return authenticator; } /** * @param authenticator The FQCN of the class that handles Cassandra authentication */ public void setAuthenticator(String authenticator) { if (this.authenticator == null) { this.authenticator = authenticator; } } /** * @return The FQCN of the class that handles Cassandra authorization */ @DeploymentProperty(name = "rhq.storage.authorizer") public String getAuthorizer() { return authorizer; } /** * @param authorizer The FQCN of the class that handles Cassandra authorization */ public void setAuthorizer(String authorizer) { if (this.authorizer == null) { this.authorizer = authorizer; } } /** * @return The directory where Cassandra stores data on disk */ @DeploymentProperty(name = "rhq.storage.data") public String getDataDir() { return dataDir; } /** * @param dir The directory where Cassandra stores data on disk */ public void setDataDir(String dir) { if (dataDir == null) { dataDir = FileUtil.useForwardSlash(dir); } } /** * @return The directory where Cassandra stores commit log files */ @DeploymentProperty(name = "rhq.storage.commitlog") public String getCommitLogDir() { return commitLogDir; } /** * @param dir The directory where Cassandra stores commit log files */ public void setCommitLogDir(String dir) { if (commitLogDir == null) { commitLogDir = FileUtil.useForwardSlash(dir); } } /** * @return The directory where Cassandra stores saved caches on disk */ @DeploymentProperty(name = "rhq.storage.saved-caches") public String getSavedCachesDir() { return savedCachesDir; } /** * @param dir The direcotry where Cassandra stores saved caches on disk */ public void setSavedCachesDir(String dir) { if (savedCachesDir == null) { savedCachesDir = FileUtil.useForwardSlash(dir); } } /** * @return The full path of the Log4J log file to which Cassandra writes. */ @DeploymentProperty(name = "rhq.storage.log.file") public String getLogFileName() { return logFileName; } /** * @param name The full path of the Log4J log file to which Cassandra writes. */ public void setLogFileName(String name) { if (logFileName == null) { logFileName = FileUtil.useForwardSlash(name); } } /** * @return The address to which Cassandra binds and tells other node to connect to */ @DeploymentProperty(name = "rhq.storage.listen.address") public String getListenAddress() { return listenAddress; } /** * @param address The address to which Cassandra binds and tells other nodes to connect to */ public void setListenAddress(String address) { if (listenAddress == null) { listenAddress = address; } } @DeploymentProperty(name = "rpc.address") public String getRpcAddress() { return rpcAddress; } public void setRpcAddress(String address) { if (rpcAddress == null) { rpcAddress = address; } } /** * @return The port on which Cassandra listens for JMX connections */ @DeploymentProperty(name = "rhq.storage.jmx-port") public Integer getJmxPort() { return jmxPort; } /** * @param port The port on which Cassandra listens for JMX connections */ public void setJmxPort(Integer port) { if (jmxPort == null) { jmxPort = port; } } /** * @return The port on which Cassandra listens for gossip requests */ @DeploymentProperty(name = "rhq.storage.gossip-port") public Integer getGossipPort() { return gossipPort; } /** * @param port The port on which Cassandra listens for gossip requests */ public void setGossipPort(Integer port) { if (gossipPort == null) { gossipPort = port; } } /** * @return A comma-delimited list of IP addresses/host names that are deemed contact * points during node start up to learn about the ring topology. */ @DeploymentProperty(name = "rhq.storage.seeds") public String getSeeds() { return seeds; } /** * @param seeds A comma-delimited list of IP addresses/host names that are deemed * contact points during node start up to learn about the ring topology. */ public void setSeeds(String seeds) { if (this.seeds == null) { this.seeds = seeds; } } /** * @return The value to use for both the max and min heap sizes. Defaults to * ${MAX_HEAP_SIZE} which allows the cassandra-env.sh script to determine the value. */ @DeploymentProperty(name = "rhq.storage.heap-size") public String getHeapSize() { return heapSize; } /** * @param heapSize The value to use for both the max and min heap sizes. This needs to * be a value value recognized by the -Xmx and -Xms options such as 512M. */ public void setHeapSize(String heapSize) { if (this.heapSize == null) { this.heapSize = heapSize; } } /** * @return The value to use for the size of the new generation. Defaults to * ${HEAP_NEWSIZE} which allows the cassandra-env.sh script to determine the value. */ @DeploymentProperty(name = "rhq.storage.heap-new-size") public String getHeapNewSize() { return heapNewSize; } /** * @param heapNewSize The value to use for the size of the new generation. This needs * to be a valid value recognized by the -Xmn option such as 256M. * is passed directly to the -Xmn option so it */ public void setHeapNewSize(String heapNewSize) { if (this.heapNewSize == null) { this.heapNewSize = heapNewSize; } } /** * @return The value to use for the JVM stack size. This is passed directly to the -Xss * JVM start up option. */ @DeploymentProperty(name = "rhq.storage.stack-size") public String getStackSize() { return stackSize; } /** * @param size The value to use for the JVM stack size which is passed directly to the * -Xss JVM start up option. */ public void setStackSize(String size) { if (stackSize == null) { stackSize = size; } } }