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

io.minecloud.daemon.MineCloudDaemon Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2015, Mazen Kotb 
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
package io.minecloud.daemon;

import io.minecloud.MineCloud;
import io.minecloud.db.Credentials;
import io.minecloud.db.mongo.MongoDatabase;
import io.minecloud.db.redis.RedisDatabase;
import io.minecloud.db.redis.msg.MessageType;
import io.minecloud.db.redis.msg.binary.MessageInputStream;
import io.minecloud.db.redis.pubsub.SimpleRedisChannel;
import io.minecloud.models.bungee.Bungee;
import io.minecloud.models.bungee.BungeeRepository;
import io.minecloud.models.bungee.type.BungeeType;
import io.minecloud.models.network.Network;
import io.minecloud.models.nodes.Node;
import io.minecloud.models.nodes.NodeRepository;
import io.minecloud.models.server.Server;
import io.minecloud.models.server.ServerMetadata;
import io.minecloud.models.server.ServerRepository;
import io.minecloud.models.server.type.ServerType;
import org.mongodb.morphia.query.Query;
import redis.clients.jedis.Jedis;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.util.*;
import java.util.logging.Level;
import java.util.stream.Collectors;

public class MineCloudDaemon {
    private static MineCloudDaemon instance;

    private final String node;
    private final RedisDatabase redis;
    private final MongoDatabase mongo;

    private List names;

