package cz.cuni.mff.d3s.been.cluster;
import static cz.cuni.mff.d3s.been.cluster.ClusterClientConfiguration.*;
import static cz.cuni.mff.d3s.been.cluster.ClusterConfiguration.*;
import static cz.cuni.mff.d3s.been.cluster.ClusterConfiguration.LOGGING_TYPE.NONE;
import static cz.cuni.mff.d3s.been.cluster.ClusterConfiguration.LOGGING_TYPE.SLF4J;
import static cz.cuni.mff.d3s.been.mapstore.MapStoreConfiguration.*;
import static java.util.concurrent.TimeUnit.SECONDS;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import com.hazelcast.client.AddressHelper;
import com.hazelcast.client.ClientConfig;
import com.hazelcast.config.*;
import com.hazelcast.nio.Address;
import cz.cuni.mff.d3s.been.util.PropertyReader;
/**
* Utility class for creating Hazelcast configurations.
*
* @author Martin Sixta
* @author Radek Macha
*/
final class InstanceConfigHelper {
/** Separator of list values */
static final String VALUE_SEPARATOR = ";";
/** Path to resource with default hazelcast configuration */
static final String CONFIG_RESOURCE = "/hazelcast.xml";
/** Name of hazelcast property for preferred TCP/IP stack */
static final String PROPERTY_HAZELCAST_PREFER_IPV4_STACK = "hazelcast.prefer.ipv4.stack";
/** Name of the hazelcast property to control binding of local interfaces */
static final String PROPERTY_HAZELCAST_SOCKET_BIND_ANY = "hazelcast.socket.bind.any";
/** How will Hazelcast log its messages */
private static final String PROPERTY_HAZELCAST_LOGGING_TYPE = "hazelcast.logging.type";
/**
* Properties which are used to create configs.
*
* The resulting properties are created by merging default and user
* properties.
*/
private final PropertyReader propReader;
/**
* Creates the helper class.
*
* @param userProperties
* user properties
*
*/
private InstanceConfigHelper(final Properties userProperties) {
this.propReader = PropertyReader.on(userProperties);
}
/**
* Creates configuration for a Hazelcast client
*
* @param userProperties
* BEEN's user-defined properties
* @return Hazelcast client configuration
* @throws ServiceException
* if configuration cannot be created
*/
static ClientConfig createClientConfig(final Properties userProperties) throws ServiceException {
return new InstanceConfigHelper(userProperties).createClientConfig();
}
/**
* Creates configuration for a Hazelcast cluster member
*
* @param userProperties
* BEEN's user-defined properties
* @return Hazelcast member configuration
* @throws ServiceException
* if configuration cannot be created
*/
static Config createMemberConfig(final Properties userProperties) throws ServiceException {
return new InstanceConfigHelper(userProperties).createMemberConfig();
}
/**
* The actual function which creates ClientConfig.
*
* @return {ClientConfig
* @throws ServiceException
* if configuration cannot be created
*/
ClientConfig createClientConfig() throws ServiceException {
final int timeout = (int) SECONDS.toMillis(propReader.getInteger(TIMEOUT, DEFAULT_TIMEOUT));
final List<InetSocketAddress> socketAddresses = getPeers(propReader.getString(MEMBERS, DEFAULT_MEMBERS));
final GroupConfig groupConfig = createGroupConfig();
ClientConfig clientConfig = new ClientConfig();
clientConfig.setConnectionTimeout(timeout).setGroupConfig(groupConfig).addInetSocketAddress(socketAddresses);
// There is no way to set property on the ClientConfig as far as I know (v2.5)
System.setProperty(PROPERTY_HAZELCAST_LOGGING_TYPE, getLoggingMode());
return clientConfig;
}
/**
*
* The actual function which creates Config.
*
* @return Hazelcast member configuration
* @throws ServiceException
* if configuration cannot be created
*/
Config createMemberConfig() throws ServiceException {
URL url = InstanceConfigHelper.class.getResource(CONFIG_RESOURCE);
Config config;
try {
// set logging type early otherwise loading of config will produce stderr messages
System.setProperty(PROPERTY_HAZELCAST_LOGGING_TYPE, getLoggingMode());
// create default config
config = new UrlXmlConfig(url);
} catch (Exception e) {
String msg = String.format("Cannot read Hazelcast's default configuration %s", CONFIG_RESOURCE);
throw new ServiceException(msg, e);
}
// override with user-defined configuration
overrideConfiguration(config);
setMapConfig(config);
return config;
}
/**
* Sets Maps properties
*
* @param config
* Hazelcast config
*/
private void setMapConfig(final Config config) {
MapConfig tasksMap = new MapConfig(Names.TASKS_MAP_NAME);
MapConfig contextsMap = new MapConfig(Names.TASK_CONTEXTS_MAP_NAME);
MapConfig benchmarksMap = new MapConfig(Names.BENCHMARKS_MAP_NAME);
MapConfig namedTaskDescriptorsMap = new MapConfig(Names.NAMED_TASK_DESCRIPTORS_MAP_NAME);
MapConfig namedTaskContextDescriptorsMap = new MapConfig(Names.NAMED_TASK_CONTEXT_DESCRIPTORS_MAP_NAME);
final int backupCount = propReader.getInteger(BACKUP_COUNT, DEFAULT_BACKUP_COUNT);
// do not locally cache to avoid synchronization hell
tasksMap.setCacheValue(false).setBackupCount(backupCount);
contextsMap.setBackupCount(backupCount);
benchmarksMap.setBackupCount(backupCount);
if (propReader.getBoolean(USE_MAP_STORE, DEFAULT_USE_MAP_STORE)) {
String factoryClassName = propReader.getString(MAP_STORE_FACTORY, DEFAULT_MAP_STORE_FACTORY);
int writeDelay = propReader.getInteger(MAP_STORE_WRITE_DELAY, DEFAULT_MAP_STORE_WRITE_DELAY);
MapStoreConfig mapStoreConfig = new MapStoreConfig();
mapStoreConfig.setEnabled(true).setFactoryClassName(factoryClassName).setWriteDelaySeconds(writeDelay);
String dbname = propReader.getString(MAP_STORE_DB_NAME, DEFAULT_MAP_STORE_DB_NAME);
String username = propReader.getString(MAP_STORE_DB_USERNAME, DEFAULT_MAP_STORE_DB_USERNAME);
String hostname = propReader.getString(MAP_STORE_DB_HOSTNAME, DEFAULT_MAP_STORE_DB_HOSTNAME);
String password = propReader.getString(MAP_STORE_DB_PASSWORD, DEFAULT_MAP_STORE_DB_PASSWORD);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
mapStoreConfig.setProperty(MAP_STORE_DB_USERNAME, username);
mapStoreConfig.setProperty(MAP_STORE_DB_PASSWORD, password);
mapStoreConfig.setProperty(MAP_STORE_DB_HOSTNAME, hostname);
mapStoreConfig.setProperty(MAP_STORE_DB_NAME, dbname);
tasksMap.setMapStoreConfig(mapStoreConfig);
contextsMap.setMapStoreConfig(mapStoreConfig);
benchmarksMap.setMapStoreConfig(mapStoreConfig);
namedTaskDescriptorsMap.setMapStoreConfig(mapStoreConfig);
namedTaskContextDescriptorsMap.setMapStoreConfig(mapStoreConfig);
}
config.addMapConfig(tasksMap).addMapConfig(contextsMap).addMapConfig(benchmarksMap).addMapConfig(
namedTaskDescriptorsMap).addMapConfig(namedTaskContextDescriptorsMap);
}
/**
* Overrides the default configuration with user-defined values.
*
* @param mainConfig
* Config to override
* @throws ServiceException
*/
private void overrideConfiguration(final Config mainConfig) throws ServiceException {
final int port = propReader.getInteger(PORT, DEFAULT_PORT);
final Interfaces interfaces = createInterfaces(propReader.getString(INTERFACES, DEFAULT_INTERFACES));
final GroupConfig groupConfig = createGroupConfig();
final Join joinConfig = createJoinConfig();
final NetworkConfig networkConfig = new NetworkConfig();
networkConfig.setPort(port).setInterfaces(interfaces).setJoin(joinConfig);
mainConfig.setNetworkConfig(networkConfig).setGroupConfig(groupConfig);
mainConfig.setProperty(
PROPERTY_HAZELCAST_PREFER_IPV4_STACK,
propReader.getBoolean(PREFER_IPV4, DEFAULT_PREFER_IPV4).toString());
mainConfig.setProperty(
PROPERTY_HAZELCAST_SOCKET_BIND_ANY,
propReader.getBoolean(SOCKET_BIND_ANY, DEFAULT_SOCKET_BIND_ANY).toString());
}
/**
* Creates Hazelcast {@link Join} from configuration.
*
* The {@link Join} class determines how cluster members will get to know
* about each other.
*
* @return Hazelcast {@link Join} configuration.
*
* @throws ServiceException
* when config cannot be created
*/
private Join createJoinConfig() throws ServiceException {
final Join join = new Join();
switch (getJoinType()) {
case MULTICAST:
join.setMulticastConfig(createMulticastConfig());
join.getTcpIpConfig().setEnabled(false);
break;
case TCP:
join.setTcpIpConfig(createTcpIpConfig());
join.getMulticastConfig().setEnabled(false); // enabled by default
break;
default:
throw new ServiceException("Unimplemented join type " + getJoinType());
}
return join;
}
/**
* Creates Hazelcast {@link GroupConfig} from configuration.
*
* This determines properties of the group of members.
*
* @return {@link GroupConfig} for the Hazelcast member
*/
private GroupConfig createGroupConfig() {
String group = propReader.getString(GROUP, DEFAULT_GROUP);
String password = propReader.getString(PASSWORD, DEFAULT_PASSWORD);
return new GroupConfig().setName(group).setPassword(password);
}
/**
* Creates Hazelcast {@link MulticastConfig} from configuration.
*
* @return {@link MulticastConfig} for the Hazelcast member
*
* @throws ServiceException
* when the config cannot be created due to config file errors
*/
private MulticastConfig createMulticastConfig() throws ServiceException {
MulticastConfig multicastConfig = new MulticastConfig();
multicastConfig.setEnabled(true);
multicastConfig.setMulticastGroup(propReader.getString(MULTICAST_GROUP, DEFAULT_MULTICAST_GROUP));
multicastConfig.setMulticastPort(propReader.getInteger(MULTICAST_PORT, DEFAULT_MULTICAST_PORT));
return multicastConfig;
}
/**
* Creates Hazelcast {@link TcpIpConfig} from configuration.
*
* @return {@link TcpIpConfig} for the Hazelcast member
*
* @throws ServiceException
* when the config cannot be created due to config file errors
*/
private TcpIpConfig createTcpIpConfig() throws ServiceException {
TcpIpConfig tcpIpConfig = new TcpIpConfig();
tcpIpConfig.setEnabled(true);
for (InetSocketAddress inetSocketAddress : getPeers(propReader.getString(TCP_MEMBERS, DEFAULT_TCP_MEMBERS))) {
tcpIpConfig.addAddress(new Address(inetSocketAddress));
}
return tcpIpConfig;
}
/**
* Creates configuration of Hazelcast's interfaces.
*
* @param interfaceList
* list of interfaces separated by
* {@link InstanceConfigHelper#VALUE_SEPARATOR}
* @return Interface configuration of Hazelcast
*/
private Interfaces createInterfaces(final String interfaceList) {
Interfaces interfaces = new Interfaces();
if (interfaceList == null || interfaceList.isEmpty()) {
interfaces.setEnabled(false);
return interfaces;
}
for (String ip : interfaceList.split(VALUE_SEPARATOR)) {
interfaces.addInterface(ip);
}
interfaces.setEnabled(true);
return interfaces;
}
/**
* Returns list of {@link InetSocketAddress} from a String.
*
* @param peersList
* List of peers separated by
* {@link InstanceConfigHelper#VALUE_SEPARATOR}
* @return List of peers from the {@code peersList}
*/
private List<InetSocketAddress> getPeers(final String peersList) {
List<InetSocketAddress> peers = new LinkedList<>();
for (String address : peersList.split(VALUE_SEPARATOR)) {
peers.addAll(AddressHelper.getSocketAddresses(address));
}
return peers;
}
/**
* Figures out what is the logging type for the instance.
*
* @return String representing logging type usable in setProperty calls on a
* Hazelcast instance
*/
private String getLoggingMode() {
final boolean enableHazelcastLogging = propReader.getBoolean(LOGGING, DEFAULT_LOGGING);
return enableHazelcastLogging ? SLF4J.name().toLowerCase() : NONE.name().toLowerCase();
}
/**
* Determines from the configuration how cluster members will join
*
* @return the join type
* @throws ServiceException
* if unknown join type is specified
*/
private JOIN_TYPE getJoinType() throws ServiceException {
String joinStringValue = propReader.getString(JOIN, DEFAULT_JOIN);
try {
return JOIN_TYPE.valueOf(joinStringValue.toUpperCase());
} catch (IllegalArgumentException e) {
String msg = String.format("Unknown join type %s", joinStringValue);
throw new ServiceException(msg, e);
}
}
}