com.mgmtp.perfload.core.daemon.LtDaemon.java Source code

Java tutorial

Introduction

Here is the source code for com.mgmtp.perfload.core.daemon.LtDaemon.java

Source

/*
 * Copyright (c) 2002-2014 mgm technology partners GmbH
 *
 * Licensed 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 com.mgmtp.perfload.core.daemon;

import com.beust.jcommander.JCommander;
import com.beust.jcommander.ParameterException;
import com.google.common.base.Predicates;
import com.mgmtp.perfload.core.clientserver.client.Client;
import com.mgmtp.perfload.core.clientserver.client.ClientMessageListener;
import com.mgmtp.perfload.core.clientserver.client.DefaultClient;
import com.mgmtp.perfload.core.clientserver.server.DefaultServer;
import com.mgmtp.perfload.core.clientserver.server.Server;
import com.mgmtp.perfload.core.clientserver.server.ServerMessageListener;
import com.mgmtp.perfload.core.clientserver.util.ChannelContainer;
import com.mgmtp.perfload.core.common.clientserver.Payload;
import com.mgmtp.perfload.core.common.clientserver.PayloadType;
import com.mgmtp.perfload.core.common.config.ProcessConfig;
import com.mgmtp.perfload.core.common.config.TestConfig;
import com.mgmtp.perfload.core.common.config.TestJar;
import com.mgmtp.perfload.core.common.util.StreamGobbler;
import com.mgmtp.perfload.core.daemon.util.AbstractClientRunner;
import com.mgmtp.perfload.core.daemon.util.ForkedProcessClientRunner;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.MessageEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import static com.google.common.base.Joiner.on;
import static com.google.common.collect.Lists.newArrayList;
import static com.mgmtp.perfload.core.common.clientserver.ChannelPredicates.isConsoleChannel;
import static com.mgmtp.perfload.core.common.clientserver.ChannelPredicates.isTestprocChannel;

/**
 * Represents a perfLoad daemon process. A perfLoad daemon is responsible for spawning test
 * processes. It acts as a server that communicates with its clients (i. e. test processes and
 * management console).
 *
 * @author rnaegele
 */
public class LtDaemon {
    private final CountDownLatch doneLatch = new CountDownLatch(1);
    private final Server server;

    /**
     * Creates a new instance.
     *
     * @param clientDir            the client's installation and working directory
     * @param abstractClientRunner the {@link AbstractClientRunner} implementation used to run client processes
     * @param server               the {@link Server} implementation for the daemon
     */
    public LtDaemon(final File clientDir, final AbstractClientRunner abstractClientRunner, final Server server) {
        this.server = server;
        this.server.addServerMessageListener(new DaemonMessageListener(clientDir, abstractClientRunner));
    }

    public static void main(final String... args) {
        JCommander jCmd = null;
        try {
            LtDaemonArgs cliArgs = new LtDaemonArgs();
            jCmd = new JCommander(cliArgs);
            jCmd.parse(args);

            log().info("Starting perfLoad Daemon...");
            log().info(cliArgs.toString());

            if (cliArgs.shutdown) {
                shutdownDaemon(cliArgs.port);
                System.exit(0);
            }

            // Client is expected next to the daemon
            File clientDir = new File(new File(System.getProperty("user.dir")).getParentFile(), "client");
            ExecutorService execService = Executors.newCachedThreadPool();
            StreamGobbler gobbler = new StreamGobbler(execService);
            Server server = new DefaultServer(cliArgs.port);
            LtDaemon daemon = new LtDaemon(clientDir, new ForkedProcessClientRunner(execService, gobbler), server);
            daemon.execute();
            execService.shutdown();
            execService.awaitTermination(10L, TimeUnit.SECONDS);
            System.exit(0);
        } catch (ParameterException ex) {
            jCmd.usage();
            System.exit(1);
        } catch (Exception ex) {
            log().error(ex.getMessage(), ex);
            System.exit(-1);
        }
    }

    private static Logger log() {
        return LoggerFactory.getLogger(LtDaemon.class);
    }

    public static void shutdownDaemon(final int port) {
        final CountDownLatch latch = new CountDownLatch(1);

        Client client = new DefaultClient("shutdownClient", "localhost", port);
        client.addClientMessageListener(new ClientMessageListener() {
            @Override
            public void messageReceived(final ChannelHandlerContext ctx, final MessageEvent e) {
                final Payload payload = (Payload) e.getMessage();
                if (payload.getPayloadType() == PayloadType.SHUTDOWN_DAEMON) {
                    log().info("Successfully requested shutdown.");
                    latch.countDown();
                }
            }
        });

        log().info("Connecting to daemon at port {}...", port);
        client.connect();
        if (client.isConnected()) {
            client.sendMessage(new Payload(PayloadType.SHUTDOWN_DAEMON));
            client.disconnect();
        } else {
            log().info("Could not connect to daemon. Daemon probably not running.");
        }
        log().info("Good bye.");
    }

    /**
     * Starts the daemon creating the server. Further actions are triggered by requests from
     * management console and test processes.
     */
    public void execute() {
        log().info("Initializing...");

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                // Count down the latch, so the daemon can exit.
                // Also executed when the user hits Ctrl-C
                if (doneLatch != null) {
                    doneLatch.countDown();
                    try {
                        // TODO timeouts configurable?
                        Thread.sleep(2000L);
                    } catch (InterruptedException ex) {
                        //
                    }
                }
                server.shutdown();
                log().info("Good bye.");
            }
        });

        server.bind();

        try {
            // Keep the daemon alive
            doneLatch.await();
            log().info("Exiting...");
        } catch (InterruptedException ex) {
            // cannot normally happen
            log().error(ex.getMessage(), ex);
        }
    }

    private final class DaemonMessageListener implements ServerMessageListener {
        private final ExecutorService execService = Executors.newCachedThreadPool();
        private final Set<String> testJarNames = new ConcurrentSkipListSet<>();
        private final File clientDir;
        private final AbstractClientRunner abstractClientRunner;

        private volatile File testLibDir;

        public DaemonMessageListener(final File clientDir, final AbstractClientRunner abstractClientRunner) {
            this.clientDir = clientDir;
            this.abstractClientRunner = abstractClientRunner;

        }

        private void deleteTestLibDir() {
            if (testLibDir != null) {
                synchronized (this) {
                    if (testLibDir != null) {
                        log().debug("Deleting test lib directory: {}", testLibDir);
                        try {
                            FileUtils.forceDelete(testLibDir);
                        } catch (IOException ex) {
                            // ignored
                        }
                        testLibDir = null;
                    }
                }
            }
        }

        private void createTestLibDir() throws IOException {
            if (testLibDir == null) {
                synchronized (this) {
                    if (testLibDir == null) {
                        testLibDir = new File(clientDir, "lib_daemon_" + System.nanoTime());
                        log().debug("Creating test lib directory: {}", testLibDir);
                    }
                }
                FileUtils.forceMkdir(testLibDir);
            }
        }

        @Override
        public void messageReceived(final ChannelHandlerContext ctx, final ChannelContainer channelContainer,
                final MessageEvent e) {
            try {
                final Payload payload = (Payload) e.getMessage();
                switch (payload.getPayloadType()) {
                case CONSOLE_DISCONNECTING:
                    deleteTestLibDir();
                    break;
                case SHUTDOWN_DAEMON:
                    log().info("Shutdown was requested.");
                    e.getChannel().write(payload);
                    doneLatch.countDown();
                    break;
                case ERROR:
                    deleteTestLibDir();
                    channelContainer.getChannel(isConsoleChannel()).write(payload);
                    break;
                case CREATE_TEST_PROC:
                    execService.submit(new Runnable() {
                        @Override
                        public void run() {
                            Channel channel = e.getChannel();
                            try {
                                ProcessConfig pc = payload.getContent();

                                List<String> arguments = newArrayList();
                                arguments.add("-processId");
                                arguments.add(String.valueOf(pc.getProcessId()));
                                arguments.add("-daemonId");
                                arguments.add(String.valueOf(pc.getDaemonId()));
                                arguments.add("-daemonPort");
                                arguments.add(String.valueOf(server.getPort()));
                                if (!testJarNames.isEmpty()) {
                                    arguments.add("-testLibDir");
                                    arguments.add(testLibDir.getAbsolutePath());
                                    arguments.add("-testJars");
                                    arguments.add(on(';').join(testJarNames));
                                }

                                Future<Integer> runResult = abstractClientRunner.runClient(clientDir, pc,
                                        arguments);

                                channel.write(new Payload(PayloadType.TEST_PROC_STARTED, pc));

                                int exitCode = runResult.get();
                                if (exitCode != 0) {
                                    // Logging is enough here
                                    log().error("Client process terminated with an error.");
                                }

                                channel.write(new Payload(PayloadType.TEST_PROC_TERMINATED, pc));
                            } catch (Throwable th) {
                                log().error(th.getMessage(), th);
                                channel.write(new Payload(PayloadType.ERROR));
                            }
                        }
                    });
                    break;
                case TEST_PROC_DISCONNECTED:
                    log().info("Client process disconnected: {}", payload.<Serializable>getContent());
                    e.getChannel().write(payload);
                    channelContainer.getChannel(isConsoleChannel()).write(payload);
                    break;
                case STATUS:
                case TEST_PROC_CONNECTED:
                case TEST_PROC_READY:
                case TEST_PROC_STARTED:
                case TEST_PROC_TERMINATED:
                    channelContainer.getChannel(isConsoleChannel()).write(payload);
                    break;
                case START:
                    for (Channel channel : channelContainer.getChannels(isTestprocChannel())) {
                        if (channel.isConnected()) {
                            channel.write(payload);
                        }
                    }
                    break;
                case ABORT:
                    deleteTestLibDir();
                    for (Channel channel : channelContainer.getChannels(isTestprocChannel())) {
                        if (channel.isConnected()) {
                            channel.write(payload);
                        }
                    }
                    break;
                case JAR:
                    TestJar jar = payload.getContent();
                    OutputStream os = null;
                    String jarName = jar.getName();
                    log().debug("Received jar file: {}", jarName);
                    try {
                        createTestLibDir();
                        os = new FileOutputStream(new File(testLibDir, jarName));
                        IOUtils.write(jar.getContent(), os);
                        log().debug("Received jar file to: {}", testLibDir);
                        testJarNames.add(jarName);

                        // Write an empty jar response back to the console, so it knows
                        // the jar was correctly received and stored.
                        e.getChannel().write(new Payload(PayloadType.JAR));
                    } catch (IOException ex) {
                        log().error("Error saving jar files: {}", jarName, ex);
                        e.getChannel().write(new Payload(PayloadType.ERROR));
                    } finally {
                        IOUtils.closeQuietly(os);
                    }
                    break;
                case CLIENT_COUNT:
                    e.getChannel()
                            .write(new Payload(PayloadType.CLIENT_COUNT, channelContainer.getChannels().size()));
                    break;
                case CONFIG:
                    TestConfig config = payload.getContent();
                    log().debug("Received test configuration");

                    String channelId = "testproc" + config.getProcessId();
                    Channel channel = channelContainer.getChannel(Predicates.equalTo(channelId));
                    log().debug("Forwarding test configuration to client '{}", channelId);
                    channel.write(new Payload(PayloadType.CONFIG, config));
                    break;
                default:
                    //
                }
            } catch (Exception ex) {
                log().error(ex.getMessage(), ex);
                channelContainer.getChannel(isConsoleChannel()).write(new Payload(PayloadType.ERROR));
            }
        }
    }
}