Implemented getting bucket users
This commit is contained in:
parent
070068aecc
commit
b8dc6a24df
@ -0,0 +1,44 @@
|
|||||||
|
package ru.dragonestia.loadbalancer.web.component;
|
||||||
|
|
||||||
|
import com.vaadin.flow.component.grid.ColumnTextAlign;
|
||||||
|
import com.vaadin.flow.component.grid.Grid;
|
||||||
|
import com.vaadin.flow.component.html.Span;
|
||||||
|
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||||
|
import ru.dragonestia.loadbalancer.web.model.Bucket;
|
||||||
|
import ru.dragonestia.loadbalancer.web.model.User;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class UserList extends VerticalLayout {
|
||||||
|
|
||||||
|
private final Bucket bucket;
|
||||||
|
private final Grid<User> usersGrid;
|
||||||
|
private final Span totalUsers = new Span();
|
||||||
|
private final Span occupancy = new Span();
|
||||||
|
private List<User> cachedUsers = new ArrayList<>();
|
||||||
|
|
||||||
|
public UserList(Bucket bucket, List<User> users) {
|
||||||
|
this.bucket = bucket;
|
||||||
|
|
||||||
|
add(usersGrid = createUsersGrid());
|
||||||
|
|
||||||
|
update(users);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Grid<User> createUsersGrid() {
|
||||||
|
var grid = new Grid<User>();
|
||||||
|
grid.addColumn(User::identifier).setHeader("User Identifier").setFooter(totalUsers);
|
||||||
|
grid.addColumn(user -> 0).setTextAlign(ColumnTextAlign.CENTER).setHeader("Linked with buckets") // TODO
|
||||||
|
.setFooter(occupancy);
|
||||||
|
grid.addComponentColumn(user -> new Span("buttons")).setHeader("Manage"); // TODO
|
||||||
|
return grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(List<User> users) {
|
||||||
|
cachedUsers = users;
|
||||||
|
usersGrid.setItems(users);
|
||||||
|
totalUsers.setText("Total users: " + users.size());
|
||||||
|
occupancy.setText("Occupancy: %s".formatted(bucket.getUsingPercentage(users.size())));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -63,4 +63,10 @@ public class Bucket {
|
|||||||
public URI createApiURI() {
|
public URI createApiURI() {
|
||||||
return URI.create("/nodes/" + nodeIdentifier + "/buckets/" + identifier);
|
return URI.create("/nodes/" + nodeIdentifier + "/buckets/" + identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getUsingPercentage(int used) {
|
||||||
|
if (getSlots().isUnlimited()) return "0%";
|
||||||
|
double percent = used / (double) getSlots().slots() * 100;
|
||||||
|
return ((int) percent) + "%";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,26 +18,31 @@ import com.vaadin.flow.router.Route;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import ru.dragonestia.loadbalancer.web.component.AddUsers;
|
import ru.dragonestia.loadbalancer.web.component.AddUsers;
|
||||||
import ru.dragonestia.loadbalancer.web.component.NavPath;
|
import ru.dragonestia.loadbalancer.web.component.NavPath;
|
||||||
|
import ru.dragonestia.loadbalancer.web.component.UserList;
|
||||||
import ru.dragonestia.loadbalancer.web.model.Bucket;
|
import ru.dragonestia.loadbalancer.web.model.Bucket;
|
||||||
import ru.dragonestia.loadbalancer.web.model.Node;
|
import ru.dragonestia.loadbalancer.web.model.Node;
|
||||||
import ru.dragonestia.loadbalancer.web.repository.BucketRepository;
|
import ru.dragonestia.loadbalancer.web.repository.BucketRepository;
|
||||||
import ru.dragonestia.loadbalancer.web.repository.NodeRepository;
|
import ru.dragonestia.loadbalancer.web.repository.NodeRepository;
|
||||||
|
import ru.dragonestia.loadbalancer.web.repository.UserRepository;
|
||||||
|
|
||||||
@Route("/nodes/:nodeId/buckets/:bucketId")
|
@Route("/nodes/:nodeId/buckets/:bucketId")
|
||||||
public class BucketDetailsPage extends VerticalLayout implements BeforeEnterObserver {
|
public class BucketDetailsPage extends VerticalLayout implements BeforeEnterObserver {
|
||||||
|
|
||||||
private final NodeRepository nodeRepository;
|
private final NodeRepository nodeRepository;
|
||||||
private final BucketRepository bucketRepository;
|
private final BucketRepository bucketRepository;
|
||||||
|
private final UserRepository userRepository;
|
||||||
private Node node;
|
private Node node;
|
||||||
private Bucket bucket;
|
private Bucket bucket;
|
||||||
private AddUsers addUsers;
|
private AddUsers addUsers;
|
||||||
|
private UserList userList;
|
||||||
private Button lockBucketButton;
|
private Button lockBucketButton;
|
||||||
private VerticalLayout bucketInfo;
|
private VerticalLayout bucketInfo;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public BucketDetailsPage(NodeRepository nodeRepository, BucketRepository bucketRepository) {
|
public BucketDetailsPage(NodeRepository nodeRepository, BucketRepository bucketRepository, UserRepository userRepository) {
|
||||||
this.nodeRepository = nodeRepository;
|
this.nodeRepository = nodeRepository;
|
||||||
this.bucketRepository = bucketRepository;
|
this.bucketRepository = bucketRepository;
|
||||||
|
this.userRepository = userRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -90,6 +95,7 @@ public class BucketDetailsPage extends VerticalLayout implements BeforeEnterObse
|
|||||||
add(addUsers = new AddUsers(bucket));
|
add(addUsers = new AddUsers(bucket));
|
||||||
add(new Hr());
|
add(new Hr());
|
||||||
add(new H2("Users"));
|
add(new H2("Users"));
|
||||||
|
add(userList = new UserList(bucket, userRepository.all(bucket)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateBucketInfo() {
|
private void updateBucketInfo() {
|
||||||
|
|||||||
@ -0,0 +1,16 @@
|
|||||||
|
package ru.dragonestia.loadbalancer.web.repository;
|
||||||
|
|
||||||
|
import ru.dragonestia.loadbalancer.web.model.Bucket;
|
||||||
|
import ru.dragonestia.loadbalancer.web.model.User;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface UserRepository {
|
||||||
|
|
||||||
|
void linkWithBucket(Bucket bucket, Collection<User> users);
|
||||||
|
|
||||||
|
void unlinkFromBucket(Bucket bucket, Collection<User> users);
|
||||||
|
|
||||||
|
List<User> all(Bucket bucket);
|
||||||
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
package ru.dragonestia.loadbalancer.web.repository.impl;
|
||||||
|
|
||||||
|
import com.vaadin.flow.spring.annotation.SpringComponent;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
import org.springframework.web.client.HttpClientErrorException;
|
||||||
|
import ru.dragonestia.loadbalancer.web.model.Bucket;
|
||||||
|
import ru.dragonestia.loadbalancer.web.model.User;
|
||||||
|
import ru.dragonestia.loadbalancer.web.repository.UserRepository;
|
||||||
|
import ru.dragonestia.loadbalancer.web.repository.impl.response.BucketUserListResponse;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Log4j2
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@SpringComponent
|
||||||
|
public class UserRepositoryImpl implements UserRepository {
|
||||||
|
|
||||||
|
private final RestUtil rest;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void linkWithBucket(Bucket bucket, Collection<User> users) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unlinkFromBucket(Bucket bucket, Collection<User> users) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<User> all(Bucket bucket) {
|
||||||
|
try {
|
||||||
|
var response = rest.get(URI.create("/nodes/%s/buckets/%s/users".formatted(bucket.getNodeIdentifier(), bucket.getIdentifier())),
|
||||||
|
BucketUserListResponse.class,
|
||||||
|
params -> {});
|
||||||
|
|
||||||
|
return response.users();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.throwing(ex);
|
||||||
|
throw new Error("Internal error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package ru.dragonestia.loadbalancer.web.repository.impl.response;
|
||||||
|
|
||||||
|
import ru.dragonestia.loadbalancer.web.model.User;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record BucketUserListResponse(int slots, int usedSlots, List<User> users) {}
|
||||||
@ -10,11 +10,16 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|||||||
import ru.dragonestia.loadbalancer.interceptor.DebugInterceptor;
|
import ru.dragonestia.loadbalancer.interceptor.DebugInterceptor;
|
||||||
import ru.dragonestia.loadbalancer.model.Bucket;
|
import ru.dragonestia.loadbalancer.model.Bucket;
|
||||||
import ru.dragonestia.loadbalancer.model.Node;
|
import ru.dragonestia.loadbalancer.model.Node;
|
||||||
|
import ru.dragonestia.loadbalancer.model.User;
|
||||||
import ru.dragonestia.loadbalancer.model.type.LoadBalancingMethod;
|
import ru.dragonestia.loadbalancer.model.type.LoadBalancingMethod;
|
||||||
import ru.dragonestia.loadbalancer.model.type.SlotLimit;
|
import ru.dragonestia.loadbalancer.model.type.SlotLimit;
|
||||||
import ru.dragonestia.loadbalancer.repository.BucketRepository;
|
import ru.dragonestia.loadbalancer.repository.BucketRepository;
|
||||||
import ru.dragonestia.loadbalancer.repository.NodeRepository;
|
import ru.dragonestia.loadbalancer.repository.NodeRepository;
|
||||||
|
import ru.dragonestia.loadbalancer.repository.UserRepository;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Profile("test")
|
@Profile("test")
|
||||||
@ -24,6 +29,9 @@ public class TestConfig implements WebMvcConfigurer {
|
|||||||
|
|
||||||
private final NodeRepository nodeRepository;
|
private final NodeRepository nodeRepository;
|
||||||
private final BucketRepository bucketRepository;
|
private final BucketRepository bucketRepository;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
|
private final Random rand = new Random(0);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addInterceptors(@NonNull InterceptorRegistry registry) {
|
public void addInterceptors(@NonNull InterceptorRegistry registry) {
|
||||||
@ -41,13 +49,26 @@ public class TestConfig implements WebMvcConfigurer {
|
|||||||
nodeRepository.createNode(node);
|
nodeRepository.createNode(node);
|
||||||
|
|
||||||
for (int i = 1; i <= 5; i++) {
|
for (int i = 1; i <= 5; i++) {
|
||||||
bucketRepository.createBucket(Bucket.create("test-" + i, node, SlotLimit.of(5 * i), "Some payload"));
|
var slots = 5 * i;
|
||||||
|
var bucket = Bucket.create("test-" + i, node, SlotLimit.of(slots), "Some payload");
|
||||||
|
bucketRepository.createBucket(bucket);
|
||||||
|
|
||||||
|
for (int j = 0, n = rand.nextInt(slots + 1); j < n; j++) {
|
||||||
|
var user = new User("test-user-" + rand.nextInt(20));
|
||||||
|
userRepository.linkWithBucket(bucket, List.of(user), false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 5; i++) {
|
for (int i = 0; i < 5; i++) {
|
||||||
var bucket = Bucket.create(UUID.randomUUID().toString(), node, SlotLimit.unlimited(), "Some payload");
|
var bucket = Bucket.create(randomUUID().toString(), node, SlotLimit.unlimited(), "Some payload");
|
||||||
bucket.setLocked((i & 1) == 0);
|
bucket.setLocked((i & 1) == 0);
|
||||||
bucketRepository.createBucket(bucket);
|
bucketRepository.createBucket(bucket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private UUID randomUUID() {
|
||||||
|
byte[] randomBytes = new byte[16];
|
||||||
|
rand.nextBytes(randomBytes);
|
||||||
|
return UUID.nameUUIDFromBytes(randomBytes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,114 @@
|
|||||||
|
package ru.dragonestia.loadbalancer.controller;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.servlet.function.EntityResponse;
|
||||||
|
import ru.dragonestia.loadbalancer.controller.response.BucketUserListResponse;
|
||||||
|
import ru.dragonestia.loadbalancer.controller.response.LinkUsersWithBucketResponse;
|
||||||
|
import ru.dragonestia.loadbalancer.model.Bucket;
|
||||||
|
import ru.dragonestia.loadbalancer.model.Node;
|
||||||
|
import ru.dragonestia.loadbalancer.model.User;
|
||||||
|
import ru.dragonestia.loadbalancer.service.BucketService;
|
||||||
|
import ru.dragonestia.loadbalancer.service.NodeService;
|
||||||
|
import ru.dragonestia.loadbalancer.service.UserService;
|
||||||
|
import ru.dragonestia.loadbalancer.util.NamingValidator;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/nodes/{nodeIdentifier}/buckets/{bucketIdentifier}/users")
|
||||||
|
public class UserBucketController {
|
||||||
|
|
||||||
|
private final NodeService nodeService;
|
||||||
|
private final BucketService bucketService;
|
||||||
|
private final UserService userService;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
ResponseEntity<BucketUserListResponse> usersInsideBucket(@PathVariable(name = "nodeIdentifier") String nodeId,
|
||||||
|
@PathVariable(name = "bucketIdentifier") String bucketId) {
|
||||||
|
|
||||||
|
Bucket bucket;
|
||||||
|
try {
|
||||||
|
var temp = getNodeAndBucket(nodeId, bucketId);
|
||||||
|
bucket = temp.bucket();
|
||||||
|
} catch (Error error) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
var users = userService.getBucketUsers(bucket);
|
||||||
|
return ResponseEntity.ok(new BucketUserListResponse(bucket.getSlots().getSlots(), users.size(), users));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
ResponseEntity<LinkUsersWithBucketResponse> linkUserWithBucket(@PathVariable(name = "nodeIdentifier") String nodeId,
|
||||||
|
@PathVariable(name = "bucketIdentifier") String bucketId,
|
||||||
|
@RequestParam(name = "users") String userIds,
|
||||||
|
@RequestParam(name = "force") boolean force) {
|
||||||
|
|
||||||
|
Bucket bucket;
|
||||||
|
try {
|
||||||
|
var temp = getNodeAndBucket(nodeId, bucketId);
|
||||||
|
bucket = temp.bucket();
|
||||||
|
} catch (Error error) {
|
||||||
|
return ResponseEntity.status(404).body(new LinkUsersWithBucketResponse(false, error.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
var list = new LinkedList<User>();
|
||||||
|
for (var username: userIds.split(",")) {
|
||||||
|
if (!NamingValidator.validateUserIdentifier(username)) continue;
|
||||||
|
|
||||||
|
list.add(new User(username));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
userService.linkUsersWithBucket(bucket, list, force);
|
||||||
|
} catch (Error error) {
|
||||||
|
return ResponseEntity.status(400).body(new LinkUsersWithBucketResponse(false, error.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok(new LinkUsersWithBucketResponse(true, "Success"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping
|
||||||
|
ResponseEntity<?> unlinkUsersForBucket(@PathVariable(name = "nodeIdentifier") String nodeId,
|
||||||
|
@PathVariable(name = "bucketIdentifier") String bucketId,
|
||||||
|
@RequestParam(name = "users") String userIdentifiers) {
|
||||||
|
|
||||||
|
Node node;
|
||||||
|
Bucket bucket;
|
||||||
|
try {
|
||||||
|
var temp = getNodeAndBucket(nodeId, bucketId);
|
||||||
|
node = temp.node();
|
||||||
|
bucket = temp.bucket();
|
||||||
|
} catch (Error error) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record NodeAndBucket(Node node, Bucket bucket) {}
|
||||||
|
|
||||||
|
private NodeAndBucket getNodeAndBucket(String nodeId, String bucketId) {
|
||||||
|
if (!NamingValidator.validateNodeIdentifier(nodeId) || !NamingValidator.validateBucketIdentifier(bucketId)) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
var nodeOpt = nodeService.findNode(nodeId);
|
||||||
|
if (nodeOpt.isEmpty()) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
var bucketOpt = bucketService.findBucket(Objects.requireNonNull(nodeOpt.get()), bucketId);
|
||||||
|
if (bucketOpt.isEmpty()) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NodeAndBucket(nodeOpt.get(), bucketOpt.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,29 +0,0 @@
|
|||||||
package ru.dragonestia.loadbalancer.controller;
|
|
||||||
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
import ru.dragonestia.loadbalancer.model.Bucket;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/users")
|
|
||||||
public class UserController {
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
Collection<Bucket> linkedBucketsForUser(@RequestParam(name = "user") String userIdentifier) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping
|
|
||||||
String linkUserWithBucket(@RequestParam(name = "user") String userIdentifier,
|
|
||||||
@RequestParam(name = "bucket") String bucketIdentifier,
|
|
||||||
@RequestParam(name = "force") boolean force) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@DeleteMapping
|
|
||||||
String unlinkUsersForBucket(@RequestParam(name = "users") String userIdentifiers,
|
|
||||||
@RequestParam(name = "bucket") String bucketIdentifier) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package ru.dragonestia.loadbalancer.controller.response;
|
||||||
|
|
||||||
|
import ru.dragonestia.loadbalancer.model.User;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record BucketUserListResponse(int slots, int usedSlots, List<User> users) {}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
package ru.dragonestia.loadbalancer.controller.response;
|
||||||
|
|
||||||
|
public record LinkUsersWithBucketResponse(boolean success, String message) {}
|
||||||
@ -26,8 +26,6 @@ public interface BucketRepository {
|
|||||||
|
|
||||||
Optional<Bucket> pickFreeBucket(Node node, Collection<User> users);
|
Optional<Bucket> pickFreeBucket(Node node, Collection<User> users);
|
||||||
|
|
||||||
void freeBucket(Bucket bucket, Collection<User> users);
|
|
||||||
|
|
||||||
void onCreateNode(Node node);
|
void onCreateNode(Node node);
|
||||||
|
|
||||||
void onRemoveNode(Node node);
|
void onRemoveNode(Node node);
|
||||||
|
|||||||
@ -9,11 +9,13 @@ import java.util.Map;
|
|||||||
|
|
||||||
public interface UserRepository {
|
public interface UserRepository {
|
||||||
|
|
||||||
Map<User, Boolean> linkWithBucket(Bucket bucket, Collection<User> users);
|
Map<User, Boolean> linkWithBucket(Bucket bucket, Collection<User> users, boolean force);
|
||||||
|
|
||||||
int unlinkWithBucket(Bucket bucket, Collection<User> users);
|
int unlinkWithBucket(Bucket bucket, Collection<User> users);
|
||||||
|
|
||||||
List<Bucket> findAllLinkedUserBuckets(User user);
|
List<Bucket> findAllLinkedUserBuckets(User user);
|
||||||
|
|
||||||
void onRemoveBucket(Bucket bucket);
|
void onRemoveBucket(Bucket bucket);
|
||||||
|
|
||||||
|
List<User> usersOf(Bucket bucket);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -98,7 +98,7 @@ public class BucketRepositoryImpl implements BucketRepository {
|
|||||||
|
|
||||||
if (container.isPresent()) {
|
if (container.isPresent()) {
|
||||||
var cont = container.get();
|
var cont = container.get();
|
||||||
var addedUsers = userRepository.linkWithBucket(cont.bucket(), users);
|
var addedUsers = userRepository.linkWithBucket(cont.bucket(), users, false);
|
||||||
cont.used().getAndAdd((int) addedUsers.values().stream().filter(Boolean.TRUE::equals).count());
|
cont.used().getAndAdd((int) addedUsers.values().stream().filter(Boolean.TRUE::equals).count());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,30 +106,6 @@ public class BucketRepositoryImpl implements BucketRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void freeBucket(Bucket bucket, Collection<User> users) {
|
|
||||||
var nodeId = bucket.getNodeIdentifier();
|
|
||||||
var node = node2bucketsMap.keySet().stream()
|
|
||||||
.filter(n -> bucket.getNodeIdentifier().equals(n.identifier()))
|
|
||||||
.findFirst();
|
|
||||||
|
|
||||||
synchronized (node2bucketsMap) {
|
|
||||||
if (node.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("Node '" + nodeId + "' does not exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
var buckets = node2bucketsMap.get(node.get());
|
|
||||||
if (!buckets.containsKey(bucket.getIdentifier())) {
|
|
||||||
throw new IllegalArgumentException("Bucket '" + nodeId + "' does not exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
var delta = userRepository.unlinkWithBucket(bucket, users);
|
|
||||||
if (buckets.get(bucket.getIdentifier()).used().getAndAdd(-delta) < 0) {
|
|
||||||
throw new RuntimeException("Bucket has less than 0 users");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateNode(Node node) {
|
public void onCreateNode(Node node) {
|
||||||
synchronized (node2bucketsMap) {
|
synchronized (node2bucketsMap) {
|
||||||
|
|||||||
@ -13,17 +13,37 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
public class UserRepositoryImpl implements UserRepository {
|
public class UserRepositoryImpl implements UserRepository {
|
||||||
|
|
||||||
private final Map<User, Set<Bucket>> usersMap = new ConcurrentHashMap<>();
|
private final Map<User, Set<Bucket>> usersMap = new ConcurrentHashMap<>();
|
||||||
|
private final Map<NodeBucketPath, Set<User>> bucketUsers = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<User, Boolean> linkWithBucket(Bucket bucket, Collection<User> users) {
|
public Map<User, Boolean> linkWithBucket(Bucket bucket, Collection<User> users, boolean force) {
|
||||||
var result = new HashMap<User, Boolean>();
|
var result = new HashMap<User, Boolean>();
|
||||||
|
|
||||||
synchronized (usersMap) {
|
synchronized (usersMap) {
|
||||||
|
var path = new NodeBucketPath(bucket.getNodeIdentifier(), bucket.getIdentifier());
|
||||||
|
var usersSet = bucketUsers.getOrDefault(path, new HashSet<>());
|
||||||
|
|
||||||
|
if (force || bucket.getSlots().isUnlimited()) {
|
||||||
|
users.forEach(user -> result.put(user, true));
|
||||||
|
} else {
|
||||||
|
for (var user : users) {
|
||||||
|
var set = usersMap.getOrDefault(user, new HashSet<>());
|
||||||
|
result.put(user, !set.contains(bucket));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bucket.getSlots().getSlots() < usersSet.size() + users.size()) {
|
||||||
|
throw new Error("Bucket are full");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (var user: users) {
|
for (var user: users) {
|
||||||
var set = usersMap.getOrDefault(user, new HashSet<>());
|
var set = usersMap.getOrDefault(user, new HashSet<>());
|
||||||
result.put(user, set.add(bucket));
|
set.add(bucket);
|
||||||
usersMap.put(user, set);
|
usersMap.put(user, set);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usersSet.addAll(users);
|
||||||
|
bucketUsers.put(path, usersSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -43,6 +63,15 @@ public class UserRepositoryImpl implements UserRepository {
|
|||||||
usersMap.remove(user);
|
usersMap.remove(user);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var path = new NodeBucketPath(bucket.getNodeIdentifier(), bucket.getIdentifier());
|
||||||
|
var set = bucketUsers.getOrDefault(path, new HashSet<>());
|
||||||
|
set.removeAll(users);
|
||||||
|
if (set.isEmpty()) {
|
||||||
|
bucketUsers.remove(path);
|
||||||
|
} else {
|
||||||
|
bucketUsers.put(path, set);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return counter.get();
|
return counter.get();
|
||||||
}
|
}
|
||||||
@ -65,4 +94,31 @@ public class UserRepositoryImpl implements UserRepository {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<User> usersOf(Bucket bucket) {
|
||||||
|
synchronized (usersMap) {
|
||||||
|
return bucketUsers.getOrDefault(new NodeBucketPath(bucket.getNodeIdentifier(), bucket.getIdentifier()), new HashSet<>())
|
||||||
|
.stream()
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record NodeBucketPath(String node, String bucket) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(node, bucket);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == null) return false;
|
||||||
|
if (o == this) return true;
|
||||||
|
if (o instanceof NodeBucketPath other) {
|
||||||
|
return other.node().equals(node()) && other.bucket().equals(bucket());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,4 @@ public interface BucketService {
|
|||||||
int countAvailableBuckets(Node node, int requiredSlots);
|
int countAvailableBuckets(Node node, int requiredSlots);
|
||||||
|
|
||||||
Bucket pickAvailableBucket(Node node, List<User> users);
|
Bucket pickAvailableBucket(Node node, List<User> users);
|
||||||
|
|
||||||
void freeBucket(Bucket bucket, List<User> users);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,9 +3,16 @@ package ru.dragonestia.loadbalancer.service;
|
|||||||
import ru.dragonestia.loadbalancer.model.Bucket;
|
import ru.dragonestia.loadbalancer.model.Bucket;
|
||||||
import ru.dragonestia.loadbalancer.model.User;
|
import ru.dragonestia.loadbalancer.model.User;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface UserService {
|
public interface UserService {
|
||||||
|
|
||||||
List<Bucket> getUserBuckets(User user);
|
List<Bucket> getUserBuckets(User user);
|
||||||
|
|
||||||
|
void linkUsersWithBucket(Bucket bucket, Collection<User> users, boolean force);
|
||||||
|
|
||||||
|
void unlinkUsersFromBucket(Bucket bucket, Collection<User> users);
|
||||||
|
|
||||||
|
List<User> getBucketUsers(Bucket bucket);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,9 +51,4 @@ public class BucketServiceImpl implements BucketService {
|
|||||||
public Bucket pickAvailableBucket(Node node, List<User> users) {
|
public Bucket pickAvailableBucket(Node node, List<User> users) {
|
||||||
throw new RuntimeException("Not implemented");
|
throw new RuntimeException("Not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void freeBucket(Bucket bucket, List<User> users) {
|
|
||||||
throw new RuntimeException("Not implemented");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,38 @@
|
|||||||
|
package ru.dragonestia.loadbalancer.service.impl;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import ru.dragonestia.loadbalancer.model.Bucket;
|
||||||
|
import ru.dragonestia.loadbalancer.model.User;
|
||||||
|
import ru.dragonestia.loadbalancer.repository.UserRepository;
|
||||||
|
import ru.dragonestia.loadbalancer.service.UserService;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Service
|
||||||
|
public class UserServiceImpl implements UserService {
|
||||||
|
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Bucket> getUserBuckets(User user) {
|
||||||
|
return userRepository.findAllLinkedUserBuckets(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void linkUsersWithBucket(Bucket bucket, Collection<User> users, boolean force) {
|
||||||
|
userRepository.linkWithBucket(bucket, users, force);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unlinkUsersFromBucket(Bucket bucket, Collection<User> users) {
|
||||||
|
userRepository.unlinkWithBucket(bucket, users);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<User> getBucketUsers(Bucket bucket) {
|
||||||
|
return userRepository.usersOf(bucket);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,4 +12,8 @@ public class NamingValidator {
|
|||||||
public boolean validateBucketIdentifier(String input) {
|
public boolean validateBucketIdentifier(String input) {
|
||||||
return input.matches("^[a-z\\d-]+$");
|
return input.matches("^[a-z\\d-]+$");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean validateUserIdentifier(String input) {
|
||||||
|
return input.matches("^[aA-zZ\\d-.\\s:/@%?!~$)(+=_|;*]+$");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user