blob: d5008eb8ad6a99af98ec91c288ee49178a13dce3 [file] [log] [blame]
/*
* Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.glassfish.gms;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import org.glassfish.api.logging.LogLevel;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.glassfish.api.admin.ServerEnvironment;
import org.glassfish.api.event.EventListener;
import org.glassfish.api.event.EventTypes;
import org.glassfish.api.event.Events;
import org.glassfish.gms.bootstrap.GMSAdapter;
import org.glassfish.gms.bootstrap.HealthHistory;
import org.glassfish.hk2.api.PerLookup;
import org.glassfish.hk2.api.PostConstruct;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.logging.annotation.LogMessageInfo;
import org.glassfish.logging.annotation.LogMessagesResourceBundle;
import org.glassfish.logging.annotation.LoggerInfo;
import org.jvnet.hk2.annotations.Service;
import org.jvnet.hk2.config.Dom;
import org.jvnet.hk2.config.types.Property;
import com.sun.enterprise.config.serverbeans.Cluster;
import com.sun.enterprise.config.serverbeans.Clusters;
import com.sun.enterprise.config.serverbeans.Config;
import com.sun.enterprise.config.serverbeans.Domain;
import com.sun.enterprise.config.serverbeans.Node;
import com.sun.enterprise.config.serverbeans.Nodes;
import com.sun.enterprise.config.serverbeans.Server;
import com.sun.enterprise.config.serverbeans.ServerRef;
import com.sun.enterprise.config.serverbeans.Servers;
import com.sun.enterprise.ee.cms.core.AliveAndReadySignal;
import com.sun.enterprise.ee.cms.core.AliveAndReadyView;
import com.sun.enterprise.ee.cms.core.CallBack;
import com.sun.enterprise.ee.cms.core.FailureNotificationActionFactory;
import com.sun.enterprise.ee.cms.core.FailureNotificationSignal;
import com.sun.enterprise.ee.cms.core.FailureRecoverySignal;
import com.sun.enterprise.ee.cms.core.FailureSuspectedActionFactory;
import com.sun.enterprise.ee.cms.core.GMSConstants;
import com.sun.enterprise.ee.cms.core.GMSException;
import com.sun.enterprise.ee.cms.core.GMSFactory;
import com.sun.enterprise.ee.cms.core.GroupLeadershipNotificationActionFactory;
import com.sun.enterprise.ee.cms.core.GroupManagementService;
import com.sun.enterprise.ee.cms.core.JoinNotificationActionFactory;
import com.sun.enterprise.ee.cms.core.JoinedAndReadyNotificationActionFactory;
import com.sun.enterprise.ee.cms.core.JoinedAndReadyNotificationSignal;
import com.sun.enterprise.ee.cms.core.PlannedShutdownActionFactory;
import com.sun.enterprise.ee.cms.core.PlannedShutdownSignal;
import com.sun.enterprise.ee.cms.core.ServiceProviderConfigurationKeys;
import com.sun.enterprise.ee.cms.core.Signal;
import com.sun.enterprise.ee.cms.impl.client.FailureNotificationActionFactoryImpl;
import com.sun.enterprise.ee.cms.impl.client.FailureRecoveryActionFactoryImpl;
import com.sun.enterprise.ee.cms.impl.client.FailureSuspectedActionFactoryImpl;
import com.sun.enterprise.ee.cms.impl.client.GroupLeadershipNotificationActionFactoryImpl;
import com.sun.enterprise.ee.cms.impl.client.JoinNotificationActionFactoryImpl;
import com.sun.enterprise.ee.cms.impl.client.JoinedAndReadyNotificationActionFactoryImpl;
import com.sun.enterprise.ee.cms.impl.client.MessageActionFactoryImpl;
import com.sun.enterprise.ee.cms.impl.client.PlannedShutdownActionFactoryImpl;
import com.sun.enterprise.mgmt.transport.NetworkUtility;
import com.sun.enterprise.mgmt.transport.grizzly.GrizzlyConfigConstants;
import com.sun.enterprise.util.io.ServerDirs;
import com.sun.logging.LogDomains;
/**
* @author Sheetal.Vartak@Sun.COM
*/
@PerLookup
@Service()
public class GMSAdapterImpl implements GMSAdapter, PostConstruct, CallBack {
//private static final Logger logger =
// LogDomains.getLogger(GMSAdapterImpl.class, LogDomains.GMS_LOGGER);
private static final String BEGINS_WITH = "^";
private static final String GMS_PROPERTY_PREFIX = "GMS_";
private static final String GMS_PROPERTY_PREFIX_REGEXP = BEGINS_WITH + GMS_PROPERTY_PREFIX;
private GroupManagementService gms;
private final static String CORE = "CORE";
private final static String SPECTATOR = "SPECTATOR";
private final static String MEMBERTYPE_STRING = "MEMBER_TYPE";
// all set in postConstruct
private String instanceName = null;
private boolean isDas = false;
private Cluster cluster = null;
private String clusterName = null;
private Config clusterConfig = null;
//private long joinTime = 0L;
private ConcurrentHashMap<CallBack, JoinNotificationActionFactory> callbackJoinActionFactoryMapping =
new ConcurrentHashMap<CallBack, JoinNotificationActionFactory>();
private ConcurrentHashMap<CallBack, JoinedAndReadyNotificationActionFactory> callbackJoinedAndReadyActionFactoryMapping =
new ConcurrentHashMap<CallBack, JoinedAndReadyNotificationActionFactory>();
private ConcurrentHashMap<CallBack, FailureNotificationActionFactory> callbackFailureActionFactoryMapping =
new ConcurrentHashMap<CallBack, FailureNotificationActionFactory>();
private ConcurrentHashMap<CallBack, FailureSuspectedActionFactory> callbackFailureSuspectedActionFactoryMapping =
new ConcurrentHashMap<CallBack, FailureSuspectedActionFactory>();
private ConcurrentHashMap<CallBack, PlannedShutdownActionFactory> callbackPlannedShutdownActionFactoryMapping =
new ConcurrentHashMap<CallBack, PlannedShutdownActionFactory>();
private EventListener glassfishEventListener = null;
private boolean aliveAndReadyLoggingEnabled = false;
private boolean testFailureRecoveryHandler = false;
@Inject
Events events;
@Inject
ServerEnvironment env;
@Inject @Named(ServerEnvironment.DEFAULT_INSTANCE_NAME)
Server server;
@Inject
ServiceLocator habitat;
@Inject
Clusters clusters;
@Inject
Nodes nodes;
@Inject
Servers servers;
private HealthHistory hHistory;
@LoggerInfo(subsystem = "CLSTR", description="Group Management Service Adapter Logger", publish=true)
private static final String GMS_LOGGER_NAME = "jakarta.enterprise.cluster.gms";
@LogMessagesResourceBundle
private static final String LOG_MESSAGES_RB = "org.glassfish.cluster.gms.LogMessages";
static final Logger GMS_LOGGER = Logger.getLogger(GMS_LOGGER_NAME, LOG_MESSAGES_RB);
//gmsservice.no.cluster.name=GMSAD1001: no clustername to lookup
//GMSAD1001.diag.cause.1=Required information was not passed into method.
//GMSAD1001.diag.check.1=File issue with all relevant information.
@LogMessageInfo(message = "no clustername to lookup",
level="SEVERE",
cause="Required information was not passed into method.",
action="File issue with all relevant information.")
private static final String GMS_NO_CLUSTER_NAME="NCLS-CLSTR-10101";
//gmsservice.multiple.adapter=GMSAD1002: Multiple gms-adapter service for cluster {0}
//GMSAD1002.diag.cause.1=GMs module is being initialized more than once for the same cluster.
//GMSAD1002.diag.check.1=File issue with all relevant information.
@LogMessageInfo(message = "Multiple gms-adapter service for cluster {0}",
level="SEVERE",
cause="GMs module is being initialized more than once for the same cluster.",
action="File issue with all relevant information.")
private static final String GMS_MULTIPLE_ADAPTER="NCLS-CLSTR-10102";
//gmsservice.nocluster.warning=GMSAD1003: GMS cannot initialize with unknown cluster
//GMSAD1003.diag.cause.1=No cluster was found with this name in the domain configuration.
//GMSAD1003.diag.check.1=Check that domain exists in domain.xml.
@LogMessageInfo(message = "GMS cannot initialize with unknown cluster",
level="WARNING",
cause="No cluster was found with this name in the domain configuration.",
action="Check that domain exists in domain.xml.")
private static final String GMS_NO_CLUSTER_WARNING="NCLS-CLSTR-10103";
//gmsservice.started=GMSAD1004: Started GMS for instance {0} in group {1}
@LogMessageInfo(message = "Started GMS for instance {0} in group {1}", level="INFO")
private static final String GMS_STARTED="NCLS-CLSTR-10104";
//gmsservice.member.joined.group=GMSAD1005: Member {0} joined group {1}
@LogMessageInfo(message = "Member {0} joined group {1}", level="INFO")
private static final String GMS_JOINED="NCLS-CLSTR-10105";
//gmsservice.alive.ready.signal=GMSAD1007: AliveAndReady for signal: {0} for member: {1} of group: {2} current:[{3}] previous:[{4}]
@LogMessageInfo(message = "AliveAndReady for signal: {0} for member: {1} of group: {2} current:[{3}] previous:[{4}]", level="INFO")
private static final String GMS_ALIVE_AND_READY="NCLS-CLSTR-10107";
//gmsservice.server_shutdown.received=GMSAD1008: GMSAdapter for member: {0} group: {1} received GlassfishEventType: {2}
@LogMessageInfo(message = "GMSAdapter for member: {0} group: {1} received GlassfishEventType: {2}", level="INFO")
private static final String GMS_SERVER_SHUTDOWN_RECEIVED="NCLS-CLSTR-10108";
//gmsexception.new.health.history=GMSAD1009: An exception occurred while creating the HealthHistory object: {0}
//GMSAD1009.diag.cause.1=An unexpected exception occurred.
//GMSAD1009.diag.check.1=See server log for more details.
@LogMessageInfo(message = "An exception occurred while creating the HealthHistory object: {0}",
level="WARNING",
cause="An unexpected exception occurred.",
action="See server log for more details.")
private static final String GMS_EXCEPTION_NEW_HEALTH_HISTORY="NCLS-CLSTR-10109";
//gmsexception.processing.config.props=GMSAD1010: An exception occurred while processing GMS configuration properties: {0}
//GMSAD1010.diag.cause.1=An unexpected exception occurred.
//GMSAD1010.diag.check.1=See server log for more details.
@LogMessageInfo(message = "An exception occurred while processing GMS configuration properties: {0}",
level="WARNING",
cause="An unexpected exception occurred.",
action="See server log for more details.")
private static final String GMS_EXCEPTION_PROCESSING_CONFIG="NCLS-CLSTR-10110";
//gmsexception.ignoring.property=GMSAD1011: Ignoring group-management-service property {0} with value of {1} due to {2}
//# todo: can we remove this try/catch?
//GMSAD1011.diag.cause.1=An illegal argument was passed into the Shoal GMS implementation.
//GMSAD1011.diag.check.1=Check the server log file for more information from Shoal-GMS.
@LogMessageInfo(message = "Ignoring group-management-service property {0} with value of {1} due to {2}",
level="WARNING",
cause="An illegal argument was passed into the Shoal GMS implementation.",
action="Check the server log file for more information from Shoal-GMS.")
private static final String GMS_EXCEPTION_IGNORING_PROPERTY="NCLS-CLSTR-10111";
//gmsexception.cluster.property.error=GMSAD1012: Error processing cluster property:{0} value:{1} due to exception {2}
//# todo: can we remove this try/catch?
//GMSAD1012.diag.cause.1=An unexpected exception occurred.
//GMSAD1012.diag.check.1=Check the server log file for more information from Shoal-GMS.
@LogMessageInfo(message = "Error processing cluster property:{0} value:{1} due to exception {2}",
level="WARNING",
cause="An unexpected exception occurred.",
action="Check the server log file for more information from Shoal-GMS.")
private static final String GMS_EXCEPTION_CLUSTER_PROPERTY_ERROR="NCLS-CLSTR-10112";
//gmsexception.cannot.get.group.module=GMSAD1013: Exception in getting GMS module for group {0}: {1}
//GMSAD1013.diag.cause.1=There was a problem withing the GMS implementation.
//GMSAD1013.diag.check.1=Check the server log file for more information from Shoal-GMS.
@LogMessageInfo(message = "Exception in getting GMS module for group {0}: {1}",
level="SEVERE",
cause="An unexpected exception occurred.",
action="Check the server log file for more information from Shoal-GMS.")
private static final String GMS_EXCEPTION_CANNOT_GET_GROUP_MODULE="NCLS-CLSTR-10113";
//gmsexception.update.health.history=GMSAD1014: An exception occurred while updating the instance health history table: {0}
//GMSAD1014.diag.cause.1=An unexpected exception occurred.
//GMSAD1014.diag.check.1=Check the log for Shoal-GMS exceptions.
@LogMessageInfo(message = "An exception occurred while updating the instance health history table: {0}",
level="WARNING",
cause="An unexpected exception occurred.",
action="Check the log file for more information from Shoal-GMS.")
private static final String GMS_EXCEPTION_UPDATE_HEALTH_HISTORY="NCLS-CLSTR-10114";
//gmsservice.failurerecovery.start.notification=GMSAD1015: start failure recovery callback for component: {0} failed member: {1}
@LogMessageInfo(message = "start failure recovery callback for component: {0} failed member: {1}", level="INFO")
private static final String GMS_FAILURERECOVERY_START="NCLS-CLSTR-10115";
//gmsservice.failurerecovery.completed.notification=GMSAD016: complete failure recovery callback for component: {0} failed member: {1}
@LogMessageInfo(message = "complete failure recovery callback for component: {0} failed member: {1}", level="INFO")
private static final String GMS_FAILURE_RECOVERY_COMPLETED="NCLS-CLSTR-10116";
//gmsservice.failed.to.start=GMSAD1017: GMS failed to start. See stack trace for additional information.
@LogMessageInfo(message = "GMS failed to start. See stack trace for additional information.",
level="SEVERE",
cause="An unexpected exception occurred.",
action="Check the log file for more information")
private static final String GMS_FAILED_TO_START="NCLS-CLSTR-10117";
//gmsservice.failed.to.start.unexpected=GMSAD1018: GMS failed to start due to a runtime exception. See stack trace for additional information.
@LogMessageInfo(message = "GMS failed to start due to a runtime exception. See stack trace for additional information.",
level="SEVERE",
cause="An unexpected exception occurred.",
action="Check the log file for more information"
)
private static final String GMS_FAILED_TO_START_UNEXCEPTED="NCLS-CLSTR-10118";
//gmsservice.bind.int.address.invalid=GMSAD1019: GMS bind interface address {0} is invalid. Will use default value instead.
//GMSAD1019.diag.cause.1=The specified bind interface address is not an active local address, so it cannot be used on this node.
//GMSAD1019.diag.check.1=Check that you have specified the proper address. See server log for more details from GMS subsystem.
@LogMessageInfo(message = "GMS bind interface address {0} is invalid. Will use default value instead.",
level="SEVERE",
cause="The specified bind interface address is not an active local address, so it cannot be used on this node.",
action="Check that you have specified the proper address. See server log for more details from GMS subsystem.")
private static final String GMS_BIND_INT_ADDRESS_INVALID="NCLS-CLSTR-10119";
//gmsservice.listener.port.required=GMSAD1020: GMS listener port is required for cluster {0}. Will attempt to use default of {1}.
@LogMessageInfo(message = "GMS listener port is required for cluster {0}. Will attempt to use default of {1}.", level="WARNING")
private static final String GMS_LISTENER_PORT_REQUIRED="NCLS-CLSTR-10120";
@Override
public void postConstruct() {
}
AtomicBoolean initialized = new AtomicBoolean(false);
AtomicBoolean initializationComplete = new AtomicBoolean(false);
@Override
public String getClusterName() {
return clusterName;
}
@Override
public boolean initialize(String clusterName) {
if (initialized.compareAndSet(false, true)) {
this.clusterName = clusterName;
if (clusterName == null) {
GMS_LOGGER.log(LogLevel.SEVERE, GMS_NO_CLUSTER_NAME);
return false;
}
try {
gms = GMSFactory.getGMSModule(clusterName);
} catch (GMSException ge) {
// ignore
}
if (gms != null) {
GMS_LOGGER.log(LogLevel.SEVERE, GMS_MULTIPLE_ADAPTER,
clusterName);
return false;
}
Domain domain = habitat.getService(Domain.class);
instanceName = env.getInstanceName();
isDas = env.isDas();
cluster = server.getCluster();
if (cluster == null && clusters != null) {
// must be the DAS since it not direclty considered a member of cluster by domain.xml.
// iterate over all clusters to find the cluster that has name passed in.
for (Cluster clusterI : clusters.getCluster()) {
if (clusterName.compareTo(clusterI.getName()) == 0) {
cluster = clusterI;
break;
}
}
}
if (cluster == null) {
GMS_LOGGER.log(LogLevel.WARNING, GMS_NO_CLUSTER_WARNING);
return false; //don't enable GMS
} else if (isDas) {
// only want to do this in the case of the DAS
initializeHealthHistory(cluster);
}
clusterConfig = domain.getConfigNamed(clusterName + "-config");
if (GMS_LOGGER.isLoggable(LogLevel.CONFIG)) {
GMS_LOGGER.log(LogLevel.CONFIG,
"clusterName=" + clusterName +
" clusterConfig=" + clusterConfig);
}
try {
initializeGMS();
} catch (GMSException e) {
GMS_LOGGER.log(LogLevel.SEVERE, GMS_FAILED_TO_START, e);
// prevent access to a malformed gms object.
return false;
// also ensure for any unchecked exceptions (such as NPE during initialization) during initialization
// that the malformed gms object is not allowed to be accesssed through the gms adapter.
} catch (Throwable t) {
GMS_LOGGER.log(LogLevel.SEVERE, GMS_FAILED_TO_START_UNEXCEPTED, t);
// prevent access to a malformed gms object.
return false;
}
initializationComplete.set(true);
}
return initialized.get();
}
@Override
public void complete() {
initialized.compareAndSet(true, false);
initializationComplete.compareAndSet(true, false);
gms = null;
GMSFactory.removeGMSModule(clusterName);
}
@Override
public HealthHistory getHealthHistory() {
checkInitialized();
return hHistory;
}
private void initializeHealthHistory(Cluster cluster) {
try {
/*
* Should not fail, but we need to make sure it doesn't
* affect GMS just in case.
*/
hHistory = new HealthHistory(cluster);
Dom.unwrap(cluster).addListener(hHistory);
} catch (Throwable t) {
GMS_LOGGER.log(LogLevel.WARNING, GMS_EXCEPTION_NEW_HEALTH_HISTORY,
t.getLocalizedMessage());
}
}
private void readGMSConfigProps(Properties configProps) {
configProps.put(MEMBERTYPE_STRING, isDas ? SPECTATOR : CORE);
for (ServiceProviderConfigurationKeys key : ServiceProviderConfigurationKeys.values()) {
String keyName = key.toString();
try {
switch (key) {
case MULTICASTADDRESS:
if (cluster != null) {
String value = cluster.getGmsMulticastAddress();
if (value != null) {
configProps.put(keyName, value);
}
}
break;
case MULTICASTPORT:
if (cluster != null) {
String value = cluster.getGmsMulticastPort();
if (value != null) {
configProps.put(keyName, value);
}
}
break;
case FAILURE_DETECTION_TIMEOUT:
if (clusterConfig != null) {
String value = clusterConfig.getGroupManagementService().getFailureDetection().getHeartbeatFrequencyInMillis();
if (value != null) {
configProps.put(keyName, value);
}
}
break;
case FAILURE_DETECTION_RETRIES:
if (clusterConfig != null) {
String value = clusterConfig.getGroupManagementService().getFailureDetection().getMaxMissedHeartbeats();
if (value != null) {
configProps.put(keyName, value);
}
}
break;
case FAILURE_VERIFICATION_TIMEOUT:
if (clusterConfig != null) {
String value = clusterConfig.getGroupManagementService().getFailureDetection().getVerifyFailureWaittimeInMillis();
if (value != null) {
configProps.put(keyName, value);
}
}
break;
case DISCOVERY_TIMEOUT:
if (clusterConfig != null) {
String value = clusterConfig.getGroupManagementService().getGroupDiscoveryTimeoutInMillis();
if (value != null) {
configProps.put(keyName, value);
}
}
break;
case IS_BOOTSTRAPPING_NODE:
configProps.put(keyName, isDas ? Boolean.TRUE.toString() : Boolean.FALSE.toString());
break;
case BIND_INTERFACE_ADDRESS:
if (cluster != null) {
String value = cluster.getGmsBindInterfaceAddress();
if (value != null) {
value = value.trim();
}
if (value != null && value.length() > 1 && value.charAt(0) != '$') {
// todo: remove check for value length greater than 1.
// this value could be anything from IPv4 address, IPv6 address, hostname, network interface name.
// Only supported IPv4 address in gf v2.
if (NetworkUtility.isBindAddressValid(value)) {
configProps.put(keyName, value);
} else {
GMS_LOGGER.log(LogLevel.SEVERE,
GMS_BIND_INT_ADDRESS_INVALID,
value);
}
}
}
break;
case FAILURE_DETECTION_TCP_RETRANSMIT_TIMEOUT:
if (clusterConfig != null) {
String value = clusterConfig.getGroupManagementService().getFailureDetection().getVerifyFailureConnectTimeoutInMillis();
if (value != null) {
configProps.put(keyName, value);
}
}
break;
case MULTICAST_POOLSIZE:
case INCOMING_MESSAGE_QUEUE_SIZE :
// case MAX_MESSAGE_LENGTH: todo uncomment with shoal-gms.jar with this defined is promoted.
case FAILURE_DETECTION_TCP_RETRANSMIT_PORT:
if (clusterConfig != null) {
Property prop = clusterConfig.getGroupManagementService().getProperty(keyName);
if (prop == null) {
if (GMS_LOGGER.isLoggable(LogLevel.FINE)) {
GMS_LOGGER.log(LogLevel.FINE, String.format(
"No config property found for %s",
keyName));
}
break;
}
String value = prop.getValue().trim();
if (value != null) {
configProps.put(keyName, value);
}
/*
int positiveint = 0;
try {
positiveint = Integer.getInteger(value);
} catch (Throwable t) {}
// todo
if (positiveint > 0) {
configProps.put(keyName, positiveint);
} // todo else log event that invalid value was provided.
*/
}
break;
// These Shoal GMS configuration parameters are not supported to be set.
// Must place here or they will get flagged as not handled.
case LOOPBACK:
case VIRTUAL_MULTICAST_URI_LIST:
break;
// end unsupported Shoal GMS configuration parameters.
default:
if (GMS_LOGGER.isLoggable(LogLevel.FINE)) {
GMS_LOGGER.log(LogLevel.FINE, String.format(
"service provider key %s ignored", keyName));
}
break;
} /* end switch over ServiceProviderConfigurationKeys enum */
} catch (Throwable t) {
GMS_LOGGER.log(LogLevel.WARNING, GMS_EXCEPTION_PROCESSING_CONFIG, t.getLocalizedMessage());
}
} /* end for loop over ServiceProviderConfigurationKeys */
// check for Grizzly transport specific properties in GroupManagementService property list and then cluster property list.
// cluster property is more specific than group-mangement-service, so allow cluster property to override group-management-service proeprty
// if a GrizzlyConfigConstant property is in both list.
List<Property> props = null;
if (clusterConfig != null) {
props = clusterConfig.getGroupManagementService().getProperty();
for (Property prop : props) {
String name = prop.getName().trim();
String value = prop.getValue().trim();
if (name == null || value == null) {
continue;
}
if (GMS_LOGGER.isLoggable(LogLevel.CONFIG)) {
GMS_LOGGER.log(LogLevel.CONFIG,
"processing group-management-service property name=" +
name + " value= " + value);
}
if (value.startsWith("${")) {
if (GMS_LOGGER.isLoggable(LogLevel.CONFIG)) {
GMS_LOGGER.log(LogLevel.CONFIG,
"skipping group-management-service property name=" +
name +
" since value is unresolved symbolic token=" +
value);
}
} else {
if (GMS_LOGGER.isLoggable(LogLevel.CONFIG)) {
GMS_LOGGER.log(LogLevel.CONFIG,
"processing group-management-service property name=" +
name + " value= " + value);
}
if (name.startsWith(GMS_PROPERTY_PREFIX)) {
name = name.replaceFirst(GMS_PROPERTY_PREFIX_REGEXP, "");
}
configProps.put(name, value);
if (! validateGMSProperty(name)) {
GMS_LOGGER.log(LogLevel.WARNING, GMS_EXCEPTION_IGNORING_PROPERTY,
new Object [] {name, value, ""} );
}
}
}
}
if (cluster != null) {
props = cluster.getProperty();
for (Property prop : props) {
String name = prop.getName().trim();
String value = prop.getValue().trim();
if (name == null || value == null) {
continue;
}
if (GMS_LOGGER.isLoggable(LogLevel.CONFIG)) {
GMS_LOGGER.log(LogLevel.CONFIG,
"processing cluster property name=" + name +
" value= " + value);
}
if (value.startsWith("${")) {
if (GMS_LOGGER.isLoggable(LogLevel.CONFIG)) {
GMS_LOGGER.log(LogLevel.CONFIG,
"skipping cluster property name=" + name +
" since value is unresolved symbolic token=" +
value);
}
} else {
if (name.startsWith(GMS_PROPERTY_PREFIX)) {
name = name.replaceFirst(GMS_PROPERTY_PREFIX_REGEXP, "");
}
// undocumented property for testing purposes.
// impossible to register handlers in a regular app before gms starts up.
if (name.compareTo("ALIVEANDREADY_LOGGING") == 0){
aliveAndReadyLoggingEnabled = Boolean.parseBoolean(value);
} else if (name.compareTo("LISTENER_PORT") == 0 ) {
// special case mapping. Glassfish Cluster property GMS_LISTENER_PORT maps to Grizzly Config Constants TCPSTARTPORT and TCPENDPORT.
configProps.put(GrizzlyConfigConstants.TCPSTARTPORT.toString(), value);
configProps.put(GrizzlyConfigConstants.TCPENDPORT.toString(), value);
} else if (name.compareTo("TEST_FAILURE_RECOVERY") == 0) {
testFailureRecoveryHandler = Boolean.parseBoolean(value);
} else if (ServiceProviderConfigurationKeys
.DISCOVERY_URI_LIST.name().equals(name) &&
"generate".equals(value)) {
value = generateDiscoveryUriList();
configProps.put(name, value);
} else {
// handle normal case. one to one mapping.
configProps.put(name, value);
GMS_LOGGER.log(LogLevel.CONFIG,
"processing cluster property name=" + name +
" value= " + value);
if (! validateGMSProperty(name)) {
GMS_LOGGER.log(LogLevel.WARNING, GMS_EXCEPTION_CLUSTER_PROPERTY_ERROR,
new Object [] {name, value, ""} );
}
}
}
}
}
}
/*
* Get existing nodes based on cluster element in domain.
* Then check for DAS address in das.properties. When the
* list is set to 'generate' then the gms listener port
* must also be specified. So the same port is used for
* each cluster member.
*/
private String generateDiscoveryUriList() {
String clusterPort = null;
Property gmsPortProp = cluster.getProperty("GMS_LISTENER_PORT");
if (gmsPortProp == null ||
gmsPortProp.getValue() == null ||
gmsPortProp.getValue().trim().charAt(0) == '$') {
clusterPort = "9090";
GMS_LOGGER.log(LogLevel.WARNING, GMS_LISTENER_PORT_REQUIRED,
new Object [] {cluster.getName(), clusterPort});
} else {
clusterPort = gmsPortProp.getValue();
if (GMS_LOGGER.isLoggable(LogLevel.FINE)) {
GMS_LOGGER.log(LogLevel.FINE, "will use gms listener port: " +
clusterPort);
}
}
// get cluster member server refs
Set<String> instanceNames = new HashSet<String>();
if (GMS_LOGGER.isLoggable(LogLevel.FINE)) {
GMS_LOGGER.log(LogLevel.FINE, String.format(
"checking cluster.getServerRef() for '%s'",
cluster.getName()));
}
for (ServerRef sRef : cluster.getServerRef()) {
/*
* When an instance (not DAS) starts up, it will add
* its own address to the discovery list. This is ok
* now. If we want to skip it, here's the place to
* check.
*/
if (GMS_LOGGER.isLoggable(LogLevel.FINE)) {
GMS_LOGGER.log(LogLevel.FINE, String.format(
"adding server ref %s to set of instance names",
sRef.getRef()));
}
instanceNames.add(sRef.getRef());
}
StringBuilder sb = new StringBuilder();
final String SEP = ",";
final String scheme = "tcp://";
// use server refs to find matching nodes
for (String name : instanceNames) {
Server server = servers.getServer(name);
if (server != null) {
if (GMS_LOGGER.isLoggable(LogLevel.FINE)) {
GMS_LOGGER.log(LogLevel.FINE, String.format(
"found server for name %s",
name));
}
Node node = nodes.getNode(server.getNodeRef());
if (node != null) {
String host = scheme + node.getNodeHost() + ":" +
clusterPort;
if (GMS_LOGGER.isLoggable(LogLevel.FINE)) {
GMS_LOGGER.log(LogLevel.FINE, String.format(
"Adding host '%s' to discovery list", host));
}
sb.append(host).append(SEP);
}
}
}
// add das location from das.properties if needed
if (server.isInstance()) {
try {
ServerDirs sDirs = new ServerDirs(env.getInstanceRoot());
File dasPropsFile = sDirs.getDasPropertiesFile();
if (GMS_LOGGER.isLoggable(LogLevel.FINE)) {
GMS_LOGGER.log(LogLevel.FINE, String.format(
"found das.props file at %s",
dasPropsFile.getAbsolutePath()));
}
Properties dasProps = getProperties(dasPropsFile);
String host = scheme +
dasProps.getProperty("agent.das.host") +
":" +
clusterPort;
if (GMS_LOGGER.isLoggable(LogLevel.FINE)) {
GMS_LOGGER.log(LogLevel.FINE, String.format(
"adding '%s' from das.props file", host));
}
sb.append(host).append(SEP);
} catch (IOException ioe) {
GMS_LOGGER.log(LogLevel.WARNING, ioe.toString());
}
}
// trim list if needed and return
int lastCommaIndex = sb.lastIndexOf(SEP);
if (lastCommaIndex != -1) {
sb.deleteCharAt(lastCommaIndex);
}
if (GMS_LOGGER.isLoggable(LogLevel.FINE)) {
GMS_LOGGER.log(LogLevel.FINE, String.format(
"returning discovery list '%s'",
sb.toString()));
}
return sb.toString();
}
final protected Properties getProperties(File propFile) throws IOException {
Properties props = new Properties();
FileInputStream fis = null;
try {
fis = new FileInputStream(propFile);
props.load(fis);
fis.close();
fis = null;
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException ignored) {}
}
}
return props;
}
private boolean validateGMSProperty(String propertyName) {
boolean result = false;
Object key = null;
try {
key = GrizzlyConfigConstants.valueOf(propertyName);
result = true;
} catch (Throwable ignored) {}
if (key == null) {
try {
key = ServiceProviderConfigurationKeys.valueOf(propertyName);
result = true;
} catch (Throwable ignored) {}
}
return key != null && result;
}
private void initializeGMS() throws GMSException{
Properties configProps = new Properties();
int HA_MAX_GMS_MESSAGE_LENGTH = 4 * (1024 * 1024) + (2 * 1024); // Default to 4 MB limit in glassfish.
configProps.put(ServiceProviderConfigurationKeys.MAX_MESSAGE_LENGTH.toString(), Integer.toString(HA_MAX_GMS_MESSAGE_LENGTH));
// read GMS configuration from domain.xml
readGMSConfigProps(configProps);
printProps(configProps);
String memberType = (String) configProps.get(MEMBERTYPE_STRING);
gms = (GroupManagementService) GMSFactory.startGMSModule(instanceName, clusterName,
GroupManagementService.MemberType.valueOf(memberType), configProps);
//remove GMSLogDomain.getLogger(GMSLogDomain.GMS_LOGGER).setLevel(gmsLogLevel);
GMSFactory.setGMSEnabledState(clusterName, Boolean.TRUE);
if (gms != null) {
try {
registerJoinedAndReadyNotificationListener(this);
registerJoinNotificationListener(this);
registerFailureNotificationListener(this);
registerPlannedShutdownListener(this);
registerFailureSuspectedListener(this);
//fix gf it 12905
if (testFailureRecoveryHandler && ! env.isDas()) {
// this must be here or appointed recovery server notification is not printed out for automated testing.
registerFailureRecoveryListener("GlassfishFailureRecoveryHandlerTest", this);
}
glassfishEventListener = new org.glassfish.api.event.EventListener() {
public void event(Event event) {
if (gms == null) {
// handle cases where gms is not set and for some reason this handler did not get unregistered.
return;
}
if (event.is(EventTypes.SERVER_SHUTDOWN)) {
GMS_LOGGER.log(LogLevel.INFO, GMS_SERVER_SHUTDOWN_RECEIVED,
new Object[]{gms.getInstanceName(), gms.getGroupName(), event.name()});
// todo: remove these when removing the test register ones above.
removeJoinedAndReadyNotificationListener(GMSAdapterImpl.this);
removeJoinNotificationListener(GMSAdapterImpl.this);
removeFailureNotificationListener(GMSAdapterImpl.this);
removeFailureSuspectedListener(GMSAdapterImpl.this);
gms.shutdown(GMSConstants.shutdownType.INSTANCE_SHUTDOWN);
removePlannedShutdownListener(GMSAdapterImpl.this);
events.unregister(glassfishEventListener);
} else if (event.is(EventTypes.SERVER_READY)) {
// consider putting following, includding call to joinedAndReady into a timertask.
// this time would give instance time to get its heartbeat cache updated by all running
// READY cluster memebrs
// final long MAX_WAIT_DURATION = 4000;
//
// long elapsedDuration = (joinTime == 0L) ? 0 : System.currentTimeMillis() - joinTime;
// long waittime = MAX_WAIT_DURATION - elapsedDuration;
// if (waittime > 0L && waittime <= MAX_WAIT_DURATION) {
// try {
// GMS_LOGGER.info("wait " + waittime + " ms before signaling joined and ready");
// Thread.sleep(waittime);
// } catch(Throwable t) {}
// }
// validateCoreMembers();
gms.reportJoinedAndReadyState();
}
}
};
events.register(glassfishEventListener);
gms.join();
//joinTime = System.currentTimeMillis();
GMS_LOGGER.log(LogLevel.INFO, GMS_JOINED,
new Object [] {instanceName, clusterName});
} catch (GMSException e) {
// failed to start so unregister event listener that calls GMS.
events.unregister(glassfishEventListener);
throw e;
}
GMS_LOGGER.log(LogLevel.INFO, GMS_STARTED,
new Object[] {instanceName, clusterName});
} else throw new GMSException("gms object is null.");
}
private void printProps(Properties prop) {
if (!GMS_LOGGER.isLoggable(LogLevel.CONFIG)) {
return;
}
StringBuilder sb = new StringBuilder();
for (String key : prop.stringPropertyNames()) {
sb.append(key).append(" = ").append(prop.get(key)).append(" ");
}
GMS_LOGGER.log(LogLevel.CONFIG,
"Printing all GMS properties: ", sb.toString());
}
private void checkInitialized() {
if( ! initialized.get() || ! initializationComplete.get()) {
throw new IllegalStateException("GMSAdapter not properly initialized.");
}
}
@Override
public GroupManagementService getModule() {
checkInitialized();
return gms;
}
public GroupManagementService getGMS(String groupName) {
//return the gms instance for that group
try {
return GMSFactory.getGMSModule(groupName);
} catch (GMSException e) {
GMS_LOGGER.log(LogLevel.SEVERE, GMS_EXCEPTION_CANNOT_GET_GROUP_MODULE,
new Object [] {groupName , e.getLocalizedMessage()});
return null;
}
}
@Override
public void processNotification(Signal signal) {
if (GMS_LOGGER.isLoggable(LogLevel.FINE)) {
GMS_LOGGER.log(LogLevel.FINE, "GMSService: Received a notification ",
signal.getClass().getName());
}
try {
/*
* Should not fail, but we need to make sure it doesn't
* affect GMS just in case. In the non-DAS case, hHistory
* will always be null so we skip it. In the DAS case,
* it shouldn't be null unless we've already seen an
* error logged during construction.
*/
if (hHistory != null) {
hHistory.updateHealth(signal);
}
} catch (Throwable t) {
GMS_LOGGER.log(LogLevel.WARNING, GMS_EXCEPTION_UPDATE_HEALTH_HISTORY,
t.getLocalizedMessage());
}
// testing only. one must set cluster property GMS_TEST_FAILURE_RECOVERY to true for the following to execute. */
if (testFailureRecoveryHandler && signal instanceof FailureRecoverySignal) {
FailureRecoverySignal frsSignal = (FailureRecoverySignal)signal;
GMS_LOGGER.log(LogLevel.INFO, GMS_FAILURERECOVERY_START, new Object[]{frsSignal.getComponentName(), frsSignal.getMemberToken()});
try {
Thread.sleep(20 * 1000); // sleep 20 seconds. simulate wait time to allow instance to restart and do self recovery before another instance does it.
} catch (InterruptedException ignored) {
}
GMS_LOGGER.log(LogLevel.INFO, GMS_FAILURE_RECOVERY_COMPLETED, new Object[]{frsSignal.getComponentName(), frsSignal.getMemberToken()});
}
if (this.aliveAndReadyLoggingEnabled) {
if (signal instanceof JoinedAndReadyNotificationSignal ||
signal instanceof FailureNotificationSignal ||
signal instanceof PlannedShutdownSignal) {
AliveAndReadySignal arSignal = (AliveAndReadySignal)signal;
String signalSubevent = "";
if (signal instanceof JoinedAndReadyNotificationSignal) {
JoinedAndReadyNotificationSignal jrsig = (JoinedAndReadyNotificationSignal)signal;
if (jrsig.getEventSubType() == GMSConstants.startupType.GROUP_STARTUP) {
signalSubevent = " Subevent: " + GMSConstants.startupType.GROUP_STARTUP;
} else if (jrsig.getRejoinSubevent() != null) {
signalSubevent = " Subevent: " + jrsig.getRejoinSubevent();
}
}
if (signal instanceof PlannedShutdownSignal) {
PlannedShutdownSignal pssig = (PlannedShutdownSignal)signal;
if (pssig.getEventSubType() == GMSConstants.shutdownType.GROUP_SHUTDOWN) {
signalSubevent = " Subevent:" + GMSConstants.shutdownType.GROUP_SHUTDOWN.toString();
}
}
AliveAndReadyView current = arSignal.getCurrentView();
AliveAndReadyView previous = arSignal.getPreviousView();
GMS_LOGGER.log(LogLevel.INFO, GMS_ALIVE_AND_READY,
new Object [] {
signal.getClass().getSimpleName() + signalSubevent,
signal.getMemberToken(),
signal.getGroupName(),
current,
previous
});
}
}
}
// each of the getModule(s) methods are temporary. see class-level comment.
/**
* Registers a JoinNotification Listener.
*
* @param callback processes GMS notification JoinNotificationSignal
*/
@Override
public void registerJoinNotificationListener(CallBack callback) {
if (gms != null && callback != null) {
JoinNotificationActionFactory jnaf = new JoinNotificationActionFactoryImpl(callback);
gms.addActionFactory(jnaf);
callbackJoinActionFactoryMapping.put(callback, jnaf);
}
}
/**
* Registers a JoinAndReadyNotification Listener.
*
* @param callback processes GMS notification JoinAndReadyNotificationSignal
*/
@Override
public void registerJoinedAndReadyNotificationListener(CallBack callback) {
if (gms != null && callback != null) {
JoinedAndReadyNotificationActionFactory jnaf = new JoinedAndReadyNotificationActionFactoryImpl(callback);
gms.addActionFactory(jnaf);
callbackJoinedAndReadyActionFactoryMapping.put(callback, jnaf);
}
}
/**
* Register a listener for all events that represent a member has left the group.
*
* @param callback Signal can be either PlannedShutdownSignal, FailureNotificationSignal or JoinNotificationSignal(subevent Rejoin).
*/
@Override
public void registerMemberLeavingListener(CallBack callback) {
if (gms != null && callback != null) {
registerFailureNotificationListener(callback);
registerPlannedShutdownListener(callback);
registerJoinNotificationListener(callback);
}
}
/**
* Registers a PlannedShutdown Listener.
*
* @param callback processes GMS notification PlannedShutdownSignal
*/
@Override
public void registerPlannedShutdownListener(CallBack callback) {
if (gms != null && callback != null) {
PlannedShutdownActionFactory psaf = new PlannedShutdownActionFactoryImpl(callback);
callbackPlannedShutdownActionFactoryMapping.put(callback, psaf);
gms.addActionFactory(psaf);
}
}
/**
* Registers a FailureSuspected Listener.
*
* @param callback processes GMS notification FailureSuspectedSignal
*/
@Override
public void registerFailureSuspectedListener(CallBack callback) {
if (gms != null) {
FailureSuspectedActionFactory fsaf = new FailureSuspectedActionFactoryImpl(callback);
callbackFailureSuspectedActionFactoryMapping.put(callback, fsaf);
gms.addActionFactory(fsaf);
}
}
/**
* Registers a FailureNotification Listener.
*
* @param callback processes GMS notification FailureNotificationSignal
*/
@Override
public void registerFailureNotificationListener(CallBack callback) {
if (gms != null) {
FailureNotificationActionFactory fnaf = new FailureNotificationActionFactoryImpl(callback);
callbackFailureActionFactoryMapping.put(callback, fnaf);
gms.addActionFactory(fnaf);
}
}
/**
* Registers a FailureRecovery Listener.
*
* @param callback processes GMS notification FailureRecoverySignal
* @param componentName The name of the parent application's component that should be notified of selected for
* performing recovery operations. One or more components in the parent application may
* want to be notified of such selection for their respective recovery operations.
*/
@Override
public void registerFailureRecoveryListener(String componentName, CallBack callback) {
if (gms != null) {
gms.addActionFactory(componentName, new FailureRecoveryActionFactoryImpl(callback));
}
}
/**
* Registers a Message Listener.
*
* @param componentName Name of the component that would like to consume
* Messages. One or more components in the parent application would want to
* be notified when messages arrive addressed to them. This registration
* allows GMS to deliver messages to specific components.
* @param messageListener processes GMS MessageSignal
*/
@Override
public void registerMessageListener(String componentName, CallBack messageListener) {
if (gms != null) {
gms.addActionFactory(new MessageActionFactoryImpl(messageListener), componentName);
}
}
/**
* Registers a GroupLeadershipNotification Listener.
*
* @param callback processes GMS notification GroupLeadershipNotificationSignal. This event occurs when the GMS masters leaves the Group
* and another member of the group takes over leadership. The signal indicates the new leader.
*/
@Override
public void registerGroupLeadershipNotificationListener(CallBack callback) {
if (gms != null) {
gms.addActionFactory(new GroupLeadershipNotificationActionFactoryImpl(callback));
}
}
@Override
public void removeFailureRecoveryListener(String componentName) {
if (gms != null) {
gms.removeFailureRecoveryActionFactory(componentName);
}
}
@Override
public void removeMessageListener(String componentName){
if (gms != null) {
gms.removeMessageActionFactory(componentName);
}
}
@Override
public void removeFailureNotificationListener(CallBack callback){
if (gms != null) {
FailureNotificationActionFactory fnaf = callbackFailureActionFactoryMapping.remove(callback);
if (fnaf != null) {
gms.removeActionFactory(fnaf);
}
}
}
@Override
public void removeFailureSuspectedListener(CallBack callback){
if (gms != null) {
FailureSuspectedActionFactory fsaf = callbackFailureSuspectedActionFactoryMapping.remove(callback);
if (fsaf != null) {
gms.removeFailureSuspectedActionFactory(fsaf);
}
}
}
@Override
public void removeJoinNotificationListener(CallBack callback){
if (gms != null) {
JoinNotificationActionFactory jaf = callbackJoinActionFactoryMapping.get(callback);
if (jaf != null) {
gms.removeActionFactory(jaf);
}
}
}
@Override
public void removeJoinedAndReadyNotificationListener(CallBack callback){
if (gms != null) {
JoinedAndReadyNotificationActionFactory jaf = callbackJoinedAndReadyActionFactoryMapping.get(callback);
if (jaf != null) {
gms.removeActionFactory(jaf);
}
}
}
@Override
public void removePlannedShutdownListener(CallBack callback){
if (gms != null) {
PlannedShutdownActionFactory psaf = callbackPlannedShutdownActionFactoryMapping.remove(callback);
if (psaf != null) {
gms.removeActionFactory(psaf);
}
}
}
@Override
public void removeGroupLeadershipNotificationListener(CallBack callback){
}
@Override
public void removeMemberLeavingListener(CallBack callback){
removePlannedShutdownListener(callback);
removeFailureNotificationListener(callback);
removeJoinNotificationListener(callback);
}
}