geforkt von Mirrors/Velocity
Merge pull request #139 from VelocityPowered/reload-command
Reload command
Dieser Commit ist enthalten in:
Commit
a3bfa292c6
@ -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";
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ allprojects {
|
||||
junitVersion = '5.3.0-M1'
|
||||
slf4jVersion = '1.7.25'
|
||||
log4jVersion = '2.11.1'
|
||||
nettyVersion = '4.1.30.Final'
|
||||
nettyVersion = '4.1.32.Final'
|
||||
guavaVersion = '25.1-jre'
|
||||
checkerFrameworkVersion = '2.5.6'
|
||||
|
||||
|
@ -7,6 +7,7 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.velocitypowered.api.event.EventManager;
|
||||
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.plugin.PluginContainer;
|
||||
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.Ratelimiters;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.KeyPair;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.TextComponent;
|
||||
@ -70,26 +74,32 @@ public class VelocityServer implements ProxyServer {
|
||||
.registerTypeHierarchyAdapter(GameProfile.class, new GameProfileSerializer())
|
||||
.create();
|
||||
|
||||
private final ConnectionManager cm;
|
||||
private final ProxyOptions options;
|
||||
private @MonotonicNonNull ConnectionManager cm;
|
||||
private @MonotonicNonNull VelocityConfiguration configuration;
|
||||
private @MonotonicNonNull NettyHttpClient httpClient;
|
||||
private @MonotonicNonNull KeyPair serverKeyPair;
|
||||
private @MonotonicNonNull ServerMap servers;
|
||||
private final ServerMap servers;
|
||||
private final VelocityCommandManager commandManager = new VelocityCommandManager();
|
||||
private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false);
|
||||
private boolean shutdown = false;
|
||||
private @MonotonicNonNull VelocityPluginManager pluginManager;
|
||||
private final VelocityPluginManager pluginManager;
|
||||
|
||||
private final Map<UUID, ConnectedPlayer> connectionsByUuid = new ConcurrentHashMap<>();
|
||||
private final Map<String, ConnectedPlayer> connectionsByName = new ConcurrentHashMap<>();
|
||||
private @MonotonicNonNull VelocityConsole console;
|
||||
private final VelocityConsole console;
|
||||
private @MonotonicNonNull Ratelimiter ipAttemptLimiter;
|
||||
private @MonotonicNonNull VelocityEventManager eventManager;
|
||||
private @MonotonicNonNull VelocityScheduler scheduler;
|
||||
private final VelocityEventManager eventManager;
|
||||
private final VelocityScheduler scheduler;
|
||||
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;
|
||||
}
|
||||
|
||||
@ -138,12 +148,6 @@ public class VelocityServer implements ProxyServer {
|
||||
logger.info("Booting up {} {}...", getVersion().getName(), getVersion().getVersion());
|
||||
|
||||
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();
|
||||
|
||||
@ -233,9 +237,6 @@ public class VelocityServer implements ProxyServer {
|
||||
}
|
||||
|
||||
public Bootstrap initializeGenericBootstrap() {
|
||||
if (cm == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return this.cm.createWorker();
|
||||
}
|
||||
|
||||
@ -243,6 +244,87 @@ public class VelocityServer implements ProxyServer {
|
||||
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) {
|
||||
if (eventManager == null || pluginManager == null || cm == null || scheduler == null) {
|
||||
throw new AssertionError();
|
||||
@ -340,66 +422,41 @@ public class VelocityServer implements ProxyServer {
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<RegisteredServer> getAllServers() {
|
||||
if (servers == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return servers.getAllServers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegisteredServer registerServer(ServerInfo server) {
|
||||
if (servers == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return servers.register(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterServer(ServerInfo server) {
|
||||
if (servers == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
servers.unregister(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VelocityConsole getConsoleCommandSource() {
|
||||
if (console == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return console;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginManager getPluginManager() {
|
||||
if (pluginManager == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return pluginManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventManager getEventManager() {
|
||||
if (eventManager == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return eventManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VelocityScheduler getScheduler() {
|
||||
if (scheduler == null) {
|
||||
throw new IllegalStateException("Server did not initialize properly.");
|
||||
}
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,8 @@ import com.velocitypowered.api.plugin.PluginContainer;
|
||||
import com.velocitypowered.api.plugin.PluginDescription;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.util.ProxyVersion;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
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.format.TextColor;
|
||||
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;
|
||||
|
||||
public class VelocityCommand implements Command {
|
||||
@ -32,10 +36,11 @@ public class VelocityCommand implements Command {
|
||||
* Initializes the command object for /velocity.
|
||||
* @param server the Velocity server
|
||||
*/
|
||||
public VelocityCommand(ProxyServer server) {
|
||||
public VelocityCommand(VelocityServer server) {
|
||||
this.subcommands = ImmutableMap.<String, Command>builder()
|
||||
.put("version", new Info(server))
|
||||
.put("plugins", new Plugins(server))
|
||||
.put("reload", new Reload(server))
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -103,6 +108,39 @@ public class VelocityCommand implements Command {
|
||||
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 final ProxyServer server;
|
||||
|
@ -383,7 +383,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
}
|
||||
}
|
||||
|
||||
Optional<RegisteredServer> getNextServerToTry() {
|
||||
public Optional<RegisteredServer> getNextServerToTry() {
|
||||
if (serversToTry == null) {
|
||||
String virtualHostStr = getVirtualHost().map(InetSocketAddress::getHostString).orElse("");
|
||||
serversToTry = server.getConfiguration().getForcedHosts()
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.velocitypowered.proxy.network;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.natives.util.Natives;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
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.WriteBufferWaterMark;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
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,
|
||||
1 << 18);
|
||||
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 EventLoopGroup bossGroup;
|
||||
private final EventLoopGroup workerGroup;
|
||||
@ -55,7 +58,7 @@ public final class ConnectionManager {
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
final Channel channel = future.channel();
|
||||
if (future.isSuccess()) {
|
||||
this.endpoints.add(channel);
|
||||
this.endpoints.put(address, channel);
|
||||
LOGGER.info("Listening on {}", channel.localAddress());
|
||||
} else {
|
||||
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) {
|
||||
InetSocketAddress address = new InetSocketAddress(hostname, port);
|
||||
final Bootstrap bootstrap = new Bootstrap()
|
||||
.channel(this.transportType.datagramChannelClass)
|
||||
.group(this.workerGroup)
|
||||
.handler(new GS4QueryHandler(this.server))
|
||||
.localAddress(hostname, port);
|
||||
.localAddress(address);
|
||||
bootstrap.bind()
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
final Channel channel = future.channel();
|
||||
if (future.isSuccess()) {
|
||||
this.endpoints.add(channel);
|
||||
this.endpoints.put(address, channel);
|
||||
LOGGER.info("Listening for GS4 query on {}", channel.localAddress());
|
||||
} else {
|
||||
LOGGER.error("Can't bind to {}", bootstrap.config().localAddress(), future.cause());
|
||||
@ -90,8 +94,15 @@ public final class ConnectionManager {
|
||||
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() {
|
||||
for (final Channel endpoint : this.endpoints) {
|
||||
for (final Channel endpoint : this.endpoints.values()) {
|
||||
try {
|
||||
LOGGER.info("Closing endpoint {}", endpoint.localAddress());
|
||||
endpoint.close().sync();
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren