3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-09-29 14:40:21 +02:00

Merge pull request #139 from VelocityPowered/reload-command

Reload command
Dieser Commit ist enthalten in:
Andrew Steinborn 2018-12-01 18:04:09 -05:00 committet von GitHub
Commit a3bfa292c6
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 4AEE18F83AFDEB23
6 geänderte Dateien mit 167 neuen und 49 gelöschten Zeilen

Datei anzeigen

@ -0,0 +1,12 @@
package com.velocitypowered.api.event.proxy;
/**
* This event is fired when the proxy is reloaded by the user using {@code /velocity reload}.
*/
public class ProxyReloadEvent {
@Override
public String toString() {
return "ProxyReloadEvent";
}
}

Datei anzeigen

@ -14,7 +14,7 @@ allprojects {
junitVersion = '5.3.0-M1' junitVersion = '5.3.0-M1'
slf4jVersion = '1.7.25' slf4jVersion = '1.7.25'
log4jVersion = '2.11.1' log4jVersion = '2.11.1'
nettyVersion = '4.1.30.Final' nettyVersion = '4.1.32.Final'
guavaVersion = '25.1-jre' guavaVersion = '25.1-jre'
checkerFrameworkVersion = '2.5.6' checkerFrameworkVersion = '2.5.6'

Datei anzeigen

@ -7,6 +7,7 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.velocitypowered.api.event.EventManager; import com.velocitypowered.api.event.EventManager;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyReloadEvent;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginManager; import com.velocitypowered.api.plugin.PluginManager;
@ -40,17 +41,20 @@ import com.velocitypowered.proxy.util.VelocityChannelRegistrar;
import com.velocitypowered.proxy.util.ratelimit.Ratelimiter; import com.velocitypowered.proxy.util.ratelimit.Ratelimiter;
import com.velocitypowered.proxy.util.ratelimit.Ratelimiters; import com.velocitypowered.proxy.util.ratelimit.Ratelimiters;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.security.KeyPair; import java.security.KeyPair;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import net.kyori.text.Component; import net.kyori.text.Component;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
@ -70,26 +74,32 @@ public class VelocityServer implements ProxyServer {
.registerTypeHierarchyAdapter(GameProfile.class, new GameProfileSerializer()) .registerTypeHierarchyAdapter(GameProfile.class, new GameProfileSerializer())
.create(); .create();
private final ConnectionManager cm;
private final ProxyOptions options; private final ProxyOptions options;
private @MonotonicNonNull ConnectionManager cm;
private @MonotonicNonNull VelocityConfiguration configuration; private @MonotonicNonNull VelocityConfiguration configuration;
private @MonotonicNonNull NettyHttpClient httpClient; private @MonotonicNonNull NettyHttpClient httpClient;
private @MonotonicNonNull KeyPair serverKeyPair; private @MonotonicNonNull KeyPair serverKeyPair;
private @MonotonicNonNull ServerMap servers; private final ServerMap servers;
private final VelocityCommandManager commandManager = new VelocityCommandManager(); private final VelocityCommandManager commandManager = new VelocityCommandManager();
private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false); private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false);
private boolean shutdown = false; private boolean shutdown = false;
private @MonotonicNonNull VelocityPluginManager pluginManager; private final VelocityPluginManager pluginManager;
private final Map<UUID, ConnectedPlayer> connectionsByUuid = new ConcurrentHashMap<>(); private final Map<UUID, ConnectedPlayer> connectionsByUuid = new ConcurrentHashMap<>();
private final Map<String, ConnectedPlayer> connectionsByName = new ConcurrentHashMap<>(); private final Map<String, ConnectedPlayer> connectionsByName = new ConcurrentHashMap<>();
private @MonotonicNonNull VelocityConsole console; private final VelocityConsole console;
private @MonotonicNonNull Ratelimiter ipAttemptLimiter; private @MonotonicNonNull Ratelimiter ipAttemptLimiter;
private @MonotonicNonNull VelocityEventManager eventManager; private final VelocityEventManager eventManager;
private @MonotonicNonNull VelocityScheduler scheduler; private final VelocityScheduler scheduler;
private final VelocityChannelRegistrar channelRegistrar = new VelocityChannelRegistrar(); private final VelocityChannelRegistrar channelRegistrar = new VelocityChannelRegistrar();
public VelocityServer(final ProxyOptions options) { VelocityServer(final ProxyOptions options) {
pluginManager = new VelocityPluginManager(this);
eventManager = new VelocityEventManager(pluginManager);
scheduler = new VelocityScheduler(pluginManager);
console = new VelocityConsole(this);
cm = new ConnectionManager(this);
servers = new ServerMap(this);
this.options = options; this.options = options;
} }
@ -138,12 +148,6 @@ public class VelocityServer implements ProxyServer {
logger.info("Booting up {} {}...", getVersion().getName(), getVersion().getVersion()); logger.info("Booting up {} {}...", getVersion().getName(), getVersion().getVersion());
serverKeyPair = EncryptionUtils.createRsaKeyPair(1024); serverKeyPair = EncryptionUtils.createRsaKeyPair(1024);
pluginManager = new VelocityPluginManager(this);
eventManager = new VelocityEventManager(pluginManager);
scheduler = new VelocityScheduler(pluginManager);
console = new VelocityConsole(this);
cm = new ConnectionManager(this);
servers = new ServerMap(this);
cm.logChannelInformation(); cm.logChannelInformation();
@ -233,9 +237,6 @@ public class VelocityServer implements ProxyServer {
} }
public Bootstrap initializeGenericBootstrap() { public Bootstrap initializeGenericBootstrap() {
if (cm == null) {
throw new IllegalStateException("Server did not initialize properly.");
}
return this.cm.createWorker(); return this.cm.createWorker();
} }
@ -243,6 +244,87 @@ public class VelocityServer implements ProxyServer {
return shutdown; return shutdown;
} }
public boolean reloadConfiguration() throws IOException {
Path configPath = Paths.get("velocity.toml");
VelocityConfiguration newConfiguration = VelocityConfiguration.read(configPath);
if (!newConfiguration.validate()) {
return false;
}
// Re-register servers. If a server is being replaced, make sure to note what players need to
// move back to a fallback server.
Collection<ConnectedPlayer> evacuate = new ArrayList<>();
for (Map.Entry<String, String> entry : newConfiguration.getServers().entrySet()) {
ServerInfo newInfo =
new ServerInfo(entry.getKey(), AddressUtil.parseAddress(entry.getValue()));
Optional<RegisteredServer> rs = servers.getServer(entry.getKey());
if (!rs.isPresent()) {
servers.register(newInfo);
} else if (!rs.get().getServerInfo().equals(newInfo)) {
for (Player player : rs.get().getPlayersConnected()) {
if (!(player instanceof ConnectedPlayer)) {
throw new IllegalStateException("ConnectedPlayer not found for player " + player
+ " in server " + rs.get().getServerInfo().getName());
}
evacuate.add((ConnectedPlayer) player);
}
servers.unregister(rs.get().getServerInfo());
servers.register(newInfo);
}
}
// If we had any players to evacuate, let's move them now. Wait until they are all moved off.
if (!evacuate.isEmpty()) {
CountDownLatch latch = new CountDownLatch(evacuate.size());
for (ConnectedPlayer player : evacuate) {
Optional<RegisteredServer> next = player.getNextServerToTry();
if (next.isPresent()) {
player.createConnectionRequest(next.get()).connectWithIndication()
.whenComplete((success, ex) -> {
if (ex != null || success == null || !success) {
player.disconnect(TextComponent.of("Your server has been changed, but we could "
+ "not move you to any fallback servers."));
}
latch.countDown();
});
} else {
latch.countDown();
player.disconnect(TextComponent.of("Your server has been changed, but we could "
+ "not move you to any fallback servers."));
}
}
try {
latch.await();
} catch (InterruptedException e) {
logger.error("Interrupted whilst moving players", e);
Thread.currentThread().interrupt();
}
}
// If we have a new bind address, bind to it
if (!configuration.getBind().equals(newConfiguration.getBind())) {
this.cm.bind(newConfiguration.getBind());
this.cm.close(configuration.getBind());
}
if (configuration.isQueryEnabled() && (!newConfiguration.isQueryEnabled()
|| newConfiguration.getQueryPort() != configuration.getQueryPort())) {
this.cm.close(new InetSocketAddress(
configuration.getBind().getHostString(), configuration.getQueryPort()));
}
if (newConfiguration.isQueryEnabled()) {
this.cm.queryBind(newConfiguration.getBind().getHostString(),
newConfiguration.getQueryPort());
}
ipAttemptLimiter = Ratelimiters.createWithMilliseconds(newConfiguration.getLoginRatelimit());
this.configuration = newConfiguration;
eventManager.fireAndForget(new ProxyReloadEvent());
return true;
}
public void shutdown(boolean explicitExit) { public void shutdown(boolean explicitExit) {
if (eventManager == null || pluginManager == null || cm == null || scheduler == null) { if (eventManager == null || pluginManager == null || cm == null || scheduler == null) {
throw new AssertionError(); throw new AssertionError();
@ -340,66 +422,41 @@ public class VelocityServer implements ProxyServer {
@Override @Override
public Optional<RegisteredServer> getServer(String name) { public Optional<RegisteredServer> getServer(String name) {
Preconditions.checkNotNull(name, "name");
if (servers == null) {
throw new IllegalStateException("Server did not initialize properly.");
}
return servers.getServer(name); return servers.getServer(name);
} }
@Override @Override
public Collection<RegisteredServer> getAllServers() { public Collection<RegisteredServer> getAllServers() {
if (servers == null) {
throw new IllegalStateException("Server did not initialize properly.");
}
return servers.getAllServers(); return servers.getAllServers();
} }
@Override @Override
public RegisteredServer registerServer(ServerInfo server) { public RegisteredServer registerServer(ServerInfo server) {
if (servers == null) {
throw new IllegalStateException("Server did not initialize properly.");
}
return servers.register(server); return servers.register(server);
} }
@Override @Override
public void unregisterServer(ServerInfo server) { public void unregisterServer(ServerInfo server) {
if (servers == null) {
throw new IllegalStateException("Server did not initialize properly.");
}
servers.unregister(server); servers.unregister(server);
} }
@Override @Override
public VelocityConsole getConsoleCommandSource() { public VelocityConsole getConsoleCommandSource() {
if (console == null) {
throw new IllegalStateException("Server did not initialize properly.");
}
return console; return console;
} }
@Override @Override
public PluginManager getPluginManager() { public PluginManager getPluginManager() {
if (pluginManager == null) {
throw new IllegalStateException("Server did not initialize properly.");
}
return pluginManager; return pluginManager;
} }
@Override @Override
public EventManager getEventManager() { public EventManager getEventManager() {
if (eventManager == null) {
throw new IllegalStateException("Server did not initialize properly.");
}
return eventManager; return eventManager;
} }
@Override @Override
public VelocityScheduler getScheduler() { public VelocityScheduler getScheduler() {
if (scheduler == null) {
throw new IllegalStateException("Server did not initialize properly.");
}
return scheduler; return scheduler;
} }

Datei anzeigen

@ -10,6 +10,8 @@ import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription; import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.util.ProxyVersion; import com.velocitypowered.api.util.ProxyVersion;
import com.velocitypowered.proxy.VelocityServer;
import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -22,6 +24,8 @@ import net.kyori.text.event.HoverEvent;
import net.kyori.text.event.HoverEvent.Action; import net.kyori.text.event.HoverEvent.Action;
import net.kyori.text.format.TextColor; import net.kyori.text.format.TextColor;
import net.kyori.text.format.TextDecoration; import net.kyori.text.format.TextDecoration;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
public class VelocityCommand implements Command { public class VelocityCommand implements Command {
@ -32,10 +36,11 @@ public class VelocityCommand implements Command {
* Initializes the command object for /velocity. * Initializes the command object for /velocity.
* @param server the Velocity server * @param server the Velocity server
*/ */
public VelocityCommand(ProxyServer server) { public VelocityCommand(VelocityServer server) {
this.subcommands = ImmutableMap.<String, Command>builder() this.subcommands = ImmutableMap.<String, Command>builder()
.put("version", new Info(server)) .put("version", new Info(server))
.put("plugins", new Plugins(server)) .put("plugins", new Plugins(server))
.put("reload", new Reload(server))
.build(); .build();
} }
@ -103,6 +108,39 @@ public class VelocityCommand implements Command {
return command.hasPermission(source, actualArgs); return command.hasPermission(source, actualArgs);
} }
private static class Reload implements Command {
private static final Logger logger = LogManager.getLogger(Reload.class);
private final VelocityServer server;
private Reload(VelocityServer server) {
this.server = server;
}
@Override
public void execute(CommandSource source, String @NonNull [] args) {
try {
if (server.reloadConfiguration()) {
source.sendMessage(TextComponent.of("Configuration reloaded.", TextColor.GREEN));
} else {
source.sendMessage(TextComponent.of(
"Unable to reload your configuration. Check the console for more details.",
TextColor.RED));
}
} catch (Exception e) {
logger.error("Unable to reload configuration", e);
source.sendMessage(TextComponent.of(
"Unable to reload your configuration. Check the console for more details.",
TextColor.RED));
}
}
@Override
public boolean hasPermission(CommandSource source, String @NonNull [] args) {
return source.getPermissionValue("velocity.command.reload") == Tristate.TRUE;
}
}
private static class Info implements Command { private static class Info implements Command {
private final ProxyServer server; private final ProxyServer server;

Datei anzeigen

@ -383,7 +383,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
} }
} }
Optional<RegisteredServer> getNextServerToTry() { public Optional<RegisteredServer> getNextServerToTry() {
if (serversToTry == null) { if (serversToTry == null) {
String virtualHostStr = getVirtualHost().map(InetSocketAddress::getHostString).orElse(""); String virtualHostStr = getVirtualHost().map(InetSocketAddress::getHostString).orElse("");
serversToTry = server.getConfiguration().getForcedHosts() serversToTry = server.getConfiguration().getForcedHosts()

Datei anzeigen

@ -1,5 +1,6 @@
package com.velocitypowered.proxy.network; package com.velocitypowered.proxy.network;
import com.google.common.base.Preconditions;
import com.velocitypowered.natives.util.Natives; import com.velocitypowered.natives.util.Natives;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.protocol.netty.GS4QueryHandler; import com.velocitypowered.proxy.protocol.netty.GS4QueryHandler;
@ -11,7 +12,9 @@ import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
import io.netty.channel.WriteBufferWaterMark; import io.netty.channel.WriteBufferWaterMark;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -21,7 +24,7 @@ public final class ConnectionManager {
private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 16, private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 16,
1 << 18); 1 << 18);
private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class); private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class);
private final Set<Channel> endpoints = new HashSet<>(); private final Map<InetSocketAddress, Channel> endpoints = new HashMap<>();
private final TransportType transportType; private final TransportType transportType;
private final EventLoopGroup bossGroup; private final EventLoopGroup bossGroup;
private final EventLoopGroup workerGroup; private final EventLoopGroup workerGroup;
@ -55,7 +58,7 @@ public final class ConnectionManager {
.addListener((ChannelFutureListener) future -> { .addListener((ChannelFutureListener) future -> {
final Channel channel = future.channel(); final Channel channel = future.channel();
if (future.isSuccess()) { if (future.isSuccess()) {
this.endpoints.add(channel); this.endpoints.put(address, channel);
LOGGER.info("Listening on {}", channel.localAddress()); LOGGER.info("Listening on {}", channel.localAddress());
} else { } else {
LOGGER.error("Can't bind to {}", address, future.cause()); LOGGER.error("Can't bind to {}", address, future.cause());
@ -64,16 +67,17 @@ public final class ConnectionManager {
} }
public void queryBind(final String hostname, final int port) { public void queryBind(final String hostname, final int port) {
InetSocketAddress address = new InetSocketAddress(hostname, port);
final Bootstrap bootstrap = new Bootstrap() final Bootstrap bootstrap = new Bootstrap()
.channel(this.transportType.datagramChannelClass) .channel(this.transportType.datagramChannelClass)
.group(this.workerGroup) .group(this.workerGroup)
.handler(new GS4QueryHandler(this.server)) .handler(new GS4QueryHandler(this.server))
.localAddress(hostname, port); .localAddress(address);
bootstrap.bind() bootstrap.bind()
.addListener((ChannelFutureListener) future -> { .addListener((ChannelFutureListener) future -> {
final Channel channel = future.channel(); final Channel channel = future.channel();
if (future.isSuccess()) { if (future.isSuccess()) {
this.endpoints.add(channel); this.endpoints.put(address, channel);
LOGGER.info("Listening for GS4 query on {}", channel.localAddress()); LOGGER.info("Listening for GS4 query on {}", channel.localAddress());
} else { } else {
LOGGER.error("Can't bind to {}", bootstrap.config().localAddress(), future.cause()); LOGGER.error("Can't bind to {}", bootstrap.config().localAddress(), future.cause());
@ -90,8 +94,15 @@ public final class ConnectionManager {
this.server.getConfiguration().getConnectTimeout()); this.server.getConfiguration().getConnectTimeout());
} }
public void close(InetSocketAddress oldBind) {
Channel serverChannel = endpoints.remove(oldBind);
Preconditions.checkState(serverChannel != null, "Endpoint %s not registered", oldBind);
LOGGER.info("Closing endpoint {}", serverChannel.localAddress());
serverChannel.close().syncUninterruptibly();
}
public void shutdown() { public void shutdown() {
for (final Channel endpoint : this.endpoints) { for (final Channel endpoint : this.endpoints.values()) {
try { try {
LOGGER.info("Closing endpoint {}", endpoint.localAddress()); LOGGER.info("Closing endpoint {}", endpoint.localAddress());
endpoint.close().sync(); endpoint.close().sync();