ee.ria.xroad.proxy.opmonitoring.OpMonitoringBuffer.java Source code

Java tutorial

Introduction

Here is the source code for ee.ria.xroad.proxy.opmonitoring.OpMonitoringBuffer.java

Source

/**
 * The MIT License
 * Copyright (c) 2016 Estonian Information System Authority (RIA), Population Register Centre (VRK)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package ee.ria.xroad.proxy.opmonitoring;

import java.net.NetworkInterface;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import akka.actor.ActorRef;
import akka.actor.Cancellable;
import akka.actor.Props;
import com.google.gson.Gson;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import scala.concurrent.duration.FiniteDuration;

import ee.ria.xroad.common.conf.serverconf.ServerConf;
import ee.ria.xroad.common.opmonitoring.AbstractOpMonitoringBuffer;
import ee.ria.xroad.common.opmonitoring.OpMonitoringDaemonHttpClient;
import ee.ria.xroad.common.opmonitoring.OpMonitoringData;
import ee.ria.xroad.common.opmonitoring.OpMonitoringSystemProperties;
import ee.ria.xroad.common.opmonitoring.StoreOpMonitoringDataRequest;
import ee.ria.xroad.common.util.JsonUtils;
import ee.ria.xroad.common.util.TimeUtils;

import static java.net.NetworkInterface.getNetworkInterfaces;
import static java.util.Collections.list;

/**
 * Operational monitoring buffer. This buffer is used for gathering
 * operational data and for periodically sending the data to the operational
 * monitoring daemon (using OpMonitoringDaemonSender).
 */
@Slf4j
public class OpMonitoringBuffer extends AbstractOpMonitoringBuffer {
    public static final String OP_MONITORING_DAEMON_SENDER = "OpMonitoringDaemonSender";

    private static final String NO_ADDRESS_FOUND = "No suitable IP address is bound to the network interface ";
    private static final String NO_INTERFACE_FOUND = "No non-loopback network interface found";

    private static final long MAX_BUFFER_SIZE = OpMonitoringSystemProperties.getOpMonitorBufferSize();

    private static final int MAX_RECORDS_IN_MESSAGE = OpMonitoringSystemProperties
            .getOpMonitorBufferMaxRecordsInMessage();
    private static final long SENDING_INTERVAL_SECONDS = OpMonitoringSystemProperties
            .getOpMonitorBufferSendingIntervalSeconds();

    private static final int CLIENT_CONNECTION_TIMEOUT_MILLISECONDS = TimeUtils
            .secondsToMillis(OpMonitoringSystemProperties.getOpMonitorBufferConnectionTimeoutSeconds());

    private static final int CLIENT_SOCKET_TIMEOUT_MILLISECONDS = TimeUtils
            .secondsToMillis(OpMonitoringSystemProperties.getOpMonitorBufferSocketTimeoutSeconds());

    private static final Gson GSON = JsonUtils.getSerializer();

    private Cancellable tick;

    final Map<Long, OpMonitoringData> buffer = new LinkedHashMap<Long, OpMonitoringData>() {
        @Override
        protected boolean removeEldestEntry(Map.Entry eldest) {
            boolean overflow = size() > MAX_BUFFER_SIZE;

            if (overflow) {
                log.warn("Operational monitoring buffer overflow, removing eldest record: {}", eldest.getKey());
            }

            return overflow;
        }
    };

    private long bufferIndex = 0;

    private final Set<Long> processedBufferIndices = new HashSet<>();

    private final CloseableHttpClient httpClient;

    private final ActorRef sender;

    private static String ipAddress;

    /**
     * Constructor.
     * @throws Exception if an error occurs
     */
    public OpMonitoringBuffer() throws Exception {
        if (ignoreOpMonitoringData()) {
            log.info("Operational monitoring buffer is switched off, no operational monitoring data is stored");

            httpClient = null;
            sender = null;
        } else {
            httpClient = createHttpClient();
            sender = createSender();
        }
    }

    CloseableHttpClient createHttpClient() throws Exception {
        return OpMonitoringDaemonHttpClient.createHttpClient(ServerConf.getSSLKey(), 1, 1,
                CLIENT_CONNECTION_TIMEOUT_MILLISECONDS, CLIENT_SOCKET_TIMEOUT_MILLISECONDS);
    }

    ActorRef createSender() {
        return getContext().system().actorOf(Props.create(OpMonitoringDaemonSender.class, httpClient),
                OP_MONITORING_DAEMON_SENDER);
    }

    @Override
    protected void store(OpMonitoringData data) throws Exception {
        if (ignoreOpMonitoringData()) {
            return;
        }

        data.setSecurityServerInternalIp(getIpAddress());

        buffer.put(getNextBufferIndex(), data);

        send();
    }

    @Override
    protected void send() throws Exception {
        if (!canSend()) {
            return;
        }

        String json = prepareMonitoringMessage();

        sender.tell(json, getSelf());
    }

    private boolean canSend() {
        return !buffer.isEmpty() && processedBufferIndices.isEmpty();
    }

    private String prepareMonitoringMessage() {
        StoreOpMonitoringDataRequest request = new StoreOpMonitoringDataRequest();

        for (Map.Entry<Long, OpMonitoringData> entry : buffer.entrySet()) {
            processedBufferIndices.add(entry.getKey());
            request.addRecord(entry.getValue().getData());

            if (request.getRecords().size() == MAX_RECORDS_IN_MESSAGE) {
                break;
            }
        }

        log.debug("Op monitoring buffer records count: {}", buffer.size());

        return GSON.toJson(request);
    }

    @Override
    protected void sendingSuccess() throws Exception {
        processedBufferIndices.forEach(buffer::remove);
        processedBufferIndices.clear();

        if (canSend()) {
            send();
        }
    }

    @Override
    protected void sendingFailure() throws Exception {
        processedBufferIndices.clear();

        // Do not worry, scheduled sending retries..
    }

    long getNextBufferIndex() {
        bufferIndex = bufferIndex == Long.MAX_VALUE ? 0 : bufferIndex + 1;

        return bufferIndex;
    }

    private void scheduleSendMonitoringData() {
        FiniteDuration interval = FiniteDuration.create(SENDING_INTERVAL_SECONDS, TimeUnit.SECONDS);

        tick = getContext().system().scheduler().schedule(interval, interval, getSelf(), SEND_MONITORING_DATA,
                getContext().dispatcher(), ActorRef.noSender());
    }

    @Override
    public void preStart() throws Exception {
        if (ignoreOpMonitoringData()) {
            return;
        }

        scheduleSendMonitoringData();
    }

    @Override
    public void postStop() throws Exception {
        if (tick != null) {
            tick.cancel();
        }

        if (httpClient != null) {
            IOUtils.closeQuietly(httpClient);
        }
    }

    private boolean ignoreOpMonitoringData() {
        return MAX_BUFFER_SIZE < 1;
    }

    private static String getIpAddress() {
        try {
            if (ipAddress == null) {
                NetworkInterface ni = list(getNetworkInterfaces()).stream()
                        .filter(OpMonitoringBuffer::isNonLoopback).findFirst()
                        .orElseThrow(() -> new Exception(NO_INTERFACE_FOUND));

                Exception addressNotFound = new Exception(NO_ADDRESS_FOUND + ni.getDisplayName());

                ipAddress = list(ni.getInetAddresses()).stream().filter(addr -> !addr.isLinkLocalAddress())
                        .findFirst().orElseThrow(() -> addressNotFound).getHostAddress();

                if (ipAddress == null) {
                    throw addressNotFound;
                }
            }

            return ipAddress;
        } catch (Exception e) {
            log.error("Cannot get IP address of a non-loopback network interface", e);

            return "0.0.0.0";
        }
    }

    @SneakyThrows
    private static boolean isNonLoopback(NetworkInterface ni) {
        return !ni.isLoopback() && ni.isUp();
    }
}