blob: bb07c99ec94de5489fbdc018e47d9288be6205ed [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.sling.discovery.impl;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.apache.sling.discovery.base.connectors.BaseConfig;
import org.apache.sling.discovery.commons.providers.spi.base.DiscoveryLiteConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Configuration object used as a central config point for the discovery service
* implementation
* <p>
* The properties are described below under.
*/
@Component(metatype = true, label="%config.name", description="%config.description")
@Service(value = { Config.class, BaseConfig.class, DiscoveryLiteConfig.class })
public class Config implements BaseConfig, DiscoveryLiteConfig {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/** resource used to keep instance information such as last heartbeat, properties, incoming announcements **/
private static final String CLUSTERINSTANCES_RESOURCE = "clusterInstances";
/** resource used to store the sync tokens as part of a topology change **/
private static final String SYNC_TOKEN_RESOURCE = "syncTokens";
/** resource used to store the clusterNodeIds to slingIds map **/
private static final String ID_MAP_RESOURCE = "idMap";
/** resource used to keep the currently established view **/
private static final String ESTABLISHED_VIEW_RESOURCE = "establishedView";
/** resource used to keep the previously established view **/
private static final String PREVIOUS_VIEW_RESOURCE = "previousView";
/** resource used to keep ongoing votings **/
private static final String ONGOING_VOTING_RESOURCE = "ongoingVotings";
/** Configure the timeout (in seconds) after which an instance is considered dead/crashed. */
public static final long DEFAULT_HEARTBEAT_TIMEOUT = 120;
@Property(longValue=DEFAULT_HEARTBEAT_TIMEOUT)
public static final String HEARTBEAT_TIMEOUT_KEY = "heartbeatTimeout";
protected long heartbeatTimeout = DEFAULT_HEARTBEAT_TIMEOUT;
/** Configure the interval (in seconds) according to which the heartbeats are exchanged in the topology. */
public static final long DEFAULT_HEARTBEAT_INTERVAL = 30;
@Property(longValue=DEFAULT_HEARTBEAT_INTERVAL)
public static final String HEARTBEAT_INTERVAL_KEY = "heartbeatInterval";
protected long heartbeatInterval = DEFAULT_HEARTBEAT_INTERVAL;
/** Configure the time (in seconds) which must be passed at minimum between sending TOPOLOGY_CHANGING/_CHANGED (avoid flooding). */
public static final int DEFAULT_MIN_EVENT_DELAY = 3;
@Property(intValue=DEFAULT_MIN_EVENT_DELAY)
public static final String MIN_EVENT_DELAY_KEY = "minEventDelay";
protected int minEventDelay = DEFAULT_MIN_EVENT_DELAY;
/** Configure the socket connect timeout for topology connectors. */
public static final int DEFAULT_CONNECTION_TIMEOUT = 10;
@Property(intValue=DEFAULT_CONNECTION_TIMEOUT)
public static final String CONNECTION_TIMEOUT_KEY = "connectionTimeout";
private int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
/** Configure the socket read timeout (SO_TIMEOUT) for topology connectors. */
public static final int DEFAULT_SO_TIMEOUT = 10;
@Property(intValue=DEFAULT_SO_TIMEOUT)
public static final String SO_TIMEOUT_KEY = "soTimeout";
private int soTimeout = DEFAULT_SO_TIMEOUT;
/** Name of the repository descriptor to be taken into account for leader election:
those instances have preference to become leader which have the corresponding descriptor value of 'false' */
@Property
public static final String LEADER_ELECTION_REPOSITORY_DESCRIPTOR_NAME_KEY = "leaderElectionRepositoryDescriptor";
/**
* Whether or not (default false) the leaderElectionRepositoryDescriptor should be inverted (if that one
* is configured at all).
*/
@Property(boolValue=false)
public static final String INVERT_REPOSITORY_DESCRIPTOR_NAME_KEY = "invertRepositoryDescriptor";
/** URLs where to join a topology, eg http://localhost:4502/libs/sling/topology/connector */
@Property(cardinality=1024)
public static final String TOPOLOGY_CONNECTOR_URLS_KEY = "topologyConnectorUrls";
private URL[] topologyConnectorUrls = {null};
/** list of ips and/or hostnames which are allowed to connect to /libs/sling/topology/connector */
private static final String[] DEFAULT_TOPOLOGY_CONNECTOR_WHITELIST = {"localhost","127.0.0.1"};
@Property(value={"localhost","127.0.0.1"})
public static final String TOPOLOGY_CONNECTOR_WHITELIST_KEY = "topologyConnectorWhitelist";
private String[] topologyConnectorWhitelist = DEFAULT_TOPOLOGY_CONNECTOR_WHITELIST;
/** Path of resource where to keep discovery information, e.g /var/discovery/impl/ */
private static final String DEFAULT_DISCOVERY_RESOURCE_PATH = "/var/discovery/impl/";
@Property(value=DEFAULT_DISCOVERY_RESOURCE_PATH, propertyPrivate=true)
public static final String DISCOVERY_RESOURCE_PATH_KEY = "discoveryResourcePath";
private String discoveryResourcePath = DEFAULT_DISCOVERY_RESOURCE_PATH;
/**
* If set to true, local-loops of topology connectors are automatically stopped when detected so.
*/
@Property(boolValue=false)
private static final String AUTO_STOP_LOCAL_LOOP_ENABLED = "autoStopLocalLoopEnabled";
/**
* If set to true, request body will be gzipped - only works if counter-part accepts gzip-requests!
*/
@Property(boolValue=false)
private static final String GZIP_CONNECTOR_REQUESTS_ENABLED = "gzipConnectorRequestsEnabled";
/**
* If set to true, hmac is enabled and the white list is disabled.
*/
@Property(boolValue=false)
private static final String HMAC_ENABLED = "hmacEnabled";
/**
* If set to true, and the whitelist is disabled, messages will be encrypted.
*/
@Property(boolValue=false)
private static final String ENCRYPTION_ENABLED = "enableEncryption";
/**
* The value fo the shared key, shared amongst all instances in the same cluster.
*/
@Property
private static final String SHARED_KEY = "sharedKey";
/**
* The default lifetime of a HMAC shared key in ms. (4h)
*/
private static final long DEFAULT_SHARED_KEY_INTERVAL = 3600*1000*4;
@Property(longValue=DEFAULT_SHARED_KEY_INTERVAL)
private static final String SHARED_KEY_INTERVAL = "hmacSharedKeyTTL";
/**
* The property for defining the backoff factor for standby (loop) connectors
*/
@Property
private static final String BACKOFF_STANDBY_FACTOR = "backoffStandbyFactor";
private static final int DEFAULT_BACKOFF_STANDBY_FACTOR = 5;
/**
* The property for defining the maximum backoff factor for stable connectors
*/
@Property
private static final String BACKOFF_STABLE_FACTOR = "backoffStableFactor";
private static final int DEFAULT_BACKOFF_STABLE_FACTOR = 5;
/**
* when set to true and the syncTokenService (of discovery.commons) is available,
* then it is used
*/
@Property(boolValue=true)
private static final String USE_SYNC_TOKEN_SERVICE_ENABLED = "useSyncTokenService";
private String leaderElectionRepositoryDescriptor ;
private boolean invertRepositoryDescriptor = false; /* default: false */
/** True when auto-stop of a local-loop is enabled. Default is false. **/
private boolean autoStopLocalLoopEnabled;
/**
* True when the hmac is enabled and signing is disabled.
*/
private boolean hmacEnabled;
/**
* the shared key.
*/
private String sharedKey;
/**
* The key interval.
*/
private long keyInterval;
/**
* true when encryption is enabled.
*/
private boolean encryptionEnabled;
/**
* true when topology connector requests should be gzipped
*/
private boolean gzipConnectorRequestsEnabled;
/** the backoff factor to be used for standby (loop) connectors **/
private int backoffStandbyFactor = DEFAULT_BACKOFF_STANDBY_FACTOR;
/** the maximum backoff factor to be used for stable connectors **/
private int backoffStableFactor = DEFAULT_BACKOFF_STABLE_FACTOR;
/**
* when set to true and the syncTokenService (of discovery.commons) is available,
* then it is used.
*/
private boolean useSyncTokenService = true;
@Activate
protected void activate(final Map<String, Object> properties) {
logger.debug("activate: config activated.");
configure(properties);
}
protected void configure(final Map<String, Object> properties) {
this.heartbeatTimeout = PropertiesUtil.toLong(
properties.get(HEARTBEAT_TIMEOUT_KEY),
DEFAULT_HEARTBEAT_TIMEOUT);
logger.debug("configure: heartbeatTimeout='{}'", this.heartbeatTimeout);
this.heartbeatInterval = PropertiesUtil.toLong(
properties.get(HEARTBEAT_INTERVAL_KEY),
DEFAULT_HEARTBEAT_INTERVAL);
logger.debug("configure: heartbeatInterval='{}'",
this.heartbeatInterval);
this.minEventDelay = PropertiesUtil.toInteger(
properties.get(MIN_EVENT_DELAY_KEY),
DEFAULT_MIN_EVENT_DELAY);
logger.debug("configure: minEventDelay='{}'",
this.minEventDelay);
this.connectionTimeout = PropertiesUtil.toInteger(
properties.get(CONNECTION_TIMEOUT_KEY),
DEFAULT_CONNECTION_TIMEOUT);
logger.debug("configure: connectionTimeout='{}'",
this.connectionTimeout);
this.soTimeout = PropertiesUtil.toInteger(
properties.get(SO_TIMEOUT_KEY),
DEFAULT_SO_TIMEOUT);
logger.debug("configure: soTimeout='{}'",
this.soTimeout);
String[] topologyConnectorUrlsStr = PropertiesUtil.toStringArray(
properties.get(TOPOLOGY_CONNECTOR_URLS_KEY), null);
if (topologyConnectorUrlsStr!=null && topologyConnectorUrlsStr.length > 0) {
List<URL> urls = new LinkedList<URL>();
for (int i = 0; i < topologyConnectorUrlsStr.length; i++) {
String anUrlStr = topologyConnectorUrlsStr[i];
try {
if (anUrlStr!=null && anUrlStr.length()>0) {
URL url = new URL(anUrlStr);
logger.debug("configure: a topologyConnectorbUrl='{}'",
url);
urls.add(url);
}
} catch (MalformedURLException e) {
logger.error("configure: could not set a topologyConnectorUrl: " + e,
e);
}
}
if (urls.size()>0) {
this.topologyConnectorUrls = urls.toArray(new URL[urls.size()]);
logger.debug("configure: number of topologyConnectorUrls='{}''",
urls.size());
} else {
this.topologyConnectorUrls = null;
logger.debug("configure: no (valid) topologyConnectorUrls configured");
}
} else {
this.topologyConnectorUrls = null;
logger.debug("configure: no (valid) topologyConnectorUrls configured");
}
this.topologyConnectorWhitelist = PropertiesUtil.toStringArray(
properties.get(TOPOLOGY_CONNECTOR_WHITELIST_KEY),
DEFAULT_TOPOLOGY_CONNECTOR_WHITELIST);
logger.debug("configure: topologyConnectorWhitelist='{}'",
this.topologyConnectorWhitelist);
this.discoveryResourcePath = PropertiesUtil.toString(
properties.get(DISCOVERY_RESOURCE_PATH_KEY),
"");
while(this.discoveryResourcePath.endsWith("/")) {
this.discoveryResourcePath = this.discoveryResourcePath.substring(0,
this.discoveryResourcePath.length()-1);
}
this.discoveryResourcePath = this.discoveryResourcePath + "/";
if (this.discoveryResourcePath==null || this.discoveryResourcePath.length()<=1) {
// if the path is empty, or /, then use the default
this.discoveryResourcePath = DEFAULT_DISCOVERY_RESOURCE_PATH;
}
logger.debug("configure: discoveryResourcePath='{}'",
this.discoveryResourcePath);
this.leaderElectionRepositoryDescriptor = PropertiesUtil.toString(
properties.get(LEADER_ELECTION_REPOSITORY_DESCRIPTOR_NAME_KEY),
null);
logger.debug("configure: leaderElectionRepositoryDescriptor='{}'",
this.leaderElectionRepositoryDescriptor);
this.invertRepositoryDescriptor = PropertiesUtil.toBoolean(
properties.get(INVERT_REPOSITORY_DESCRIPTOR_NAME_KEY),
false /* default: false*/);
logger.debug("configure: invertRepositoryDescriptor='{}'",
this.invertRepositoryDescriptor);
autoStopLocalLoopEnabled = PropertiesUtil.toBoolean(properties.get(AUTO_STOP_LOCAL_LOOP_ENABLED), false);
gzipConnectorRequestsEnabled = PropertiesUtil.toBoolean(properties.get(GZIP_CONNECTOR_REQUESTS_ENABLED), false);
hmacEnabled = PropertiesUtil.toBoolean(properties.get(HMAC_ENABLED), true);
encryptionEnabled = PropertiesUtil.toBoolean(properties.get(ENCRYPTION_ENABLED), false);
sharedKey = PropertiesUtil.toString(properties.get(SHARED_KEY), null);
keyInterval = PropertiesUtil.toLong(SHARED_KEY_INTERVAL, DEFAULT_SHARED_KEY_INTERVAL);
backoffStandbyFactor = PropertiesUtil.toInteger(properties.get(BACKOFF_STANDBY_FACTOR),
DEFAULT_BACKOFF_STANDBY_FACTOR);
backoffStableFactor = PropertiesUtil.toInteger(properties.get(BACKOFF_STABLE_FACTOR),
DEFAULT_BACKOFF_STABLE_FACTOR);
useSyncTokenService = PropertiesUtil.toBoolean(properties.get(USE_SYNC_TOKEN_SERVICE_ENABLED), true);
}
/**
* Returns the timeout (in seconds) after which an instance or voting is considered invalid/timed out
* @return the timeout (in seconds) after which an instance or voting is considered invalid/timed out
*/
public long getHeartbeatTimeout() {
return heartbeatTimeout;
}
/**
* Returns the timeout (in milliseconds) after which an instance or voting is considered invalid/timed out
* @return the timeout (in milliseconds) after which an instance or voting is considered invalid/timed out
*/
public long getHeartbeatTimeoutMillis() {
return getHeartbeatTimeout() * 1000;
}
/**
* Returns the socket connect() timeout used by the topology connector, 0 disables the timeout
* @return the socket connect() timeout used by the topology connector, 0 disables the timeout
*/
public int getSocketConnectTimeout() {
return connectionTimeout;
}
/**
* Returns the socket read timeout (SO_TIMEOUT) used by the topology connector, 0 disables the timeout
* @return the socket read timeout (SO_TIMEOUT) used by the topology connector, 0 disables the timeout
*/
public int getSoTimeout() {
return soTimeout;
}
/**
* Returns the interval (in seconds) in which heartbeats are sent
* @return the interval (in seconds) in which heartbeats are sent
*/
public long getHeartbeatInterval() {
return heartbeatInterval;
}
/**
* Returns the minimum time (in seconds) between sending TOPOLOGY_CHANGING/_CHANGED events - to avoid flooding
* @return the minimum time (in seconds) between sending TOPOLOGY_CHANGING/_CHANGED events - to avoid flooding
*/
public int getMinEventDelay() {
return minEventDelay;
}
/**
* Returns the URLs to which to open a topology connector - or null/empty if no topology connector
* is configured (default is null)
* @return the URLs to which to open a topology connector - or null/empty if no topology connector
* is configured
*/
public URL[] getTopologyConnectorURLs() {
return topologyConnectorUrls;
}
/**
* Returns a comma separated list of hostnames and/or ip addresses which are allowed as
* remote hosts to open connections to the topology connector servlet
* @return a comma separated list of hostnames and/or ip addresses which are allowed as
* remote hosts to open connections to the topology connector servlet
*/
public String[] getTopologyConnectorWhitelist() {
return topologyConnectorWhitelist;
}
public String getDiscoveryResourcePath() {
return discoveryResourcePath;
}
/**
* Returns the resource path where cluster instance informations are stored.
* @return the resource path where cluster instance informations are stored
*/
public String getClusterInstancesPath() {
return getDiscoveryResourcePath() + CLUSTERINSTANCES_RESOURCE;
}
/**
* Returns the resource path where the established view is stored.
* @return the resource path where the established view is stored
*/
public String getEstablishedViewPath() {
return getDiscoveryResourcePath() + ESTABLISHED_VIEW_RESOURCE;
}
/**
* Returns the resource path where ongoing votings are stored.
* @return the resource path where ongoing votings are stored
*/
public String getOngoingVotingsPath() {
return getDiscoveryResourcePath() + ONGOING_VOTING_RESOURCE;
}
/**
* Returns the resource path where the previous view is stored.
* @return the resource path where the previous view is stored
*/
public String getPreviousViewPath() {
return getDiscoveryResourcePath() + PREVIOUS_VIEW_RESOURCE;
}
/**
* Returns the repository descriptor key which is to be included in the
* cluster leader election - or null.
* <p>
* When set, the value (treated as a boolean) of the repository descriptor
* is prepended to the leader election id.
* @return the repository descriptor key which is to be included in the
* cluster leader election - or null
*/
public String getLeaderElectionRepositoryDescriptor() {
return leaderElectionRepositoryDescriptor;
}
/**
* Returns true when the value of the repository descriptor identified
* via the property 'leaderElectionRepositoryDescriptor' should be
* inverted - only applies when 'leaderElectionRepositoryDescriptor'
* is configured of course.
* @return true when property resulting from 'leaderElectionRepositoryDescriptor'
* should be inverted, false if it should remain unchanged.
*/
public boolean shouldInvertRepositoryDescriptor() {
return invertRepositoryDescriptor;
}
/**
* @return true if hmac is enabled.
*/
public boolean isHmacEnabled() {
return hmacEnabled;
}
/**
* @return the shared key
*/
public String getSharedKey() {
return sharedKey;
}
/**
* @return the interval of the shared key for hmac.
*/
public long getKeyInterval() {
return keyInterval;
}
/**
* @return true if encryption is enabled.
*/
public boolean isEncryptionEnabled() {
return encryptionEnabled;
}
/**
* @return true if requests on the topology connector should be gzipped
* (which only works if the server accepts that.. ie discovery.impl 1.0.4+)
*/
public boolean isGzipConnectorRequestsEnabled() {
return gzipConnectorRequestsEnabled;
}
/**
* @return true if the auto-stopping of local-loop topology connectors is enabled.
*/
public boolean isAutoStopLocalLoopEnabled() {
return autoStopLocalLoopEnabled;
}
/**
* Returns the backoff factor to be used for standby (loop) connectors
* @return the backoff factor to be used for standby (loop) connectors
*/
public int getBackoffStandbyFactor() {
return backoffStandbyFactor;
}
/**
* Returns the (maximum) backoff factor to be used for stable connectors
* @return the (maximum) backoff factor to be used for stable connectors
*/
public int getBackoffStableFactor() {
return backoffStableFactor;
}
/**
* Returns the backoff interval for standby (loop) connectors in seconds
* @return the backoff interval for standby (loop) connectors in seconds
*/
public long getBackoffStandbyInterval() {
final int factor = getBackoffStandbyFactor();
if (factor<=1) {
return -1;
} else {
return factor * getHeartbeatInterval();
}
}
@Override
public long getConnectorPingInterval() {
return getHeartbeatInterval();
}
@Override
public long getConnectorPingTimeout() {
return getHeartbeatTimeout();
}
@Override
public String getSyncTokenPath() {
return getDiscoveryResourcePath() + SYNC_TOKEN_RESOURCE;
}
@Override
public String getIdMapPath() {
return getDiscoveryResourcePath() + ID_MAP_RESOURCE;
}
@Override
public long getClusterSyncServiceTimeoutMillis() {
return -1;
}
@Override
public long getClusterSyncServiceIntervalMillis() {
return 1000;
}
public boolean useSyncTokenService() {
return useSyncTokenService;
}
}