Example #1
0
  @Override
  public void setup() {
    JedisPoolConfig config = new JedisPoolConfig();

    config.setMaxTotal(20);
    config.setMinIdle(5);
    config.setMaxIdle(10);
    config.setMaxWaitMillis(200L);
    config.setBlockWhenExhausted(false);

    String host = credentials.hosts()[0];
    int port = 6379;

    if (host.split(":").length == 2) {
      try {
        port = Integer.parseInt(host.split(":")[1]);
      } catch (NumberFormatException ignored) {
        MineCloud.logger().warning("Host " + host + " has an invalid port!");
      }
    }

    pool =
        credentials.password() != null && credentials.password().length > 0
            ? new JedisPool(config, host, port, 1000, new String(credentials.password()))
            : new JedisPool(config, host, port, 1000);
  }
  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);
  }
  @Override
  public void onEnable() {
    MineCloud.environmentSetup();

    mongo = MineCloud.instance().mongo();
    redis = MineCloud.instance().redis();

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

                              try {
                                MessageInputStream stream = message.contents();

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

                                addServer(server);
                              } catch (IOException ignored) {
                              }
                            },
                            1,
                            TimeUnit.SECONDS)));

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

                  MessageInputStream stream = message.contents();

                  removeServer(stream.readString());
                }));

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

                  MessageInputStream stream = message.contents();
                  ProxiedPlayer player = getProxy().getPlayer(stream.readString());

                  if (player == null) {
                    return;
                  }

                  String name = stream.readString();
                  ServerInfo info = getProxy().getServerInfo(name);

                  if (info == null) {
                    ServerRepository repository = mongo.repositoryBy(Server.class);
                    Server server = repository.findOne("_id", name);

                    if (server != null) {
                      addServer(server);
                      info = getProxy().getServerInfo(name);
                    }
                  }

                  player.connect(info);
                }));

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

                  MessageInputStream stream = message.contents();
                  ProxiedPlayer player = getProxy().getPlayer(stream.readString());

                  if (player == null) {
                    return;
                  }

                  ServerType type =
                      mongo.repositoryBy(ServerType.class).findFirst(stream.readString());

                  if (type == null) {
                    getLogger()
                        .log(Level.SEVERE, "Received teleport message with invalid server type");
                    return;
                  }

                  ServerRepository repository = mongo.repositoryBy(Server.class);
                  List<Server> servers =
                      repository
                          .find(
                              repository
                                  .createQuery()
                                  .field("network")
                                  .equal(bungee().network())
                                  .field("type")
                                  .equal(type)
                                  .field("port")
                                  .notEqual(-1)
                                  .field("ramUsage")
                                  .notEqual(-1))
                          .asList();

                  Collections.sort(
                      servers, (a, b) -> a.onlinePlayers().size() - b.onlinePlayers().size());

                  Server server = servers.get(0);
                  ServerInfo info = getProxy().getServerInfo(server.name());

                  if (info == null) {
                    getLogger()
                        .warning("Cannot find " + server.name() + " in ServerInfo store, adding.");
                    addServer(server);
                    info = getProxy().getServerInfo(server.name());
                  }

                  player.connect(info);
                }));

    getProxy()
        .getScheduler()
        .schedule(
            this,
            () ->
                getProxy()
                    .getScheduler()
                    .runAsync(
                        this,
                        () -> {
                          Bungee bungee = bungee();

                          if (bungee != null) {
                            return;
                          }

                          getLogger().info("Bungee removed from database, going down...");
                          getProxy().stop(); // bye bye
                        }),
            2,
            2,
            TimeUnit.SECONDS);

    BungeeType type = bungee().type();

    File nContainer = new File("nplugins/");
    nContainer.mkdirs();

    type.plugins()
        .forEach(
            (plugin) -> {
              String version = plugin.version();
              PluginType pluginType = plugin.type();
              File pluginsContainer =
                  new File("/mnt/minecloud/plugins/", pluginType.name() + "/" + version);
              List<File> plugins = new ArrayList<>();

              getLogger().info("Loading " + pluginType.name() + "...");

              if (validateFolder(pluginsContainer, pluginType, version)) return;

              for (File f : pluginsContainer.listFiles()) {
                if (f.isDirectory()) continue; // ignore directories
                File pl = new File(nContainer, f.getName());

                try {
                  Files.copy(f, pl);
                } catch (IOException ex) {
                  getLogger()
                      .log(
                          Level.SEVERE,
                          "Could not load " + pluginType.name() + ", printing stacktrace...");
                  ex.printStackTrace();
                  return;
                }

                plugins.add(pl);
              }

              File configs =
                  new File(
                      "/mnt/minecloud/configs/",
                      pluginType.name()
                          + "/"
                          + (plugin.config() == null ? version : plugin.config()));
              File configContainer = new File(nContainer, pluginType.name());

              if (!validateFolder(configs, pluginType, version))
                copyFolder(configs, configContainer);
            });

    getProxy()
        .getScheduler()
        .schedule(
            this,
            () -> {
              ServerRepository repository = mongo.repositoryBy(Server.class);
              List<Server> servers =
                  repository
                      .find(repository.createQuery().field("network").equal(bungee().network()))
                      .asList();

              servers.removeIf((s) -> s.port() == -1);
              servers.forEach(this::addServer);

              getProxy().setReconnectHandler(new ReconnectHandler(this));
              getProxy().getPluginManager().registerListener(this, new MineCloudListener(this));

              // release plugin manager lock
              try {
                Field f = PluginManager.class.getDeclaredField("toLoad");

                f.setAccessible(true);
                f.set(getProxy().getPluginManager(), new HashMap<>());
              } catch (NoSuchFieldException | IllegalAccessException ignored) {
              }

              getProxy().getPluginManager().detectPlugins(nContainer);
              getProxy().getPluginManager().loadPlugins();
              getProxy()
                  .getPluginManager()
                  .getPlugins()
                  .stream()
                  .filter((p) -> !p.getDescription().getName().equals("MineCloud-Bungee"))
                  .forEach(Plugin::onEnable);
            },
            0,
            TimeUnit.SECONDS);
  }
  private MineCloudDaemon(Properties properties) {
    redis = MineCloud.instance().redis();
    mongo = MineCloud.instance().mongo();
    dockerClient = new DefaultDockerClient("unix:///var/run/docker.sock");

    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<ServerMetadata> 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 {
                    dockerClient.killContainer(server.containerId());
                    MineCloud.logger()
                        .info(
                            "Killed server "
                                + server.name()
                                + " with container id "
                                + server.containerId());

                    mongo.repositoryBy(Server.class).delete(server);
                  } catch (DockerException | InterruptedException 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 {
                    dockerClient.killContainer(bungee.containerId());
                    MineCloud.logger()
                        .info(
                            "Killed bungee "
                                + bungee.name()
                                + " with container id "
                                + bungee.containerId());

                    mongo.repositoryBy(Bungee.class).delete(bungee);
                  } catch (DockerException | InterruptedException 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;

                  if (server.port() != -1) return; // don't even know how this would happen

                  try {
                    ContainerInfo info = dockerClient.inspectContainer(server.containerId());

                    info.networkSettings()
                        .ports()
                        .forEach(
                            (s, l) -> {
                              if (!s.contains("25565")) return;

                              server.setPort(Integer.parseInt(l.get(0).hostPort()));
                              MineCloud.logger()
                                  .log(
                                      Level.INFO,
                                      "Set " + server.name() + "'s port to " + server.port());
                            });

                    mongo.repositoryBy(Server.class).save(server);
                  } catch (Exception e) {
                    MineCloud.logger()
                        .log(Level.SEVERE, "Was unable to set the port of a started server", e);
                  }
                }));

    redis.addChannel(SimpleRedisChannel.create("server-shutdown-notif", redis));

    new StatisticsWatcher().start();

    while (!Thread.currentThread().isInterrupted()) {
      try {
        dockerClient
            .listContainers(DockerClient.ListContainersParam.allContainers())
            .stream()
            .filter(
                (container) ->
                    !container.status().toLowerCase().contains("up")
                        && ((System.currentTimeMillis() / 1000L) - container.created()) > 5L)
            .forEach(
                (container) -> {
                  try {
                    String name =
                        container.names() == null || container.names().isEmpty()
                            ? "null"
                            : container.names().get(0);
                    dockerClient.removeContainer(container.id());

                    if (container.image().contains("minecloud")) {
                      String type = container.image().substring(9);

                      switch (type.toLowerCase()) {
                        case "bungee":
                          AbstractMongoRepository<Bungee> repository =
                              mongo.repositoryBy(Bungee.class);
                          Query<Bungee> query =
                              repository.createQuery().field("_id").equal(node().publicIp());
                          repository.deleteByQuery(query);
                          break;

                        case "server":
                          Server server =
                              mongo
                                  .repositoryBy(Server.class)
                                  .findOne("containerId", container.names().get(0));

                          if (server != null) {
                            mongo.repositoryBy(Server.class).delete(server);
                          }
                          break;
                      }
                    }

                    MineCloud.logger()
                        .info("Killed dead container " + container.id() + " (" + name + ")");
                  } catch (DockerException | InterruptedException e) {
                    MineCloud.logger()
                        .log(
                            Level.SEVERE,
                            "Was unable to kill exited container " + container.id(),
                            e);
                  }
                });
        ServerRepository repository = mongo.repositoryBy(Server.class);
        Query<Server> query =
            repository
                .createQuery()
                .field("node")
                .equal(node())
                .field("port")
                .notEqual(-1)
                .field("tps")
                .notEqual(-1);

        repository
            .find(query)
            .asList()
            .forEach(
                (server) -> {
                  boolean exists;

                  try {
                    dockerClient.inspectContainer(server.containerId());
                    exists = true;
                  } catch (DockerException | InterruptedException ex) {
                    exists = false;
                  }

                  if (exists) {
                    return;
                  }

                  repository.delete(server);
                  MineCloud.logger()
                      .info(
                          "Removed "
                              + server.containerId()
                              + " from DB due to not existing as a container");
                });
      } catch (DockerException | InterruptedException e) {
        MineCloud.logger().log(Level.SEVERE, "Was unable to list containers for update", e);
      }

      try {
        Thread.sleep(2000L);
      } catch (InterruptedException ignored) {
        // I don't care
      }
    }
  }