Skip to content

Commit

Permalink
v0.9.2.2
Browse files Browse the repository at this point in the history
- rate limit from Panel API getting automatically tracked
- if Panel server ends the connection Plugin retrys  the request
- you can turn on rate limit logging in the console
- version bump

Took 1 hour 37 minutes
  • Loading branch information
TubYoub committed Oct 5, 2024
1 parent 0fcde5a commit 6aeac47
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 52 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>de.tubyoub</groupId>
<artifactId>VelocityPteroPower</artifactId>
<version>0.9.2.1</version>
<version>0.9.2.2</version>
<packaging>jar</packaging>

<name>VelocityPteroPower</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public class ConfigurationManager {
private String apiKey;
private PanelType panel;
private boolean checkUpdate;
private boolean printRateLimit;
private int startupJoinDelay;
private int apiThreads;
private final VelocityPteroPower plugin;
Expand Down Expand Up @@ -93,6 +94,7 @@ public void loadConfig(){


checkUpdate = (boolean) config.get("checkUpdate");
printRateLimit = (boolean) config.get("printRateLimit");
apiThreads = (int) config.get("apiThreads", 10);
Section startupJoinSection = config.getSection("startupJoin");
Map<String, Object> startupJoin = new HashMap<>();
Expand Down Expand Up @@ -237,4 +239,8 @@ public PanelType getPanelType(){
public int getApiThreads() {
return apiThreads;
}

public boolean isPrintRateLimit() {
return printRateLimit;
}
}
8 changes: 6 additions & 2 deletions src/main/java/de/tubyoub/velocitypteropower/PteroCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ private void startServer(CommandSource sender, String[] args) {
Map<String, PteroServerInfo> serverInfoMap = plugin.getServerInfoMap();
if (serverInfoMap.containsKey(serverName)) {
PteroServerInfo serverInfo = serverInfoMap.get(serverName);
apiClient.powerServer(serverInfo.getServerId(), "start");
if (plugin.canMakeRequest()) {
apiClient.powerServer(serverInfo.getServerId(), "start");
}
sender.sendMessage(getSPPPrefix().append(Component.text("The server: "+ serverName + " is starting")));
} else {
}
Expand All @@ -147,7 +149,9 @@ private void stopServer(CommandSource sender, String[] args) {
Map<String, PteroServerInfo> serverInfoMap = plugin.getServerInfoMap();
if (serverInfoMap.containsKey(serverName)) {
PteroServerInfo serverInfo = serverInfoMap.get(serverName);
apiClient.powerServer(serverInfo.getServerId(), "stop");
if (plugin.canMakeRequest()) {
apiClient.powerServer(serverInfo.getServerId(), "stop");
}
sender.sendMessage(getSPPPrefix().append(Component.text("The server: "+ serverName + " is stopping")));
} else {
}
Expand Down
59 changes: 48 additions & 11 deletions src/main/java/de/tubyoub/velocitypteropower/VelocityPteroPower.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,27 +39,33 @@
import de.tubyoub.velocitypteropower.api.PanelType;
import de.tubyoub.velocitypteropower.api.PelicanAPIClient;
import de.tubyoub.velocitypteropower.api.PterodactylAPIClient;
import de.tubyoub.velocitypteropower.libs.Metrics;
import de.tubyoub.velocitypteropower.util.Metrics;
import de.tubyoub.velocitypteropower.util.VersionChecker;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import net.kyori.adventure.text.minimessage.MiniMessage;

import java.net.http.HttpResponse;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

/**
* Main class for the VelocityPteroPower plugin.
* This class handles the initialization of the plugin and the registration of commands and events.
*/
@Plugin(id = "velocity-ptero-power", name = "VelocityPteroPower", version = "0.9.2.1", authors = {"TubYoub"}, description = "A plugin for Velocity that allows you to manage your Pterodactyl/Pelican servers from the Velocity console.", url = "https://github.com/TubYoub/VelocityPteroPower")
@Plugin(id = "velocity-ptero-power", name = "VelocityPteroPower", version = "0.9.2.2", authors = {"TubYoub"}, description = "A plugin for Velocity that allows you to manage your Pterodactyl/Pelican servers from the Velocity console.", url = "https://github.com/TubYoub/VelocityPteroPower")
public class VelocityPteroPower {
private final String version = "0.9.2.1";
private final String version = "0.9.2.2";
private final String modrinthID = "";
private final int pluginId = 21465;
private final ProxyServer proxyServer;
private final ComponentLogger logger;
Expand All @@ -70,6 +76,10 @@ public class VelocityPteroPower {
private PanelAPIClient apiClient;
private final Metrics.Factory metricsFactory;
private final Set<String> startingServers = ConcurrentHashMap.newKeySet();
private final AtomicInteger rateLimit = new AtomicInteger(60); // Default value, will be updated
private final AtomicInteger remainingRequests = new AtomicInteger(60); // Default value, will be updated
private final ReentrantLock rateLimitLock = new ReentrantLock();


/**
* Constructor for the VelocityPteroPower class.
Expand All @@ -87,7 +97,6 @@ public VelocityPteroPower(ProxyServer proxy, @DataDirectory Path dataDirectory,C
this.dataDirectory = dataDirectory;
this.commandManager = commandManager;
this.configurationManager = new ConfigurationManager(this);

this.metricsFactory = metricsFactory;
}

Expand Down Expand Up @@ -164,8 +173,6 @@ public void onServerPreConnect(ServerPreConnectEvent event) {
this.serverInfoMap = configurationManager.getServerInfoMap();
PteroServerInfo serverInfo = serverInfoMap.get(serverName);



if (!serverInfoMap.containsKey(serverName)) {
logger.warn("Server '" + serverName + "' not found in configuration.");
player.sendMessage(
Expand All @@ -174,7 +181,7 @@ public void onServerPreConnect(ServerPreConnectEvent event) {
.append(Component.text("] Server not found in configuration: " + serverName, NamedTextColor.WHITE)));
return;
}
if (apiClient.isServerOnline(serverInfo.getServerId())) {
if (apiClient.isServerOnline(serverInfo.getServerId()) && this.canMakeRequest()) {
if (startingServers.contains(serverName)){
startingServers.remove(serverName);
}
Expand All @@ -198,7 +205,7 @@ public void onServerPreConnect(ServerPreConnectEvent event) {
event.setResult(ServerPreConnectEvent.ServerResult.denied());

proxyServer.getScheduler().buildTask(this, () -> {
if (apiClient.isServerOnline(serverInfo.getServerId())) {
if (apiClient.isServerOnline(serverInfo.getServerId()) && this.canMakeRequest()) {
connectPlayer(player, serverName);
} else {
proxyServer.getScheduler().buildTask(this, () -> checkServerAndConnectPlayer(player, serverName)).schedule();
Expand All @@ -208,7 +215,7 @@ public void onServerPreConnect(ServerPreConnectEvent event) {

private void checkServerAndConnectPlayer(Player player, String serverName) {
PteroServerInfo serverInfo = serverInfoMap.get(serverName);
if (apiClient.isServerOnline(serverInfo.getServerId())) {
if (apiClient.isServerOnline(serverInfo.getServerId()) && this.canMakeRequest()) {
connectPlayer(player, serverName);
} else {
proxyServer.getScheduler().buildTask(this, () -> checkServerAndConnectPlayer(player, serverName)).delay(configurationManager.getStartupJoinDelay(), TimeUnit.SECONDS).schedule();
Expand Down Expand Up @@ -239,11 +246,41 @@ private void connectPlayer(Player player, String serverName) {
return;
}

if (apiClient.isServerOnline(serverInfoMap.get(serverName).getServerId())) {
if (apiClient.isServerOnline(serverInfoMap.get(serverName).getServerId()) && this.canMakeRequest()) {
player.createConnectionRequest(server).fireAndForget();
startingServers.remove(serverName);
}
}
public boolean canMakeRequest() {
rateLimitLock.lock();
try {
return remainingRequests.get() > 0;
} finally {
rateLimitLock.unlock();
}
}

public void updateRateLimitInfo(HttpResponse<String> response) {
rateLimitLock.lock();
try {
String limitHeader = response.headers().firstValue("x-ratelimit-limit").orElse(null);
String remainingHeader = response.headers().firstValue("x-ratelimit-remaining").orElse(null);

if (limitHeader != null) {
rateLimit.set(Integer.parseInt(limitHeader));
}
if (remainingHeader != null) {
remainingRequests.set(Integer.parseInt(remainingHeader));
}
} finally {
rateLimitLock.unlock();
if (configurationManager.isPrintRateLimit()) {
logger.info("Rate limit updated: Limit: {}, Remaining: {}", rateLimit.get(), remainingRequests.get());
}
}
}



/**
* This method reloads the configuration for the VelocityPteroPower plugin.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import de.tubyoub.velocitypteropower.VelocityPteroPower;
import org.slf4j.Logger;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
Expand All @@ -23,11 +24,13 @@ public class PelicanAPIClient implements PanelAPIClient {
public final ConfigurationManager configurationManager;
public final ProxyServer proxyServer;
private final ObjectMapper objectMapper = new ObjectMapper();
private final VelocityPteroPower plugin;

private final HttpClient httpClient;
private final ExecutorService executorService;

public PelicanAPIClient(VelocityPteroPower plugin) {
this.plugin = plugin;
this.logger = plugin.getLogger();
this.configurationManager = plugin.getConfigurationManager();
this.proxyServer = plugin.getProxyServer();
Expand All @@ -49,34 +52,55 @@ public void powerServer(String serverId, String signal) {
.POST(HttpRequest.BodyPublishers.ofString("{\"signal\": \"" + signal + "\"}"))
.build();

client.send(request, HttpResponse.BodyHandlers.ofString());
plugin.updateRateLimitInfo(httpClient.send(request, HttpResponse.BodyHandlers.ofString()));
} catch (Exception e) {
logger.error("Error powering server.", e);
}
}

@Override
public boolean isServerOnline(String serverId) {
int retryCount = 3;
while (retryCount > 0) {
try {
// Make the API request to get the server status
// Replace this with the actual API request code for the new panel API
String responseBody = "{\"object\": \"stats\", \"attributes\": {\"current_state\": \"running\", \"is_suspended\": false, \"resources\": {\"memory_bytes\": 1662955520, \"cpu_absolute\": 17.335, \"disk_bytes\": 180404668, \"network_rx_bytes\": 11376, \"network_tx_bytes\": 3184, \"uptime\": 183942}}}";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(configurationManager.getPterodactylUrl() + "api/client/servers/" + serverId + "/resources"))
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + configurationManager.getPterodactylApiKey())
.GET()
.build();

JsonNode rootNode = objectMapper.readTree(responseBody);
JsonNode attributesNode = rootNode.get("attributes");

if (attributesNode != null) {
String currentState = attributesNode.get("current_state").asText();
boolean isSuspended = attributesNode.get("is_suspended").asBoolean();

return currentState.equals("running") && !isSuspended;
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
plugin.updateRateLimitInfo(response);
String responseBody = response.body();
if (response.statusCode() == 200) {
return responseBody.contains("{\"object\":\"stats\",\"attributes\":{\"current_state\":\"running\"");
} else {
return false;
}
} catch (IOException | InterruptedException e) {
if (e.getMessage().contains("GOAWAY")) {
retryCount--;
if (retryCount == 0) {
logger.error("Failed to check server status after retries: " + e.getMessage());
return false;
}
logger.warn("GOAWAY received, retrying... (" + retryCount + " retries left)");
try {
Thread.sleep(1000); // Wait before retrying
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
return false;
}
} else {
logger.error("Error checking server status: " + e.getMessage());
return false;
}
} catch (JsonProcessingException e) {
e.printStackTrace();
}

return false;
}
return false;
}

@Override
public boolean isServerEmpty(String serverName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public class PterodactylAPIClient implements PanelAPIClient{
public final Logger logger;
public final ConfigurationManager configurationManager;
public final ProxyServer proxyServer;
private final VelocityPteroPower plugin;

private final HttpClient httpClient;
private final ExecutorService executorService;
Expand All @@ -67,6 +68,7 @@ public class PterodactylAPIClient implements PanelAPIClient{
*/

public PterodactylAPIClient(VelocityPteroPower plugin){
this.plugin = plugin;
this.logger = plugin.getLogger();
this.configurationManager = plugin.getConfigurationManager();
this.proxyServer = plugin.getProxyServer();
Expand Down Expand Up @@ -94,7 +96,7 @@ public void powerServer(String serverId, String signal) {
.POST(HttpRequest.BodyPublishers.ofString("{\"signal\": \"" + signal + "\"}"))
.build();

httpClient.send(request, HttpResponse.BodyHandlers.ofString());
plugin.updateRateLimitInfo(httpClient.send(request, HttpResponse.BodyHandlers.ofString()));
} catch (Exception e) {
logger.error("Error powering server.", e);
}
Expand All @@ -109,26 +111,46 @@ public void powerServer(String serverId, String signal) {
*/
@Override
public boolean isServerOnline(String serverId) {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(configurationManager.getPterodactylUrl() + "api/client/servers/" + serverId + "/resources"))
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + configurationManager.getPterodactylApiKey())
.GET()
.build();

HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
String responseBody = response.body();
if (response.statusCode() == 200) {
return responseBody.contains("{\"object\":\"stats\",\"attributes\":{\"current_state\":\"running\"");
} else {
return false;
int retryCount = 3;
while (retryCount > 0) {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(configurationManager.getPterodactylUrl() + "api/client/servers/" + serverId + "/resources"))
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + configurationManager.getPterodactylApiKey())
.GET()
.build();

HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
plugin.updateRateLimitInfo(response);
String responseBody = response.body();
if (response.statusCode() == 200) {
return responseBody.contains("{\"object\":\"stats\",\"attributes\":{\"current_state\":\"running\"");
} else {
return false;
}
} catch (IOException | InterruptedException e) {
if (e.getMessage().contains("GOAWAY")) {
retryCount--;
if (retryCount == 0) {
logger.error("Failed to check server status after retries: " + e.getMessage());
return false;
}
logger.warn("GOAWAY received, retrying... (" + retryCount + " retries left)");
try {
Thread.sleep(1000); // Wait before retrying
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
return false;
}
} else {
logger.error("Error checking server status: " + e.getMessage());
return false;
}
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
return false;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*
* Violations will result in a ban of your plugin and account from bStats.
*/
package de.tubyoub.velocitypteropower.libs;
package de.tubyoub.velocitypteropower.util;

import com.google.inject.Inject;
import com.velocitypowered.api.plugin.PluginContainer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*/


package de.tubyoub.velocitypteropower;
package de.tubyoub.velocitypteropower.util;

import java.io.BufferedReader;
import java.io.IOException;
Expand Down
Loading

0 comments on commit 6aeac47

Please sign in to comment.