Archived
0

Новый модуль: MultiServer

Данный модуль разработан для работы с множеством однотипных серверов.
This commit is contained in:
2016-08-21 04:35:35 +03:00
parent 404f85ad89
commit f85459aba3
8 changed files with 813 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
/*
* DmitriyMX <mail@dmitriymx.ru>
* 2016-08-15
*/
package asys.multiserver;
import asys.api.BankObject;
import asys.api.MinecraftServerFactory;
import asys.api.ServerManager;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.ServiceTracker;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;
import static asys.api.ASysUtils.*;
public class Activator implements BundleActivator {
private ServiceTracker<?, MinecraftServerFactory> mcServerFactoryTracker;
private ServiceTracker<?, BankObject> bankObjectTracker;
private ServiceRegistration<?> commands, serverManager;
private MultiServer multiServer;
@Override
public void start(BundleContext bundleContext) throws Exception {
mcServerFactoryTracker = new ServiceTracker<>(bundleContext, MinecraftServerFactory.class.getName(), null);
mcServerFactoryTracker.open();
bankObjectTracker = new ServiceTracker<>(bundleContext, BankObject.class.getName(), null);
bankObjectTracker.open();
multiServer = new MultiServer(
loadProps(GetProperty(bundleContext, "asys.config.dir", "conf")),
mcServerFactoryTracker);
multiServer.loadState(bankObjectTracker.getService());
serverManager = bundleContext.registerService(ServerManager.class.getName(), multiServer, null);
commands = RegisterCommands(bundleContext, new Commands(multiServer), "asys.server");
}
@Override
public void stop(BundleContext bundleContext) throws Exception {
commands.unregister();
serverManager.unregister();
multiServer.saveState(bankObjectTracker.getService());
multiServer = null;
bankObjectTracker.close();
mcServerFactoryTracker.close();
}
private Properties loadProps(String confDir) {
Properties properties = new Properties();
final String propsFileName = "asys-multiserver.properties";
Path propsPath = Paths.get(confDir).resolve(propsFileName);
if (Files.notExists(propsPath)) {
try {
SaveResource(Activator.class.getResourceAsStream("/"+propsFileName), propsPath.toFile());
} catch (IOException e) {
e.printStackTrace();
return properties;
}
}
try {
properties.load(new FileReader(propsPath.toFile()));
return properties;
} catch (IOException e) {
e.printStackTrace();
}
return properties;
}
}

View File

@@ -0,0 +1,165 @@
/*
* DmitriyMX <mail@dmitriymx.ru>
* 2016-08-15
*/
package asys.multiserver;
import asys.api.ASysUtils;
import asys.api.Command;
import asys.api.MinecraftServer;
import org.apache.felix.service.command.Descriptor;
import java.io.IOException;
import java.util.Arrays;
import java.util.StringJoiner;
public class Commands {
private MultiServer multiServer;
public Commands(MultiServer multiServer) {
this.multiServer = multiServer;
}
@Command
@Descriptor("Распечатать текущие настройки модуля")
public void config() {
ASysUtils.Log("------ Config ------");
ASysUtils.Log("%-20s %s", "BuildScript folder:", multiServer.buildScriptPath.toAbsolutePath());
ASysUtils.Log("%-20s %s", "Distributive folder:", multiServer.distrPath.toAbsolutePath());
ASysUtils.Log("%-20s %s", "Servers folder:", multiServer.serversPath.toAbsolutePath());
}
@Command
@Descriptor("Развернуть новый сервер")
public void deploy(@Descriptor("тип сервера") String type) {
try {
ASysUtils.Log("New server id: %s", multiServer.deployServer(type));
} catch (IOException e) {
e.printStackTrace();
}
}
@Command
@Descriptor("Развернуть новый сервер")
public void deploy(@Descriptor("тип сервера") String type, @Descriptor("количество") int count) {
for (int i = 0; i < count; i++) {
try {
ASysUtils.Log("New server id: %s", multiServer.deployServer(type));
} catch (IOException e) {
e.printStackTrace();
break;
}
}
}
@Command
@Descriptor("Получить список серверов")
public void list() {
ASysUtils.Log("------ List servers ------");
final String format = " %-9s | %-6s";
ASysUtils.Log("%10s | %-6s", "ID", "STATUS");
multiServer.listServers().forEach(mcServer -> ASysUtils.Log(
format,
mcServer.getName(),
mcServer.isAlive() ? "Active" : "Ready"));
}
@Command
@Descriptor("Получить список серверов")
public void list(@Descriptor("статус") String status) {
ASysUtils.Log("------ List servers ------");
final String format = " %-9s | %-6s";
ASysUtils.Log("%10s | %-6s", "ID", "STATUS");
multiServer.listServers().stream()
.filter(mcServer -> ((status.equalsIgnoreCase("active") && mcServer.isAlive()) ||
(status.equalsIgnoreCase("ready") && !mcServer.isAlive())))
.forEach(mcServer -> ASysUtils.Log(
format,
mcServer.getName(),
mcServer.isAlive() ? "Active" : "Ready"));
}
@Command
@Descriptor("Получить список типов серверов")
public void types() {
ASysUtils.Log("------ Type servers ------");
multiServer.getTypes().forEach(type -> ASysUtils.Log(" %s", type));
}
@Command
@Descriptor("Старт сервера")
public void start(@Descriptor("id сервера") String serverId) {
MinecraftServer server = multiServer.getServer(serverId);
if (server == null) {
ASysUtils.Log("Server \"%s\" not found", serverId);
} else if (!server.isAlive()) {
server.start();
ASysUtils.Log("Server \"%s\" started", serverId);
}
}
@Command
@Descriptor("Остановка сервера")
public void stop(@Descriptor("id сервера") String serverId) {
MinecraftServer server = multiServer.getServer(serverId);
if (server == null) {
ASysUtils.Log("Server \"%s\" not found", serverId);
} else if (server.isAlive()) {
server.stop();
ASysUtils.Log("Server \"%s\" stoppind", serverId);
}
}
@Command
@Descriptor("Убить процесс сервера")
public void kill(@Descriptor("id сервера") String serverId) {
MinecraftServer server = multiServer.getServer(serverId);
if (server == null) {
ASysUtils.Log("Server \"%s\" not found", serverId);
} else if (server.isAlive()) {
server.forceStop();
ASysUtils.Log("Server \"%s\" killing", serverId);
}
}
@Command
@Descriptor("Отправить на сервер комманду")
public void cmd(@Descriptor("id сервера") String serverId, @Descriptor("коменда") String... command) {
MinecraftServer server = multiServer.getServer(serverId);
if (server == null) {
ASysUtils.Log("Server \"%s\" not found", serverId);
} else if (server.isAlive()) {
StringJoiner sj = new StringJoiner(" ");
Arrays.asList(command).forEach(sj::add);
server.sendCommand(sj.toString());
}
}
// @Command
@Descriptor("Получить информацию о сервере")
public void info(@Descriptor("id сервера") String serverId) {
/*TODO вывести информацию о соответствующем сервере
* информация должна содержать следующее:
* - id сервера
* - текущий онлайн
* - максимальный онлайн
* - TPS
* - политику доступа
* - время uptime
* - использумое кол-во оперативной памяти
* - максимально доступное кол-во памяти
*/
}
// @Command
@Descriptor("Установить серверу политику доступа")
public void accessPolicy(@Descriptor("id сервера") String serverid, String newPolicy) {
//TODO устанавливает соответствующему серверу политику доступа
}
// @Command
@Descriptor("Удалить сервер (физически)")
public void remove(@Descriptor("id сервера") String serverId) {
//TODO удаляет сервер с диска
}
}

View File

@@ -0,0 +1,185 @@
/*
* DmitriyMX <mail@dmitriymx.ru>
* 2016-08-15
*/
package asys.multiserver;
import asys.api.BankObject;
import asys.api.MinecraftServer;
import asys.api.MinecraftServerFactory;
import asys.api.ServerManager;
import asys.multiserver.buildscript.BuildScript;
import asys.multiserver.buildscript.CommandException;
import org.osgi.util.tracker.ServiceTracker;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
public class MultiServer implements ServerManager {
private Map<String, MinecraftServer> mapMcServers = new HashMap<>();
private Random random = new Random(System.currentTimeMillis());
private ServiceTracker<?, MinecraftServerFactory> mcsfTracker;
Path buildScriptPath, distrPath, serversPath;
public MultiServer(Properties properties, ServiceTracker<?, MinecraftServerFactory> mcServerFactoryTracker) {
buildScriptPath = Paths.get(properties.getProperty("buildscript.dir", "scripts"));
distrPath = Paths.get(properties.getProperty("distributive.dir", "distr"));
serversPath = Paths.get(properties.getProperty("servers.dir", "servers"));
this.mcsfTracker = mcServerFactoryTracker;
File[] serverDirs = serversPath.toFile().listFiles((dir, name) -> dir.isDirectory());
if (serverDirs != null) {
for(File serverDir :serverDirs) {
Path asysPropsPath = serverDir.toPath().resolve("asys.properties");
if (Files.exists(asysPropsPath)) {
Properties asysProps = new Properties();
try {
FileReader fileReader = new FileReader(asysPropsPath.toFile());
asysProps.load(fileReader);
fileReader.close();
} catch (IOException e) {
// e.printStackTrace();
return;
}
putServer(asysProps, serverDir);
}
}
}
}
public List<String> getTypes() {
List<String> list = new ArrayList<>();
String[] files = buildScriptPath.toFile().list((dir, name) -> name.indexOf(' ') == -1 && name.endsWith(".bs"));
if (files != null) {
Arrays.stream(files).map(s -> {
int i = s.lastIndexOf(".bs");
return s.substring(0, i);
}).forEach(list::add);
}
return list;
}
public String deployServer(String type) throws IOException {
String serverId;
Path newServerPath;
final int max = 99;
int _try = 0;
int nId;
do {
if (_try == max*2) throw new IOException("End of free server id");
nId = random.nextInt(max);
serverId = type + nId;
newServerPath = serversPath.resolve(serverId);
_try++;
} while (Files.exists(newServerPath));
Files.createDirectory(newServerPath);
try {
BuildScript buildScript = BuildScript.loadFromFile(buildScriptPath.resolve(type + ".bs").toFile());
buildScript.setVariable("servers", serversPath.toAbsolutePath().toString());
buildScript.setVariable("distrib", distrPath.toAbsolutePath().toString());
buildScript.setVariable("serverId", serverId);
buildScript.execute();
} catch (CommandException e) {
throw new IOException(e);
}
Properties asysProps = new Properties();
Path serverDirPath = serversPath.resolve(serverId);
Path asysPropPath = serverDirPath.resolve("asys.properties");
if (Files.exists(asysPropPath)) {
asysProps.load(new FileReader(asysPropPath.toFile()));
}
asysProps.setProperty("server.id", serverId);
if (asysProps.getProperty("server.mainjar", null) == null) {
asysProps.setProperty("server.mainjar", "spigot.jar");
}
if (asysProps.getProperty("server.port.prefix", null) == null) {
asysProps.setProperty("server.port.prefix", "00");
}
asysProps.setProperty("server.port",
"2"+asysProps.getProperty("server.port.prefix")+nId);
asysProps.store(new FileWriter(asysPropPath.toFile()), "ASys Server settings");
putServer(asysProps, serverDirPath.toFile());
return serverId;
}
private void putServer(Properties asysProps, File serverDir) {
if (asysProps.getProperty("server.id", null) == null)
return; //TODO ошибку бы генерировать
if (asysProps.getProperty("server.port", null) == null)
return; //TODO ошибку бы генерировать
MinecraftServerFactory serverFactory = null;
try {
serverFactory = mcsfTracker.waitForService(1000L);
} catch (InterruptedException e) {
}
if (serverFactory == null)
return; //TODO ошибку бы генерировать
mapMcServers.put(
asysProps.getProperty("server.id"),
serverFactory.createServer(
asysProps.getProperty("server.id"),
serverDir,
asysProps.getProperty("server.mainjar"),
Short.valueOf(asysProps.getProperty("server.port")), //TODO надо сделать защиту от дурака: что если буквы введут?
asysProps.getProperty("server.jvm.args"),
asysProps.getProperty("server.params")
));
}
public List<MinecraftServer> listServers() {
return new ArrayList<>(mapMcServers.values());
}
@Override
public MinecraftServer getServer(String serverId) {
return mapMcServers.get(serverId); //TODO по хорошему, надо бы возвращать какой-нибудь EmptyPbject, а не null
}
@Override
public void removeServer(String serverId) {
mapMcServers.remove(serverId);
}
@SuppressWarnings("unchecked")
void loadState(BankObject bankObject) {
if(bankObject == null) return;
List<MinecraftServer> serversState = (List<MinecraftServer>) bankObject.get(MultiServer.class.getName()+"#servers");
if (serversState == null) return;
serversState.forEach(server -> {
if (server.isAlive()) {
mapMcServers.put(server.getName(), server);
}
});
}
void saveState(BankObject bankObject) {
if(bankObject == null) return;
List<MinecraftServer> serversState = new ArrayList<>();
mapMcServers.values().forEach(server -> {
if (server.isAlive()) {
serversState.add(server);
}
});
bankObject.save(MultiServer.class.getName()+"#servers", serversState);
}
}

View File

@@ -0,0 +1,277 @@
/*
* DmitriyMX <mail@dmitriymx.ru>
* 2016-06-23
*/
package asys.multiserver.buildscript;
import asys.api.ASysUtils;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* Скрипт формирования окружения игрового сервера.<br><br>
*
* Команды:<br>
* - <code>lnk [источник] [цель]</code> - создание ссылки;<br>
* - <code>copy [источник] [цель]</code> - копирование;<br>
* - <code>mkdir [путь]</code> - создание папки или древа папок;<br>
* - <code>unpack [архив] [папка]</code> - распаковка архива в указанную папку;<br>
* - <code>undoparent</code> - отменить запланированные действия вышестоящего скрипта.
*/
public class BuildScript {
List<String[]> scriptLines = new ArrayList<>();
private Map<String, String> variables = new HashMap<>();
public static BuildScript loadFromFile(File file) throws IOException, UnknowCommandException {
return new BuildScript(FileUtils.readFileToString(file, Charset.forName("UTF-8")));
}
public BuildScript(String script) throws UnknowCommandException {
if (script == null || script.trim().isEmpty()) {
ASysUtils.Log("[WARN] Empty script!");
return;
}
if (script.contains("\r\n")) {
// ASysUtils.Log("[WARN] Finded CRLF! Replaced...");
script = script.replaceAll("\r\n", "\n");
}
for (String line : script.split("\n")) {
scriptLines.add(parseCommandLine(line));
}
}
public void setVariables(Map<String, String> variables) {
this.variables = variables;
}
public void setVariable(String name, String value) {
this.variables.put(name, value);
}
private String[] parseCommandLine(String line) throws UnknowCommandException {
final StringTokenizer strTok = new StringTokenizer(line, " \"\'", true);
final List<String> preArr = new ArrayList<>();
byte state = 0; // 0-normal, 1-quoting1, 2-quoting2
String buff = "";
boolean foundCmd = false;
while (strTok.hasMoreTokens()) {
String partLine = strTok.nextToken();
if (!foundCmd) {
switch (partLine) {
case "lnk":
case "copy":
case "mkdir":
case "unpack":
case "undoparent":
foundCmd = true; // cmd correct;
partLine = partLine.toLowerCase();
break;
default:
foundCmd = false; // cmd correct;
}
if (!foundCmd) {
throw new UnknowCommandException(partLine);
}
}
if (partLine.equals("\"")) {
if (state == 0) {
state = 1;
continue;
} else if (state == 1){
state = 0;
preArr.add(buff);
buff = "";
continue;
}
}
if (partLine.equals("\'")) {
if (state == 0) {
state = 2;
continue;
} else if (state == 2){
state = 0;
preArr.add(buff);
buff = "";
continue;
}
}
if (state == 1 || state == 2) {
buff += partLine;
continue;
}
if (partLine.equals(" ")) continue;
preArr.add(partLine);
}
return preArr.toArray(new String[preArr.size()]);
}
public void execute() throws CommandException {
for (String[] cmdline : scriptLines) {
switch (cmdline[0]) {
case "lnk": cmd_lnk(cmdline[1], cmdline[2]); break;
case "copy": cmd_copy(cmdline[1], cmdline[2]); break;
case "mkdir": cmd_mkdir(cmdline[1]); break;
case "unpack": cmd_unpack(cmdline[1], cmdline[2]); break;
case "undoparent": cmd_undo(); break;
default: return;
}
}
}
private String applyVariables(String string) {
if (variables != null && !variables.isEmpty()) {
for (Map.Entry<String, String> entry : variables.entrySet()) {
string = string.replace("%"+entry.getKey()+"%", entry.getValue());
}
}
return string;
}
/* Создание символьной ссылки */
private void cmd_lnk(String source, String target) throws CommandException {
try {
Files.createSymbolicLink(
Paths.get(applyVariables(target)),
Paths.get(applyVariables(source)));
} catch (IOException e) {
throw new CommandException("LNK", e);
}
}
/* Коирование */
private void cmd_copy(String source, String target) throws CommandException {
//TODO надо изюавиться от излишних Path.toFile()
Path sourcePath = Paths.get(applyVariables(source));
if (!sourcePath.toFile().exists()) {
throw new CommandException(String.format("COPY: source not found %s [%s]", source, sourcePath.toAbsolutePath()));
}
Path targetPath = Paths.get(applyVariables(target));
if (!targetPath.toFile().exists()) {
if (sourcePath.toFile().isDirectory()) {
try {
FileUtils.copyDirectory(sourcePath.toFile(), targetPath.toFile());
} catch (IOException e) {
throw new CommandException("COPY: error copy directory", e);
}
} else if (sourcePath.toFile().isFile()) {
try {
FileUtils.copyFile(sourcePath.toFile(), targetPath.toFile());
} catch (IOException e) {
throw new CommandException("COPY: error copy file", e);
}
} else {
throw new CommandException("COPY: unknow type source file");
}
} else {
if (sourcePath.toFile().isDirectory()) {
if (targetPath.toFile().isFile()) {
throw new CommandException("COPY: can't be copy dir to file");
} else {
try {
FileUtils.copyDirectory(sourcePath.toFile(), targetPath.toFile());
} catch (IOException e) {
throw new CommandException("COPY: error copy directory", e);
}
}
} else if (sourcePath.toFile().isFile()) {
if (targetPath.toFile().isDirectory()) {
try {
FileUtils.copyFileToDirectory(sourcePath.toFile(), targetPath.toFile());
} catch (IOException e) {
throw new CommandException("COPY: error copy file", e);
}
} else if (targetPath.toFile().isFile()) {
try {
FileUtils.copyFile(sourcePath.toFile(), targetPath.toFile());
} catch (IOException e) {
throw new CommandException("COPY: error copy file", e);
}
}
}
}
}
/* Создание папки/древа папок
* параметры: цель */
private void cmd_mkdir(String dir) throws CommandException {
File treeDir = Paths.get(applyVariables(dir)).toFile();
if (treeDir.mkdirs() && !treeDir.exists()) {
throw new CommandException(String.format("MKDIR: can't create dirs %s [%s]", dir, treeDir.getAbsolutePath()));
}
}
private void cmd_unpack(String source, String target) throws CommandException {
Path sourcePath = Paths.get(applyVariables(source));
if (!sourcePath.toFile().exists()) {
throw new CommandException(String.format("UNPACK: source not found %s [%s]", source, sourcePath.toFile().getAbsolutePath()));
}
if (!sourcePath.toFile().isFile()) {
throw new CommandException(String.format("UNPACK: source is not file %s [%s]", source, sourcePath.toFile().getAbsolutePath()));
}
Path targetPath = Paths.get(applyVariables(target));
if (!targetPath.toFile().exists()) {
if (targetPath.toFile().mkdirs() && !targetPath.toFile().exists()) {
throw new CommandException(String.format("UNPACK: can't create dir %s [%s]", target, targetPath.toFile().getAbsolutePath()));
}
} else if (targetPath.toFile().exists() && targetPath.toFile().isFile()) {
throw new CommandException(String.format("UNPACK: target can't be file %s [%s]", target, targetPath.toFile().getAbsolutePath()));
}
// unzip
byte[] buffer = new byte[65536];
try {
ZipInputStream zis = new ZipInputStream(new FileInputStream(sourcePath.toFile()));
ZipEntry ze;
while ((ze = zis.getNextEntry()) != null) {
if (ze.isDirectory()) continue;
String fileName = ze.getName();
File newFile = new File(targetPath.toFile(), fileName);
Files.createDirectories(Paths.get(newFile.getParent()));
FileOutputStream fos = new FileOutputStream(newFile);
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
fos.close();
}
zis.closeEntry();
zis.close();
} catch (IOException e) {
throw new CommandException("UNPACK", e);
}
}
private void cmd_undo() {
// empty
}
}

View File

@@ -0,0 +1,15 @@
/*
* DmitriyMX <mail@dmitriymx.ru>
* 2016-06-28
*/
package asys.multiserver.buildscript;
public class CommandException extends Exception {
CommandException(String message) {
super(message);
}
CommandException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,11 @@
/*
* DmitriyMX <mail@dmitriymx.ru>
* 2016-06-23
*/
package asys.multiserver.buildscript;
public class UnknowCommandException extends CommandException {
UnknowCommandException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,3 @@
buildscript.dir=scripts
distributive.dir=distr
servers.dir=servers