All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.gms.bootstrap.HealthHistory Maven / Gradle / Ivy

There is a newer version: 8.0.0-JDK17-M9
Show newest version
/*
 * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * 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.bootstrap;

import com.sun.enterprise.config.serverbeans.Cluster;
import com.sun.enterprise.config.serverbeans.Server;
import com.sun.enterprise.config.serverbeans.ServerRef;
import com.sun.enterprise.ee.cms.core.FailureNotificationSignal;
import com.sun.enterprise.ee.cms.core.JoinNotificationSignal;
import com.sun.enterprise.ee.cms.core.JoinedAndReadyNotificationSignal;
import com.sun.enterprise.ee.cms.core.PlannedShutdownSignal;
import com.sun.enterprise.ee.cms.core.RejoinSubevent;
import com.sun.enterprise.ee.cms.core.Signal;
import com.sun.enterprise.util.i18n.StringManager;

import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Logger;

import org.glassfish.api.logging.LogLevel;
import org.glassfish.logging.annotation.LogMessageInfo;
import org.glassfish.logging.annotation.LogMessagesResourceBundle;
import org.glassfish.logging.annotation.LoggerInfo;
import org.jvnet.hk2.config.ConfigListener;
import org.jvnet.hk2.config.UnprocessedChangeEvents;

/**
 * Used to hold cluster history. This information is backed by
 * a ConcurrentMap, so iterating over the instances is
 * "weakly consistent" as the state could change at any time
 * (especially during cluster startup).
 */
public final class HealthHistory implements ConfigListener {

    //private final static Logger logger = LogDomains.getLogger(
    //    HealthHistory.class, LogDomains.CORE_LOGGER);

    private static final StringManager strings =
        StringManager.getManager(HealthHistory.class);

    @LoggerInfo(subsystem = "CLSTR", description="Group Management Service Logger", publish=true)
    private static final String GMSBS_LOGGER_NAME = "jakarta.enterprise.cluster.gms.bootstrap";


    @LogMessagesResourceBundle
    private static final String LOG_MESSAGES_RB = "org.glassfish.cluster.gms.bootstrap.LogMessages";

    static final Logger GMSBS_LOGGER = Logger.getLogger(GMSBS_LOGGER_NAME, LOG_MESSAGES_RB);

    @LogMessageInfo(message = "Adding instance {0} to health history table.", level="INFO")
    private static final String GMS_ADDING_INSTANCE="NCLS-CLSTR-20001";

    @LogMessageInfo(message = "Instance {0} was not in map when deleted from health history table.",
                    level="WARNING",
                    cause="More than one call may have been made to remove this instance" +
                          " from the cluster. This has no other effect on the health history information.",
                    action="No action is necessary.")
    private static final String GMS_INSTANCE_NOT_PRESENT="NCLS-CLSTR-20002";

    // deleting_instance=GMSBS2003: Deleting instance {0} from health history table.
    @LogMessageInfo(message = "Deleting instance {0} from health history table.", level="INFO")
    private static final String GMS_DELETE_INSTANCE="NCLS-CLSTR-20003";

    // duplicate_instance=GMSBS2004: Duplicate instance {0} ignored in health history.
    @LogMessageInfo(message = "Duplicate instance {0} ignored in health history.",
                    level="WARNING",
                    cause="There may be more than one instance in the cluster with the same name.",
                    action="Check that instance names are unique within the cluster.")
    private static final String GMS_DUPLICATE_INSTANCE="NCLS-CLSTR-20004";

    // key_already.present=GMSBS2005: State already known for instance {0}. Not adding to health history table.
    @LogMessageInfo(message = "State already known for instance {0}. Not adding to health history table.", level="INFO")
    private static final String GMS_INSTANCE_ALREADY_PRESENT="NCLS-CLSTR-20005";

    // unknown_instance=GMSBS2006: New state {0} added for unknown instance {1}
    @LogMessageInfo(message = "New state {0} added for unknown instance {1}", level="INFO")
    private static final String GMS_INSTANCE_UNKNOWN_STATE="NCLS-CLSTR-20006";

    // NOT_RUNNING means there is no time information associated
    public static enum STATE {
        NOT_RUNNING (strings.getString("state.not_running")),
        RUNNING     (strings.getString("state.running")),
        REJOINED    (strings.getString("state.rejoined")),
        FAILURE     (strings.getString("state.failure")),
        SHUTDOWN    (strings.getString("state.shutdown"));

        private final String stringVal;

        STATE(String stringVal) {
            this.stringVal = stringVal;
        }

        @Override
        public String toString() {
            return stringVal;
        }
    };

    /**
     * Used when no time information is known, for instance at
     * cluster startup before an instance has started.
     */
    public static final long NOTIME = -1l;

    private final ConcurrentMap healthMap;

    /*
     * Creates a health history that knows about the expected
     * list of instances. This is called from the GMS adapter
     * during initialization, before
     */
    public HealthHistory(Cluster cluster) {
        healthMap = new ConcurrentHashMap(
            cluster.getInstances().size());
        for (Server server : cluster.getInstances()) {
            if (server.isDas()) {
                continue;
            }
            if (GMSBS_LOGGER.isLoggable(LogLevel.FINE)) {
                GMSBS_LOGGER.log(LogLevel.FINE, String.format(
                    "instance name in HealthHistory constructor %s",
                    server.getName()));
            }
            if (healthMap.putIfAbsent(server.getName(),
                new InstanceHealth(STATE.NOT_RUNNING, NOTIME)) != null) {
                GMSBS_LOGGER.log(LogLevel.WARNING, GMS_DUPLICATE_INSTANCE, server.getName());
            }
        }
    }

    /**
     * Returns the state/time of a specific instance.
     */
    public InstanceHealth getHealthByInstance(String name) {
        return healthMap.get(name);
    }

    /**
     * The returned list may be modified without affecting
     * the information in the HealthHistory object.
     */
    public List getInstancesByState(STATE targetState) {
        List retVal = new ArrayList(healthMap.size());
        for (String name : healthMap.keySet()) {
            if (healthMap.get(name).state == targetState) {
                retVal.add(name);
            }
        }
        return retVal;
    }

    /**
     * Returns a copy of the instance names.
     */
    public Set getInstances() {
        return Collections.unmodifiableSet(healthMap.keySet());
    }

    /**
     * Called by GMS subsystem to update the health of an instance.
     *
     * TODO: add try/catch around everything for safety
     */
    public void updateHealth(Signal signal) {
        if (GMSBS_LOGGER.isLoggable(LogLevel.FINE)) {
            GMSBS_LOGGER.log(LogLevel.FINE, "signal: " + signal.toString());
        }
        String name = signal.getMemberToken();
        long time = signal.getStartTime();
        STATE state = null;

        // a little if-elsie....
        if (signal instanceof  JoinNotificationSignal) {
            /*
             * Means an instance is running. We will usually get a
             * JoinedAndReadyNotificationSignal after this in a usual
             * startup. If not, it means the DAS is restarting and
             * the cluster is already up. In that case, we need
             * the original startup time, not the time of the signal.
             */
            state = STATE.RUNNING;
        }   else if (signal instanceof JoinedAndReadyNotificationSignal) {
            /*
             * During a normal startup, this will occur after the
             * JoinNotificationSignal. If it's not a Rejoin, we
             * don't need to process the data since it's already
             * happened during the Join event. But it doesn't hurt.
             */
            JoinedAndReadyNotificationSignal jar =
                (JoinedAndReadyNotificationSignal) signal;
            RejoinSubevent sub = jar.getRejoinSubevent();
            if (sub == null) {
                if (GMSBS_LOGGER.isLoggable(LogLevel.FINE)) {
                    GMSBS_LOGGER.log(LogLevel.FINE, "it's a joined and ready");
                }
                state = STATE.RUNNING;
            } else {
                if (GMSBS_LOGGER.isLoggable(LogLevel.FINE)) {
                    GMSBS_LOGGER.log(LogLevel.FINE, "it's a rejoin");
                }
                state = STATE.REJOINED;
                time = sub.getGroupJoinTime();
            }
        } else if (signal instanceof FailureNotificationSignal) {
            state = STATE.FAILURE;
            time = System.currentTimeMillis();
        } else if (signal instanceof PlannedShutdownSignal) {
            state = STATE.SHUTDOWN;
            time = System.currentTimeMillis();
        } else {
            if (GMSBS_LOGGER.isLoggable(LogLevel.FINE)) {
                GMSBS_LOGGER.log(LogLevel.FINE, String.format(
                    "Signal %s not handled in updateHealth",
                    signal.toString()));
            }
            return;
        }
        InstanceHealth ih = new InstanceHealth(state, time);
        if (GMSBS_LOGGER.isLoggable(LogLevel.FINE)) {
            GMSBS_LOGGER.log(LogLevel.FINE, String.format(
                "updating health with %s : %s for signal %s",
                name, ih.toString(), signal.toString()));
        }
        if (healthMap.put(name, ih) == null) {
            GMSBS_LOGGER.log(LogLevel.INFO, GMS_INSTANCE_UNKNOWN_STATE,
                new Object [] {state, name});
        }
    }

    @Override
    public UnprocessedChangeEvents changed(PropertyChangeEvent[] events) {
        Object oldVal;
        Object newVal;
        for (PropertyChangeEvent event : events) {
            oldVal = event.getOldValue();
            newVal = event.getNewValue();
            if (oldVal instanceof ServerRef && newVal == null) {
                ServerRef instance = (ServerRef) oldVal;
                deleteInstance(instance.getRef());
            } else if (newVal instanceof ServerRef && oldVal == null) {
                ServerRef instance = (ServerRef) newVal;
                addInstance(instance.getRef());
            }
        }
        return null;
    }

    private void deleteInstance(String name) {
        GMSBS_LOGGER.log(LogLevel.INFO, GMS_DELETE_INSTANCE, name);
        InstanceHealth oldHealth = healthMap.remove(name);
        if (oldHealth == null) {
            GMSBS_LOGGER.log(LogLevel.WARNING, GMS_INSTANCE_NOT_PRESENT, name);
        }
    }

    /*
     * We only want to add the instance if it's not already
     * in the map. It could exist already if some trick of time
     * caused a GMS message to be received from the instance
     * before the config changes were processed. We could use
     * current time in the instance health object, but we should
     * be consistent with startup behavior.
     */
    private void addInstance(String name) {
        GMSBS_LOGGER.log(LogLevel.INFO, GMS_ADDING_INSTANCE, name);
        InstanceHealth oldHealth = healthMap.putIfAbsent(name,
            new InstanceHealth(STATE.NOT_RUNNING, NOTIME));
        if (oldHealth != null) {
            GMSBS_LOGGER.log(LogLevel.INFO, GMS_INSTANCE_ALREADY_PRESENT, name);
        }
    }

    /*
     * Information in an InstanceHealth object is immutable. For
     * convenience, the fields are public for direct access.
     */
    public static final class InstanceHealth {

        /**
         * The last-known state of the instance.
         */
        public final STATE state;

        /**
         * The time, if known, corresponding to the last change in state.
         */
        public final long time;

        InstanceHealth(STATE state, long time) {
            this.state = state;
            this.time = time;
        }

        @Override
        public String toString() {
            return String.format("InstanceHealth: state '%s' time '%s'",
                state, new Date(time).toString());
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy