com.danielflower.apprunner.router.mgmt.Cluster Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of app-runner-router Show documentation
Show all versions of app-runner-router Show documentation
A reverse proxy for AppRunner that allows you horizontally scale AppRunner
package com.danielflower.apprunner.router.mgmt;
import io.muserver.MuRequest;
import org.apache.commons.io.FileUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
public class Cluster {
private static final Logger log = LoggerFactory.getLogger(Cluster.class);
private final File config;
private final List runners = new CopyOnWriteArrayList<>();
private final MapManager querier;
private Cluster(File config, MapManager querier, List runners) {
this.config = config;
this.querier = querier;
this.runners.addAll(runners);
}
public static Cluster load(File config, MapManager mapManager) throws IOException {
ArrayList runners = new ArrayList<>();
boolean isNew = !config.exists();
if (config.exists()) {
JSONObject json = new JSONObject(FileUtils.readFileToString(config, StandardCharsets.UTF_8));
for (Object o : json.getJSONArray("runners")) {
runners.add(Runner.fromJSON((JSONObject) o));
}
} else {
config.getParentFile().mkdirs();
config.createNewFile();
}
Cluster cluster = new Cluster(config, mapManager, runners);
if (isNew) {
cluster.save();
}
return cluster;
}
public List getRunners() {
return runners;
}
public synchronized void addRunner(MuRequest clientRequest, Runner runner) throws Exception {
if (!runners.contains(runner)) {
runners.add(runner);
}
querier.loadRunner(clientRequest, runner);
refreshRunnerCountCache(querier.getCurrentMapping());
save();
}
public synchronized void deleteRunner(Runner runner) throws IOException {
runners.remove(runner);
querier.removeRunner(runner);
save();
}
private void save() throws IOException {
FileUtils.write(config, toJSON().toString(4), "UTF-8", false);
}
public JSONObject toJSON() {
JSONArray all = new JSONArray();
for (Runner runner : runners) {
all.put(runner.toJSON());
}
return new JSONObject()
.put("runners", all);
}
public Optional runner(String id) {
return runners.stream()
.filter(runner -> runner.id.equals(id))
.findFirst();
}
public synchronized Optional allocateRunner(ConcurrentHashMap currentMapping, Collection excludedRunnerIDs) {
refreshRunnerCountCache(currentMapping);
Runner leastContended = null;
for (Runner runner : runners) {
if (!runner.hasCapacity()) {
continue;
}
if (leastContended == null || leastContended.numberOfApps() > runner.numberOfApps()) {
boolean isBanned = excludedRunnerIDs.contains(runner.id);
if (isBanned) {
log.info("Not allocating to " + runner.id + " because it was requested to be excluded.");
} else {
leastContended = runner;
}
}
}
if (leastContended != null) {
log.info("Incrementing app count for " + leastContended.id + " because apparently it is the least contended with "
+ leastContended.numberOfApps() + " apps (with max capacity of " + leastContended.maxApps + "). The full mapping is: " + currentMapping);
leastContended.incrementNumberOfApps();
return Optional.of(leastContended);
}
log.info("Could not allocate a runner because it seems there is no capacity. Current mapping: " + currentMapping);
return Optional.empty();
}
public void refreshRunnerCountCache(ConcurrentHashMap currentMapping) {
for (Runner runner : runners) {
runner.refreshRunnerCountCache(currentMapping);
}
}
public Optional getRunnerByURL(URI url) {
for (Runner runner : runners) {
if (runner.url.getAuthority().equals(url.getAuthority())) {
return Optional.of(runner);
}
}
return Optional.empty();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy