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

org.graylog2.restclient.models.Node Maven / Gradle / Ivy

/**
 * This file is part of Graylog.
 *
 * Graylog is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Graylog 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Graylog.  If not, see .
 */
package org.graylog2.restclient.models;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.net.MediaType;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import org.graylog2.rest.models.system.buffers.responses.BufferClasses;
import org.graylog2.rest.models.system.inputs.requests.InputLaunchRequest;
import org.graylog2.restclient.lib.APIException;
import org.graylog2.restclient.lib.ApiClient;
import org.graylog2.restclient.lib.DateTools;
import org.graylog2.restclient.lib.ExclusiveInputException;
import org.graylog2.restclient.lib.metrics.Metric;
import org.graylog2.restclient.models.api.responses.BufferClassesResponse;
import org.graylog2.restclient.models.api.responses.BuffersResponse;
import org.graylog2.restclient.models.api.responses.JournalInfo;
import org.graylog2.restclient.models.api.responses.SystemOverviewResponse;
import org.graylog2.restclient.models.api.responses.cluster.NodeSummaryResponse;
import org.graylog2.restclient.models.api.responses.metrics.MetricsListResponse;
import org.graylog2.restclient.models.api.responses.system.ClusterEntityJVMStatsResponse;
import org.graylog2.restclient.models.api.responses.system.InputLaunchResponse;
import org.graylog2.restclient.models.api.responses.system.InputStateSummaryResponse;
import org.graylog2.restclient.models.api.responses.system.InputSummaryResponse;
import org.graylog2.restclient.models.api.responses.system.InputTypeSummaryResponse;
import org.graylog2.restclient.models.api.responses.system.InputTypesResponse;
import org.graylog2.restclient.models.api.responses.system.InputsResponse;
import org.graylog2.restclient.models.api.responses.system.NodeThroughputResponse;
import org.graylog2.restclient.models.api.responses.system.loggers.LoggerSubsystemSummary;
import org.graylog2.restclient.models.api.responses.system.loggers.LoggerSubsystemsResponse;
import org.graylog2.restclient.models.api.responses.system.loggers.LoggerSummary;
import org.graylog2.restclient.models.api.responses.system.loggers.LoggersResponse;
import org.graylog2.restroutes.generated.routes;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import play.mvc.Http;

import java.io.IOException;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import static com.google.common.base.MoreObjects.firstNonNull;

public class Node extends ClusterEntity {

    public interface Factory {
        Node fromSummaryResponse(NodeSummaryResponse r);

        Node fromTransportAddress(URI transportAddress);
    }

    private static final Logger LOG = LoggerFactory.getLogger(Node.class);

    private final ApiClient api;
    private final Input.Factory inputFactory;
    private final InputState.Factory inputStateFactory;
    private final URI transportAddress;

    private DateTime lastSeen;
    private DateTime lastContact;
    private String nodeId;
    private String clusterId;
    private boolean isMaster;
    private String shortNodeId;
    private AtomicBoolean active = new AtomicBoolean();

    private final boolean fromConfiguration;
    private SystemOverviewResponse systemInfo;
    private NodeJVMStats jvmInfo;

    private AtomicInteger failureCount = new AtomicInteger(0);

    private BufferInfo bufferInfo;
    private BufferClasses bufferClasses;
    private JournalInfo journalInfo;

    @AssistedInject
    public Node(ApiClient api,
                Input.Factory inputFactory,
                InputState.Factory inputStateFactory,
                @Assisted NodeSummaryResponse r) {
        this.api = api;
        this.inputFactory = inputFactory;
        this.inputStateFactory = inputStateFactory;

        transportAddress = normalizeUriPath(r.transportAddress);
        lastSeen = new DateTime(r.lastSeen, DateTimeZone.UTC);
        clusterId = r.clusterId;
        nodeId = r.nodeId;
        shortNodeId = r.shortNodeId;
        isMaster = r.isMaster;
        fromConfiguration = false;
    }

    @AssistedInject
    public Node(ApiClient api,
                Input.Factory inputFactory,
                InputState.Factory inputStateFactory,
                @Assisted URI transportAddress) {
        this.api = api;
        this.inputFactory = inputFactory;
        this.inputStateFactory = inputStateFactory;

        this.transportAddress = normalizeUriPath(transportAddress);
        lastSeen = null;
        clusterId = null;
        nodeId = null;
        shortNodeId = "unresolved";
        isMaster = false;
        fromConfiguration = true;
    }

    public synchronized BufferInfo getBufferInfo() {
        if (this.bufferInfo == null) {
            this.bufferInfo = loadBufferInfo();
        }
        return this.bufferInfo;
    }
    
    public BufferInfo loadBufferInfo() {
        try {
            return new BufferInfo(
                    api.path(routes.BuffersResource().utilization(), BuffersResponse.class)
                            .node(this)
                            .execute());
        } catch (Exception e) {
            LOG.error("Unable to read buffer info from node " + this, e);
        }
        return BufferInfo.buildEmpty();
    }

    public synchronized BufferClasses getBufferClasses() {
        if (this.bufferClasses == null) {
            final BufferClassesResponse response = loadBufferClasses();
            this.bufferClasses = BufferClasses.create(response.inputBufferClass, response.processBufferClass, response.outputBufferClass);
        }
        return this.bufferClasses;
    }
    
    public BufferClassesResponse loadBufferClasses() {
        try {
            return api.path(routes.BuffersResource().getBufferClasses(), BufferClassesResponse.class).node(this).execute();
        } catch (Exception e) {
            LOG.error("Unable to read buffer class names from node " + this, e);
        }
        return BufferClassesResponse.buildEmpty();
    }

    public synchronized JournalInfo getJournalInfo() {
        if (this.journalInfo == null) {
            this.journalInfo = loadJournalInfo();
        }
        return this.journalInfo;
    }
    
    public JournalInfo loadJournalInfo() {
        try {
            return api.path(routes.JournalResource().show(), JournalInfo.class).node(this).execute();
        } catch (Exception e) {
            LOG.error("Unable to read journal info from node " + this, e);
        }
        return JournalInfo.buildEmpty();
    }

    public Map allLoggerSubsystems() {
        Map subsystems = Maps.newHashMap();
        try {
            LoggerSubsystemsResponse response = api.path(routes.LoggersResource().subsystems(), LoggerSubsystemsResponse.class)
                    .node(this)
                    .execute();

            for (Map.Entry ss : response.subsystems.entrySet()) {
                subsystems.put(ss.getKey(), new InternalLoggerSubsystem(
                        ss.getValue().title,
                        ss.getValue().level,
                        ss.getValue().levelSyslog
                ));
            }
        } catch (Exception e) {
            LOG.error("Unable to load subsystems for node " + this, e);
        }
        return subsystems;
    }

    public List allLoggers() {
        List loggers = Lists.newArrayList();
        try {
            LoggersResponse response = api.path(routes.LoggersResource().loggers(), LoggersResponse.class)
                    .node(this)
                    .execute();

            for (Map.Entry logger : response.loggers.entrySet()) {
                loggers.add(new InternalLogger(logger.getKey(), logger.getValue().level, logger.getValue().syslogLevel));
            }
        } catch (Exception e) {
            LOG.error("Unable to load loggers for node " + this, e);
        }
        return loggers;
    }

    public void setSubsystemLoggerLevel(String subsystem, String level) throws APIException, IOException {
        api.path(routes.LoggersResource().setSubsystemLoggerLevel(subsystem, level))
                .node(this)
                .execute();
    }

    public String getThreadDump() throws IOException, APIException {
        return api.path(routes.SystemResource().threaddump(), String.class)
                .node(this)
                .accept(MediaType.ANY_TEXT_TYPE)
                .execute();
    }

    @JsonIgnore
    public List getInputStates() {
        List inputStates = Lists.newArrayList();
        for (InputStateSummaryResponse issr : inputs().inputs) {
            inputStates.add(inputStateFactory.fromSummaryResponse(issr, this));
        }
        return inputStates;
    }

    public List getInputs() {
        List inputs = Lists.newArrayList();

        for (InputState input : getInputStates()) {
            inputs.add(input.getInput());
        }

        return inputs;
    }

    public Input getInput(String inputId) throws IOException, APIException {
        final InputSummaryResponse inputSummaryResponse = api.path(routes.InputsResource().single(inputId), InputSummaryResponse.class).node(this).execute();
        return inputFactory.fromSummaryResponse(inputSummaryResponse, this);
    }

    public int numberOfInputs() {
        return inputs().total;
    }

    public InputLaunchResponse updateInput(String inputId, String title, String type, boolean global, Map configuration, String node) {
        final InputLaunchRequest request = InputLaunchRequest.create(title, type, global, configuration, node);

        try {
            return api.path(routes.InputsResource().update(inputId), InputLaunchResponse.class)
                    .node(this)
                    .body(request)
                    .expect(Http.Status.CREATED)
                    .execute();
        } catch (APIException | IOException e) {
            LOG.error("Could not update input " + title, e);
            return null;
        }
    }

    @Override
    public InputLaunchResponse launchInput(String title, String type, Boolean global, Map configuration, boolean isExclusive, String nodeId) throws ExclusiveInputException {
        if (isExclusive) {
            for (Input input : getInputs()) {
                if (input.getType().equals(type)) {
                    throw new ExclusiveInputException();
                }
            }
        }

        final InputLaunchRequest request = InputLaunchRequest.create(title, type, global, configuration, nodeId);

        try {
            return api.path(routes.InputsResource().create(), InputLaunchResponse.class)
                    .node(this)
                    .body(request)
                    .expect(Http.Status.ACCEPTED)
                    .execute();
        } catch (Exception e) {
            LOG.error("Could not launch input " + title, e);
            return null;
        }
    }

    @Override
    public boolean launchExistingInput(String inputId) {
        try {
            api.path(routes.InputsResource().launchExisting(inputId))
                    .node(this)
                    .expect(Http.Status.ACCEPTED)
                    .execute();
            return true;
        } catch (Exception e) {
            LOG.error("Could not launch input " + inputId, e);
        }

        return false;
    }

    @Override
    public boolean terminateInput(String inputId) {
        try {
            api.path(routes.InputsResource().terminate(inputId))
                    .node(this)
                    .expect(Http.Status.ACCEPTED)
                    .execute();
            return true;
        } catch (Exception e) {
            LOG.error("Could not terminate input " + inputId, e);
        }

        return false;
    }

    public Map getInputTypes() throws IOException, APIException {
        return api.path(routes.InputTypesResource().types(), InputTypesResponse.class).node(this).execute().types;
    }

    public InputTypeSummaryResponse getInputTypeInformation(String type) throws IOException, APIException {
        return api.path(routes.InputTypesResource().info(type), InputTypeSummaryResponse.class).node(this).execute();
    }

    @JsonIgnore
    public Map getAllInputTypeInformation() throws IOException, APIException {
        Map types = Maps.newHashMap();

        for (String type : getInputTypes().keySet()) {
            InputTypeSummaryResponse itr = getInputTypeInformation(type);
            types.put(itr.type, itr);
        }

        return types;
    }

    // TODO nodes should not have state beyond their activity status
    public synchronized SystemOverviewResponse loadSystemInformation() {
        try {
            return api.path(routes.SystemResource().system(), SystemOverviewResponse.class)
                    .node(this)
                    .execute();
        } catch (Exception e) {
            LOG.error("Unable to load system information for node " + this, e);
            return null;
        }
    }

    public synchronized NodeJVMStats loadJVMInformation() {
        try {
            return new NodeJVMStats(api.path(routes.SystemResource().jvm(), ClusterEntityJVMStatsResponse.class)
                    .node(this)
                    .execute()
            );
        } catch (Exception e) {
            LOG.error("Unable to load JVM information for node " + this, e);
            return null;
        }
    }

    @Override
    public String getTransportAddress() {
        return transportAddress.toASCIIString();
    }

    public URI getTransportAddressUri() {
        return transportAddress;
    }

    public DateTime getLastSeen() {
        return lastSeen;
    }

    public String getClusterId() {
        return clusterId;
    }

    public String getNodeId() {
        return nodeId;
    }

    @Override
    public String getShortNodeId() {
        return shortNodeId;
    }

    @Override
    public String getHostname() {
        requireSystemInfo();
        return systemInfo.hostname;
    }

    public boolean isMaster() {
        return isMaster;
    }

    public boolean isProcessing() {
        requireSystemInfo();
        return this.systemInfo.isProcessing;
    }

    public String getLifecycle() {
        requireSystemInfo();
        return this.systemInfo.lifecycle;
    }

    public boolean lbAlive() {
        requireSystemInfo();
        return this.systemInfo.lbStatus != null && this.systemInfo.lbStatus.equals("alive");
    }

    public String getVersion() {
        requireSystemInfo();
        return systemInfo.version;
    }

    public String getCodename() {
        requireSystemInfo();
        return systemInfo.codename;
    }

    public String getTimezone() {
        requireSystemInfo();
        return systemInfo.timezone;
    }

    public String getPid() {
        requireJVMInfo();
        return jvmInfo.getPid();
    }

    public String getJVMDescription() {
        requireJVMInfo();
        return jvmInfo.getInfo();
    }

    public NodeJVMStats jvm() {
        requireJVMInfo();
        return jvmInfo;
    }

    public Map getMetrics(String namespace) throws APIException, IOException {
        MetricsListResponse response = api.path(routes.MetricsResource().byNamespace(namespace), MetricsListResponse.class)
                .node(this)
                .expect(200)
                .execute();
        if (response == null) {
            return Collections.emptyMap();
        }
        return response.getMetrics();
    }

    public Metric getSingleMetric(String metricName) throws APIException, IOException {
        return getMetrics(metricName).get(metricName);
    }

    public void pause() throws IOException, APIException {
        api.path(routes.SystemProcessingResource().pauseProcessing())
                .node(this)
                .execute();
    }

    public void resume() throws IOException, APIException {
        api.path(routes.SystemProcessingResource().resumeProcessing())
                .node(this)
                .execute();
    }

    public void overrideLbStatus(String override) throws APIException, IOException {
        api.path(routes.LoadBalancerStatusResource().override(override))
                .node(this)
                .execute();
    }

    public int getThroughput() {
        try {
            return api.path(routes.ThroughputResource().total(), NodeThroughputResponse.class).node(this).execute().throughput;
        } catch (Exception e) {
            LOG.error("Could not load throughput for node " + this, e);
        }
        return 0;
    }

    /**
     * This swallows all exceptions to allow easy lazy-loading in views without exception handling.
     *
     * @return List of running inputs o this node.
     */
    private InputsResponse inputs() {
        try {
            return api.path(routes.InputsResource().list(), InputsResponse.class).node(this).execute();
        } catch (Exception e) {
            LOG.error("Could not get inputs.", e);
            throw new RuntimeException("Could not get inputs.", e);
        }
    }

    public boolean isFromConfiguration() {
        return fromConfiguration;
    }

    @Override
    public void markFailure() {
        failureCount.incrementAndGet();
        setActive(false);
        LOG.info("{} failed, marking as inactive.", this);
    }

    public int getFailureCount() {
        return failureCount.get();
    }

    public DateTime getLastContact() {
        return lastContact;
    }

    public void merge(Node updatedNode) {
        LOG.debug("Merging node {} in this node {}", updatedNode, this);
        this.lastSeen = updatedNode.lastSeen;
        this.isMaster = updatedNode.isMaster;
        this.nodeId = updatedNode.nodeId;
        this.shortNodeId = updatedNode.shortNodeId;
        this.setActive(updatedNode.isActive());
    }

    @Override
    public void touch() {
        this.lastContact = DateTools.nowInUTC();
        setActive(true);
    }

    public boolean isActive() {
        return active.get();
    }

    public void setActive(boolean active) {
        this.active.set(active);
    }

    public void shutdown() throws APIException, IOException {
        api.path(routes.SystemShutdownResource().shutdown())
                .node(this)
                .expect(Http.Status.ACCEPTED)
                .execute();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Node node = (Node) o;

        // if both have a node id, and they are the same, the nodes are the same.
        if (nodeId != null && node.nodeId != null) {
            if (nodeId.equals(node.nodeId)) {
                return true;
            }
        }
        // otherwise if the transport addresses are the same, we consider the nodes to be the same.
        if (transportAddress.equals(node.transportAddress)) return true;

        // otherwise the nodes aren't the same
        return false;
    }

    @Override
    public int hashCode() {
        int result = transportAddress.hashCode();
        result = 31 * result + (nodeId != null ? nodeId.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        final StringBuilder b = new StringBuilder();
        if (nodeId == null) {
            b.append("UnresolvedNode {'").append(transportAddress).append("'}");
            return b.toString();
        }

        b.append("Node {");
        b.append("'").append(nodeId).append("'");
        b.append(", ").append(transportAddress);
        if (isMaster) {
            b.append(", master");
        }
        if (isActive()) {
            b.append(", active");
        } else {
            b.append(", inactive");
        }
        final int failures = getFailureCount();
        if (failures > 0) {
            b.append(", failed: ").append(failures).append(" times");
        }
        b.append("}");
        return b.toString();
    }

    public void requireSystemInfo() {
        if (this.systemInfo == null) {
            this.systemInfo = firstNonNull(loadSystemInformation(), SystemOverviewResponse.buildEmpty());
        }
    }

    public void requireJVMInfo() {
        if (this.jvmInfo == null) {
            this.jvmInfo = firstNonNull(loadJVMInformation(), NodeJVMStats.buildEmpty());
        }
    }

    @Override
    public void stopInput(String inputId) throws IOException, APIException {
        api.path(routes.InputsResource().stop(inputId))
                .node(this)
                .expect(Http.Status.ACCEPTED)
                .execute();
    }

    @Override
    public void startInput(String inputId) throws IOException, APIException {
        api.path(routes.InputsResource().launchExisting(inputId))
                .node(this)
                .expect(Http.Status.ACCEPTED)
                .execute();
    }

    @Override
    public void restartInput(String inputId) throws IOException, APIException {
        api.path(routes.InputsResource().restart(inputId))
                .node(this)
                .expect(Http.Status.ACCEPTED)
                .execute();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy