Skip to content

Commit

Permalink
Multi proxy support (#2968)
Browse files Browse the repository at this point in the history
- Change proxy server info storage to allow multiple proxies to exist in the database
- Allow naming Proxies with Server.Name config setting
- Server.IP is no longer required to be set during installation
- Change logic reliant on single proxy server to consider multiple proxies
  - If multiple proxies have webserver or export enabled, the address given by commands can be any of them.
- Network players online graph now stacks if redisbungee is not used. Individual players online graphs for different proxies can be viewed from Performance tab.
- Last Peak and All Time Peak are not given for multi-proxy networks without redisbungee since it would be expensive to calculate.
- Improved network performance tab considerably
- /plan info displays the Server UUID of the current server
- /plan server link goes to /server/UUID instead of /server/Name
- Fix join address graphs not loading if strict GROUP BY is enabled in MySQL

Affects issues:
- Close #1454
  • Loading branch information
AuroraLS3 authored Apr 9, 2023
1 parent f43d8f8 commit f7cec19
Show file tree
Hide file tree
Showing 43 changed files with 649 additions and 258 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,9 @@ public int getOnlinePlayerCount() {
public List<String> getOnlinePlayerNames() {
return getPlayers.get().stream().map(ProxiedPlayer::getName).collect(Collectors.toList());
}

@Override
public boolean usingRedisBungee() {
return RedisCheck.isClassAvailable();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.djrapitops.plan.settings.locale.Locale;
import com.djrapitops.plan.settings.locale.lang.PluginLang;
import net.playeranalytics.plugin.server.PluginLogger;
import org.jetbrains.annotations.Nullable;

import javax.inject.Inject;
import javax.inject.Named;
Expand Down Expand Up @@ -73,17 +74,16 @@ public BungeeServerInfo(
@Override
public void loadServerInfo() {
logger.info(locale.getString(PluginLang.LOADING_SERVER_INFO));
checkIfDefaultIP();

this.server = fromFile.load(null).orElseGet(() -> fromDatabase.load(null)
.orElseGet(this::registerServer));
this.server = fromFile.load(null)
.orElseGet(this::registerServer);
this.server.setProxy(true); // Ensure isProxy if loaded from file

processing.submitNonCritical(this::updateStorage);
}

private void updateStorage() {
String address = addresses.getAccessAddress().orElseGet(addresses::getFallbackLocalhostAddress);
String address = getAddress();

server.setWebAddress(address);

Expand All @@ -92,38 +92,29 @@ private void updateStorage() {
fromFile.save(server);
}

/**
* @throws EnableException If IP setting is unset
*/
private void checkIfDefaultIP() {
String ip = serverProperties.getIp();
if ("0.0.0.0".equals(ip)) {
logger.error("IP setting still 0.0.0.0 - Configure Alternative_IP/IP that connects to the Proxy server.");
logger.info("Player Analytics partially enabled (Use /planproxy reload to reload config)");
throw new EnableException("IP setting still 0.0.0.0 - Configure Alternative_IP/IP that connects to the Proxy server.");
}
}

/**
* @throws EnableException If IP setting is unset
*/
private Server registerServer() {
Server proxy = createServerObject();

fromDatabase.save(proxy);
Server stored = fromDatabase.load(null)
.orElseThrow(() -> new EnableException("BungeeCord registration failed (DB)"));
Server stored = fromDatabase.load(proxy.getUuid())
.orElseThrow(() -> new EnableException("Server registration to database failed"));

fromFile.save(stored);
return stored;
}

/**
* @throws EnableException If IP setting is unset
*/
private Server createServerObject() {
ServerUUID serverUUID = generateNewUUID();
String accessAddress = addresses.getAccessAddress().orElseThrow(() -> new EnableException("Velocity can not have '0.0.0.0' or '' as an address. Set up 'Server.IP' setting."));
String accessAddress = getAddress();
return new Server(-1, serverUUID, "BungeeCord", accessAddress, true, currentVersion);
}

@Nullable
private String getAddress() {
return addresses.getAccessAddress()
.orElse(addresses.isWebserverEnabled() ? addresses.getFallbackLocalhostAddress() : null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@

import java.nio.file.Path;

import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

/**
Expand Down Expand Up @@ -74,7 +75,7 @@ void bungeeEnables() {
}

@Test
void bungeeDoesNotEnableWithDefaultIP() {
void bungeeEnablesWithDefaultIP() {
PlanSystem bungeeSystem = component.getPlanSystem();
try {
PlanConfig config = bungeeSystem.getConfigSystem().getConfig();
Expand All @@ -86,8 +87,8 @@ void bungeeDoesNotEnableWithDefaultIP() {
db.setTransactionExecutorServiceProvider(MoreExecutors::newDirectExecutorService);
dbSystem.setActiveDatabase(db);

EnableException thrown = assertThrows(EnableException.class, bungeeSystem::enable);
assertEquals("IP setting still 0.0.0.0 - Configure Alternative_IP/IP that connects to the Proxy server.", thrown.getMessage());
bungeeSystem.enable();
assertTrue(bungeeSystem.isEnabled());
} finally {
bungeeSystem.disable();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public void onServerCommand(CMDSender sender, @Untrusted Arguments arguments) {
.orElseThrow(() -> new IllegalArgumentException(locale.getString(CommandLang.FAIL_SERVER_NOT_FOUND, identifier)));
}

String address = getAddress(sender) + "/server/" + Html.encodeToURL(server.getName());
String address = getAddress(sender) + "/server/" + Html.encodeToURL(server.getUuid().toString());
sender.buildMessage()
.addPart(colors.getMainColor() + locale.getString(CommandLang.LINK_SERVER))
.apply(builder -> linkTo(builder, sender, address))
Expand All @@ -126,7 +126,7 @@ public void onServersCommand(CMDSender sender, @Untrusted Arguments arguments) {
String serversListed = dbSystem.getDatabase()
.query(ServerQueries.fetchPlanServerInformationCollection())
.stream().sorted()
.map(server -> m + server.getId().orElse(0) + "::" + t + server.getName() + "::" + s + server.getUuid() + "::" + s + server.getPlanVersion() + "\n")
.map(server -> m + server.getId().orElse(0) + "::" + t + server.getIdentifiableName() + "::" + s + server.getUuid() + "::" + s + server.getPlanVersion() + "\n")
.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
.toString();
sender.buildMessage()
Expand Down Expand Up @@ -195,7 +195,7 @@ public void onNetworkCommand(CMDSender sender, @Untrusted Arguments arguments) {
.addPart(colors.getMainColor() + locale.getString(CommandLang.LINK_NETWORK))
.apply(builder -> linkTo(builder, sender, address))
.send();
if (dbSystem.getDatabase().query(ServerQueries.fetchProxyServerInformation()).isEmpty()) {
if (dbSystem.getDatabase().query(ServerQueries.fetchProxyServers()).isEmpty()) {
throw new IllegalArgumentException(locale.getString(CommandLang.NOTIFY_NO_NETWORK));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public void onInfo(CMDSender sender) {
Database database = dbSystem.getDatabase();

String updateAvailable = versionChecker.isNewVersionAvailable() ? yes : no;
String proxyAvailable = database.query(ServerQueries.fetchProxyServerInformation()).isPresent() ? yes : no;
String proxyAvailable = database.query(ServerQueries.fetchProxyServers()).isEmpty() ? no : yes;


String[] messages = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.delivery.domain.datatransfer.graphs;

import java.util.List;
import java.util.Objects;

/**
* Represents multiple graphs of same type.
*
* @author AuroraLS3
*/
public class GraphCollection<T> {

private final List<T> graphs;
private final String color;

public GraphCollection(List<T> graphs, String color) {
this.graphs = graphs;
this.color = color;
}

public List<T> getGraphs() {
return graphs;
}

public String getColor() {
return color;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GraphCollection<?> that = (GraphCollection<?>) o;
return Objects.equals(getGraphs(), that.getGraphs()) && Objects.equals(getColor(), that.getColor());
}

@Override
public int hashCode() {
return Objects.hash(getGraphs(), getColor());
}

@Override
public String toString() {
return "GraphCollection{" +
"graphs=" + graphs +
", color='" + color + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.delivery.domain.datatransfer.graphs;

import com.djrapitops.plan.delivery.domain.datatransfer.ServerDto;

import java.util.List;
import java.util.Objects;

/**
* Represents a line graph of some server so that they can be stacked.
*
* @author AuroraLS3
*/
public class ServerSpecificLineGraph {

private final List<Double[]> points;
private final ServerDto server;

public ServerSpecificLineGraph(List<Double[]> points, ServerDto server) {
this.points = points;
this.server = server;
}

public List<Double[]> getPoints() {
return points;
}

public ServerDto getServer() {
return server;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ServerSpecificLineGraph that = (ServerSpecificLineGraph) o;
return Objects.equals(getPoints(), that.getPoints()) && Objects.equals(getServer(), that.getServer());
}

@Override
public int hashCode() {
return Objects.hash(getPoints(), getServer());
}

@Override
public String toString() {
return "ServerSpecificLineGraph{" +
"points=" + points +
", server=" + server +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public void run() {

private void scheduleExport() {
Database database = dbSystem.getDatabase();
boolean hasProxy = database.query(ServerQueries.fetchProxyServerInformation()).isPresent();
boolean hasProxy = !database.query(ServerQueries.fetchProxyServers()).isEmpty();
if (serverInfo.getServer().isNotProxy() && hasProxy) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ public void exportJSON(ExportPaths exportPaths, Path toDirectory, Server server)
"network/sessionsOverview",
"network/playerbaseOverview",
"graph?type=playersOnline&server=" + serverUUID,
"graph?type=playersOnlineProxies",
"graph?type=uniqueAndNew",
"graph?type=hourlyUniqueAndNew",
"graph?type=serverPie",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,32 @@ public Map<String, Object> networkPlayersTableJSON() {

Database database = dbSystem.getDatabase();

ServerUUID mainServerUUID = database.query(ServerQueries.fetchProxyServerInformation()).map(Server::getUuid).orElse(serverInfo.getServerUUID());
Map<UUID, ExtensionTabData> pluginData = database.query(new ExtensionServerTableDataQuery(mainServerUUID, xMostRecentPlayers));
List<ServerUUID> mainServerUUIDs = database.query(ServerQueries.fetchProxyServers())
.stream()
.map(Server::getUuid)
.collect(Collectors.toList());
if (mainServerUUIDs.isEmpty()) mainServerUUIDs.add(serverInfo.getServerUUID());

Map<UUID, ExtensionTabData> allPluginData = new HashMap<>();

for (ServerUUID serverUUID : mainServerUUIDs) {
Map<UUID, ExtensionTabData> pluginData = database.query(new ExtensionServerTableDataQuery(serverUUID, xMostRecentPlayers));
for (Map.Entry<UUID, ExtensionTabData> entry : pluginData.entrySet()) {
UUID playerUUID = entry.getKey();
ExtensionTabData dataFromServer = entry.getValue();
ExtensionTabData alreadyIncludedData = allPluginData.get(playerUUID);
if (alreadyIncludedData == null) {
allPluginData.put(playerUUID, dataFromServer);
} else {
alreadyIncludedData.combine(dataFromServer);
}
}
}


return new PlayersTableJSONCreator(
database.query(new NetworkTablePlayersQuery(System.currentTimeMillis(), playtimeThreshold, xMostRecentPlayers)),
pluginData,
allPluginData,
openPlayerLinksInNewTab,
formatters, locale,
true // players page
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
import com.djrapitops.plan.delivery.domain.DateObj;
import com.djrapitops.plan.delivery.domain.JoinAddressCount;
import com.djrapitops.plan.delivery.domain.JoinAddressCounts;
import com.djrapitops.plan.delivery.domain.datatransfer.ServerDto;
import com.djrapitops.plan.delivery.domain.datatransfer.graphs.GraphCollection;
import com.djrapitops.plan.delivery.domain.datatransfer.graphs.ServerSpecificLineGraph;
import com.djrapitops.plan.delivery.domain.mutators.MutatorFunctions;
import com.djrapitops.plan.delivery.domain.mutators.PingMutator;
import com.djrapitops.plan.delivery.domain.mutators.TPSMutator;
Expand Down Expand Up @@ -59,10 +62,7 @@

import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -495,4 +495,22 @@ private Map<String, Object> mapToJson(String[] pieColors, List<DateObj<Map<Strin
.put("join_addresses_by_date", joinAddressCounts)
.build();
}

public GraphCollection<ServerSpecificLineGraph> proxyPlayersOnlineGraphs() {
Database db = dbSystem.getDatabase();
long now = System.currentTimeMillis();
long halfYearAgo = now - TimeUnit.DAYS.toMillis(180L);

List<ServerSpecificLineGraph> proxyGraphs = new ArrayList<>();
for (Server proxy : db.query(ServerQueries.fetchProxyServers())) {
ServerUUID proxyUUID = proxy.getUuid();
List<Double[]> points = Lists.map(
db.query(TPSQueries.fetchPlayersOnlineOfServer(halfYearAgo, now, proxyUUID)),
point -> Point.fromDateObj(point).toArray()
);
proxyGraphs.add(new ServerSpecificLineGraph(points, ServerDto.fromServer(proxy)));
}

return new GraphCollection<>(proxyGraphs, theme.getValue(ThemeVal.GRAPH_PLAYERS_ONLINE));
}
}
Loading

0 comments on commit f7cec19

Please sign in to comment.