    private MineCloudDaemon(Properties properties) {
        redis = MineCloud.instance().redis();
        mongo = MineCloud.instance().mongo();
        node = (String) properties.get("node-name");

        instance = this;

        redis.addChannel(SimpleRedisChannel.create("server-create", redis)
                .addCallback((message) -> {
                    if (message.type() != MessageType.BINARY) {
                        return;
                    }

                    MessageInputStream stream = message.contents();

                    if (!stream.readString().equalsIgnoreCase(node)) {
                        return;
                    }

                    Network network = mongo.repositoryBy(Network.class).findFirst(stream.readString());
                    ServerType type = mongo.repositoryBy(ServerType.class).findFirst(stream.readString());
                    List metadata = new ArrayList<>();
                    int size = stream.readVarInt32();

                    for (int i = 0; i < size; i++) {
                        metadata.add(new ServerMetadata(stream.readString(), stream.readString()));
                    }

                    Deployer.deployServer(network, type, metadata);
                }));

        redis.addChannel(SimpleRedisChannel.create("server-kill", redis)
                .addCallback((message) -> {
                    if (message.type() != MessageType.BINARY) {
                        return;
                    }

                    MessageInputStream stream = message.contents();

                    if (!stream.readString().equalsIgnoreCase(node))
                        return;

                    Server server = mongo.repositoryBy(Server.class).findFirst(stream.readString());

                    if (!server.node().name().equals(node)) {
                        MineCloud.logger().log(Level.SEVERE, "Invalid request was sent to kill a server " +
                                "not on the current node");
                        return;
                    }

                    try {
                        new ProcessBuilder().command("/usr/bin/kill", "-9", String.valueOf(Deployer.pidOf(server.name()))).start();
                        Deployer.runExit(server.name());
                        MineCloud.logger().info("Killed server " + server.name()
                                + " with container id " + server.containerId());
                        mongo.repositoryBy(Server.class).delete(server);
                        try (Jedis jedis = this.redis.grabResource()) {
                            jedis.hdel("server:" + server.entityId(), "heartbeat");
                        }
                    } catch (IOException e) {
                        MineCloud.logger().log(Level.SEVERE, "Was unable to kill a server", e);
                    }
                }));

        redis.addChannel(SimpleRedisChannel.create("bungee-create", redis)
                .addCallback((message) -> {
                    if (message.type() != MessageType.BINARY) {
                        return;
                    }

                    MessageInputStream stream = message.contents();

                    if (!stream.readString().equalsIgnoreCase(node))
                        return;

                    Network network = mongo.repositoryBy(Network.class).findFirst(stream.readString());
                    BungeeType type = mongo.repositoryBy(BungeeType.class).findFirst(stream.readString());

                    Deployer.deployBungee(network, type);
                }));

        redis.addChannel(SimpleRedisChannel.create("bungee-kill", redis)
                .addCallback((message) -> {
                    if (message.type() != MessageType.BINARY) {
                        return;
                    }

                    MessageInputStream stream = message.contents();

                    if (!stream.readString().equalsIgnoreCase(node))
                        return;

                    Bungee bungee = mongo.repositoryBy(Bungee.class).findFirst(stream.readString());

                    if (!bungee.node().name().equals(node)) {
                        MineCloud.logger().log(Level.SEVERE, "Invalid request was sent to kill a bungee " +
                                "not on the current node");
                        return;
                    }

                    try {
                        new ProcessBuilder().command("/usr/bin/kill", "-9", String.valueOf(Deployer.pidOf("bungee"))).start();
                        MineCloud.logger().info("Killed bungee " + bungee.name()
                                + " with container id " + bungee.containerId());

                        mongo.repositoryBy(Bungee.class).delete(bungee);
                    } catch (IOException e) {
                        MineCloud.logger().log(Level.SEVERE, "Was unable to kill a server", e);
                    }
                }));

        redis.addChannel(SimpleRedisChannel.create("server-start-notif", redis)
                .addCallback((message) -> {
                    if (message.type() != MessageType.BINARY)
                        return;

                    MessageInputStream stream = message.contents();
                    Server server = mongo.repositoryBy(Server.class).findFirst(stream.readString());

                    if (!server.node().name().equals(node))
                        return;

                    server.setContainerId(String.valueOf(Deployer.pidOf(server.name())));
                    mongo.repositoryBy(Server.class).save(server);
                }));
        redis.addChannel(SimpleRedisChannel.create("server-shutdown-notif", redis)
                .addCallback((message) -> {
                    if (message.type() != MessageType.BINARY)
                        return;

                    MessageInputStream stream = message.contents();
                    File file = new File("/var/minecloud/" + stream.readString());

                    if (file.exists()) {
                        file.delete();
                    }
                }));

        new StatisticsWatcher().start();
        ServerRepository repository = mongo.repositoryBy(Server.class);
        OptionalInt port = repository.find(repository.createQuery().field("node").equal(node()))
                .asList().stream().mapToInt(Server::port).sorted().max();
        Deployer.PORT_COUNTER.set((port.isPresent() ? port.getAsInt() : 32811) + 1);

        while (!Thread.currentThread().isInterrupted()) {
            this.redis.connected(); //Checks for Redis death, if it's dead it will reconnect.

            BungeeRepository bungeeRepo = mongo.repositoryBy(Bungee.class);
            Node node = node();
            Query query = repository.createQuery()
                    .field("node").equal(node);
            List nodeServers = repository.find(query).asList();
            names = nodeServers.stream()
                    .map(Server::name)
                    .collect(Collectors.toList());

            nodeServers.stream().filter((s) -> s.ramUsage() != -1).forEach((server) -> {
                File runDir = new File("/var/minecloud/" + server.name());

                if (!runDir.exists()) {
                    repository.delete(server);
                    MineCloud.logger().info("Removed " + server.containerId() + " from DB due to not existing on node");
                    return;
                }

                try {
                    if ((System.currentTimeMillis() - Deployer.timeStarted(server.name())) < 600_000L) {
                        return;
                    }

                    if (Deployer.isRunning(server.name())) {
                        return;
                    }

                    Deployer.killServer(server.name());
                    repository.delete(server);
                    names.remove(server.name());
                    try (Jedis jedis = this.redis.grabResource()) {
                        jedis.hdel("server:" + server.entityId(), "heartbeat");
                    }
                    MineCloud.logger().info("Removed dead server (" + server.name() + ")");
                } catch (IOException | InterruptedException ex) {
                    if (!(ex instanceof NoSuchFileException)) {
                        MineCloud.logger().log(Level.SEVERE, "Was unable to check if server is running", ex);
                    }
                }
            });

            try (Jedis jedis = this.redis.grabResource()) {
                nodeServers.stream().filter((s) -> s.ramUsage() != -1).forEach(server ->  {
                    Map hResult = jedis.hgetAll("server:" + server.entityId());

                    if (hResult == null || hResult.isEmpty()) {
                        return; //Prevent loop from dying.
                    }

                    long heartbeat = Long.valueOf(hResult.get("heartbeat"));
                    long difference = System.currentTimeMillis() - heartbeat;
                    if (difference > 35000L) {
                        Deployer.killServer(server.name());
                        repository.delete(server);
                        names.remove(server.name());
                        MineCloud.logger().log(Level.WARNING, "Found server not updated in 20s, killing (" + server.name() + ")");
                    }
                });
            }

            if (bungeeRepo.findOne("_id", node.publicIp()) != null) {
                try {
                    if ((System.currentTimeMillis() - Deployer.timeStarted("bungee")) > 600_000L & !Deployer.isRunning("bungee")) {
                        bungeeRepo.deleteById(node.publicIp());
                        MineCloud.logger().info("Removed dead bungee (" + node.publicIp() + ")");
                    }
                } catch (IOException | InterruptedException ex) {
                    if (!(ex instanceof NoSuchFileException)) {
                        MineCloud.logger().log(Level.SEVERE, "Was unable to check if bungee is running", ex);
                    }
                }
            }

            File appContainer = new File("/var/minecloud");

            names.add("bungee"); // don't remove bungee servers

            if (!appContainer.isDirectory()) {
                appContainer.delete();
            }

            if (!appContainer.exists()) {
                appContainer.mkdirs();
            }

            for (File f : appContainer.listFiles(File::isDirectory)) {
                if (!names.contains(f.getName())) {
                    Deployer.killServer(f.getName());
                }
            }

            /* Startup timeout */
            nodeServers.stream().filter((s) -> s.ramUsage() == -1).forEach((s) -> {
                long timeOut = s.type().timeOut() * 1000L; // to ms

                if ((System.currentTimeMillis() - s.startTime()) >= timeOut) {
                    Deployer.killServer(s.name());
                    repository.delete(s);
                    names.remove(s.name());
                }
            });

            try {
                Thread.sleep(2000L);
            } catch (InterruptedException ignored) {
            }
        }
    }

    public static void main(String[] args) throws Exception {
        Properties properties = new Properties();
        File configFolder = new File("/etc/minecloud/");
        File file = new File(configFolder, "details.properties");

        if (!configFolder.exists()) {
            configFolder.mkdirs();
        }

        if (!file.exists()) {
            file.createNewFile();
        }

        properties.load(new FileInputStream(file));

        if (!properties.containsKey("mongo-hosts")) {
            MineCloud.runSetup(properties, file);
            new MineCloudDaemon(properties);

            properties = null;
            return;
        }

        Credentials mongo = new Credentials(properties.getProperty("mongo-hosts").split(";"),
                properties.getProperty("mongo-username"),
                properties.getProperty("mongo-password").toCharArray(),
                properties.getProperty("mongo-database"));
        Credentials redis = new Credentials(new String[] {properties.getProperty("redis-host")},
                "",
                properties.getProperty("redis-password").toCharArray());

        MineCloud.instance().initiateMongo(mongo);
        MineCloud.instance().initiateRedis(redis);

        new MineCloudDaemon(properties);
    }

    public static MineCloudDaemon instance() {
        return instance;
    }

    public Node node() {
        return ((NodeRepository) mongo.repositoryBy(Node.class)).nodeBy(node);
    }

    public RedisDatabase redis() {
        return redis;
    }

    private List files(File directory) {
        List files = new ArrayList<>();
        File[] dirFiles = directory.listFiles();
        files.addAll(Arrays.asList(dirFiles));

        return files;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy