Switch to a batched solution for server heartbeats

This commit is contained in:
Colin McDonald 2016-05-29 02:44:10 -04:00
parent 37af5a7bd5
commit 63a9a51c5f
4 changed files with 122 additions and 35 deletions

View File

@ -4,15 +4,13 @@ import com.google.common.collect.Collections2;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.utils.UUIDUtils;
import org.bson.types.ObjectId;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.*;
@Entity(value = "grants", noClassnameStored = true)
@AllArgsConstructor
@ -36,6 +34,25 @@ public final class Grant {
return APIv3.getDatastore().createQuery(Grant.class).field("id").equal(id).get();
}
public static Map<UUID, List<Grant>> byUserGrouped(Iterable<UUID> ids) {
Map<UUID, List<Grant>> result = new HashMap<>();
Set<UUID> uuidsToSearch = new HashSet<>();
for (UUID id : ids) {
result.put(id, new ArrayList<>());
if (UUIDUtils.isAcceptableUUID(id)) {
uuidsToSearch.add(id);
}
}
APIv3.getDatastore().createQuery(Grant.class).field("user").in(uuidsToSearch).forEach((grant) -> {
result.get(grant.getUser()).add(grant);
});
return result;
}
public Grant() {} // For Morphia
public Grant(User user, String reason, Set<ServerGroup> scopes, Rank rank, Date expiresAt, User addedBy) {

View File

@ -1,17 +1,17 @@
package net.frozenorb.apiv3.models;
import com.google.common.collect.ImmutableSet;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.actors.ActorType;
import net.frozenorb.apiv3.utils.TimeUtils;
import net.frozenorb.apiv3.utils.UUIDUtils;
import org.bson.types.ObjectId;
import org.mongodb.morphia.annotations.*;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
import java.util.*;
@Entity(value = "punishments", noClassnameStored = true)
@AllArgsConstructor
@ -43,6 +43,29 @@ public final class Punishment {
return APIv3.getDatastore().createQuery(Punishment.class).field("id").equal(id).get();
}
public static Map<UUID, List<Punishment>> byUserGrouped(Iterable<UUID> ids) {
return byUserGrouped(ids, ImmutableSet.copyOf(PunishmentType.values()));
}
public static Map<UUID, List<Punishment>> byUserGrouped(Iterable<UUID> ids, Iterable<Punishment.PunishmentType> types) {
Map<UUID, List<Punishment>> result = new HashMap<>();
Set<UUID> uuidsToSearch = new HashSet<>();
for (UUID id : ids) {
result.put(id, new ArrayList<>());
if (UUIDUtils.isAcceptableUUID(id)) {
uuidsToSearch.add(id);
}
}
APIv3.getDatastore().createQuery(Punishment.class).field("user").in(uuidsToSearch).field("type").in(types).forEach((punishment) -> {
result.get(punishment.getUser()).add(punishment);
});
return result;
}
public Punishment() {} // For Morphia
public Punishment(User user, String reason, PunishmentType type, Date expiresAt, User addedBy, Actor actor, Map<String, Object> metadata) {

View File

@ -53,6 +53,25 @@ public final class User {
}
}
public static Map<UUID, User> byIdGrouped(Iterable<UUID> ids) {
Map<UUID, User> result = new HashMap<>();
Set<UUID> uuidsToSearch = new HashSet<>();
for (UUID id : ids) {
result.put(id, null);
if (UUIDUtils.isAcceptableUUID(id)) {
uuidsToSearch.add(id);
}
}
APIv3.getDatastore().createQuery(User.class).field("id").in(uuidsToSearch).forEach((user) -> {
result.put(user.getId(), user);
});
return result;
}
public static User byEmailToken(String name) {
return APIv3.getDatastore().createQuery(User.class).field("emailToken").equal(name).get();
}
@ -128,7 +147,7 @@ public final class User {
return APIv3.getDatastore().createQuery(Punishment.class).field("user").equal(id).asList();
}
public List<Punishment> getPunishments(Collection<Punishment.PunishmentType> types) {
public List<Punishment> getPunishments(Iterable<Punishment.PunishmentType> types) {
return APIv3.getDatastore().createQuery(Punishment.class).field("user").equal(id).field("type").in(types).asList();
}
@ -204,9 +223,14 @@ public final class User {
}
public Rank getHighestRank(ServerGroup serverGroup) {
return getHighestRank(serverGroup, getGrants());
}
// This is only used to help batch requests to mongo
public Rank getHighestRank(ServerGroup serverGroup, Iterable<Grant> grants) {
Rank highest = null;
for (Grant grant : getGrants()) {
for (Grant grant : grants) {
if (!grant.isActive() || (serverGroup != null && !grant.appliesOn(serverGroup))) {
continue;
}
@ -252,14 +276,23 @@ public final class User {
}
public Map<String, Object> getLoginInfo(Server server) {
return getLoginInfo(
server,
getPunishments(ImmutableSet.of(
Punishment.PunishmentType.BLACKLIST,
Punishment.PunishmentType.BAN,
Punishment.PunishmentType.MUTE
)),
getGrants()
);
}
// This is only used to help batch requests to mongo
public Map<String, Object> getLoginInfo(Server server, Iterable<Punishment> punishments, Iterable<Grant> grants) {
Punishment activeMute = null;
String accessDenialReason = null;
for (Punishment punishment : getPunishments(ImmutableSet.of(
Punishment.PunishmentType.BLACKLIST,
Punishment.PunishmentType.BAN,
Punishment.PunishmentType.MUTE
))) {
for (Punishment punishment : punishments) {
if (!punishment.isActive()) {
continue;
}
@ -271,8 +304,7 @@ public final class User {
}
}
ServerGroup actorGroup = ServerGroup.byId(server.getServerGroup());
Rank highestRank = getHighestRank(actorGroup);
Rank highestRank = getHighestRank(ServerGroup.byId(server.getServerGroup()), grants);
// Generics are weird, yes we have to do this.
ImmutableMap.Builder<String, Object> result = ImmutableMap.<String, Object>builder()

View File

@ -5,10 +5,7 @@ import lombok.extern.slf4j.Slf4j;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.actors.ActorType;
import net.frozenorb.apiv3.models.Rank;
import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.models.ServerGroup;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.models.*;
import net.frozenorb.apiv3.utils.ErrorUtils;
import net.frozenorb.apiv3.utils.PermissionUtils;
import net.frozenorb.apiv3.utils.UUIDUtils;
@ -33,33 +30,51 @@ public final class POSTServerHeartbeat implements Route {
Server actorServer = Server.byId(actor.getName());
ServerGroup actorServerGroup = ServerGroup.byId(actorServer.getServerGroup());
Document reqJson = Document.parse(req.body());
Set<UUID> onlinePlayers = new HashSet<>();
Map<UUID, String> onlinePlayersNames = new HashMap<>();
Map<UUID, User> onlinePlayersUsers = new HashMap<>();
Map<UUID, List<Grant>> onlinePlayersGrants = new HashMap<>();
Map<UUID, List<Punishment>> onlinePlayersPunishments = new HashMap<>();
Map<String, Object> playersResponse = new HashMap<>();
// This code is messy, but we have to do db ops in parallel to avoid
// spamming Mongo with queries, so we do this.
for (Object player : (List<Object>) reqJson.get("players")) {
Document playerJson = (Document) player;
UUID uuid = UUID.fromString(playerJson.getString("uuid"));
if (!UUIDUtils.isAcceptableUUID(uuid)) {
continue;
}
User user = User.byId(uuid);
String username = playerJson.getString("username");
if (UUIDUtils.isAcceptableUUID(uuid)) {
onlinePlayersNames.put(uuid, username);
}
}
onlinePlayersUsers = User.byIdGrouped(onlinePlayersNames.keySet());
for (Map.Entry<UUID, User> entry : new HashMap<>(onlinePlayersUsers).entrySet()) {
UUID uuid = entry.getKey();
User user = entry.getValue();
if (user == null) {
// Will be saved by the save command a few lines down.
// Will be saved in the User constructor
String username = onlinePlayersNames.get(uuid);
user = new User(uuid, username);
APIv3.getDatastore().save(user);
onlinePlayersUsers.put(uuid, user);
}
// Only save if needed
if (user.seenOnServer(actorServer)) {
APIv3.getDatastore().save(user);
}
}
onlinePlayers.add(user.getId());
playersResponse.put(user.getId().toString(), user.getLoginInfo(actorServer));
onlinePlayersGrants = Grant.byUserGrouped(onlinePlayersUsers.keySet());
onlinePlayersPunishments = Punishment.byUserGrouped(onlinePlayersUsers.keySet());
for (Map.Entry<UUID, User> entry : onlinePlayersUsers.entrySet()) {
UUID uuid = entry.getKey();
User user = entry.getValue();
playersResponse.put(uuid.toString(), user.getLoginInfo(actorServer, onlinePlayersPunishments.get(uuid), onlinePlayersGrants.get(uuid)));
}
for (Object event : (List<Object>) reqJson.get("events")) {
@ -78,7 +93,7 @@ public final class POSTServerHeartbeat implements Route {
}
}
Map<String, Map<String, Boolean>> permissions = new HashMap<>();
Map<String, Map<String, Boolean>> permissionsResponse = new HashMap<>();
for (Rank rank : Rank.values()) {
Map<String, Boolean> scopedPermissions = PermissionUtils.mergePermissions(
@ -86,17 +101,17 @@ public final class POSTServerHeartbeat implements Route {
actorServerGroup.calculatePermissions(rank)
);
permissions.put(rank.getId(), scopedPermissions);
permissionsResponse.put(rank.getId(), scopedPermissions);
}
actorServer.setPlayers(onlinePlayers);
actorServer.setPlayers(onlinePlayersNames.keySet());
actorServer.setLastTps(reqJson.getDouble("lastTps"));
actorServer.setLastUpdatedAt(new Date());
APIv3.getDatastore().save(actorServer);
return ImmutableMap.of(
"players", playersResponse,
"permissions", permissions
"permissions", permissionsResponse
);
}