From b8dc6a24dfb0b4f37e80e9fc26c3f74bd2cf7ff2 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 4 Dec 2023 20:45:29 +0700 Subject: [PATCH] Implemented getting bucket users --- .../loadbalancer/web/component/UserList.java | 44 +++++++ .../loadbalancer/web/model/Bucket.java | 6 + .../web/page/BucketDetailsPage.java | 8 +- .../web/repository/UserRepository.java | 16 +++ .../repository/impl/UserRepositoryImpl.java | 46 +++++++ .../impl/response/BucketUserListResponse.java | 7 ++ .../loadbalancer/config/TestConfig.java | 25 +++- .../controller/UserBucketController.java | 114 ++++++++++++++++++ .../controller/UserController.java | 29 ----- .../response/BucketUserListResponse.java | 7 ++ .../response/LinkUsersWithBucketResponse.java | 3 + .../repository/BucketRepository.java | 2 - .../repository/UserRepository.java | 4 +- .../repository/impl/BucketRepositoryImpl.java | 26 +--- .../repository/impl/UserRepositoryImpl.java | 60 ++++++++- .../loadbalancer/service/BucketService.java | 2 - .../loadbalancer/service/UserService.java | 7 ++ .../service/impl/BucketServiceImpl.java | 5 - .../service/impl/UserServiceImpl.java | 38 ++++++ .../loadbalancer/util/NamingValidator.java | 4 + 20 files changed, 384 insertions(+), 69 deletions(-) create mode 100644 LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/UserList.java create mode 100644 LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/repository/UserRepository.java create mode 100644 LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/repository/impl/UserRepositoryImpl.java create mode 100644 LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/repository/impl/response/BucketUserListResponse.java create mode 100644 src/main/java/ru/dragonestia/loadbalancer/controller/UserBucketController.java delete mode 100644 src/main/java/ru/dragonestia/loadbalancer/controller/UserController.java create mode 100644 src/main/java/ru/dragonestia/loadbalancer/controller/response/BucketUserListResponse.java create mode 100644 src/main/java/ru/dragonestia/loadbalancer/controller/response/LinkUsersWithBucketResponse.java create mode 100644 src/main/java/ru/dragonestia/loadbalancer/service/impl/UserServiceImpl.java diff --git a/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/UserList.java b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/UserList.java new file mode 100644 index 0000000..7ed11b9 --- /dev/null +++ b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/UserList.java @@ -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 usersGrid; + private final Span totalUsers = new Span(); + private final Span occupancy = new Span(); + private List cachedUsers = new ArrayList<>(); + + public UserList(Bucket bucket, List users) { + this.bucket = bucket; + + add(usersGrid = createUsersGrid()); + + update(users); + } + + private Grid createUsersGrid() { + var grid = new Grid(); + 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 users) { + cachedUsers = users; + usersGrid.setItems(users); + totalUsers.setText("Total users: " + users.size()); + occupancy.setText("Occupancy: %s".formatted(bucket.getUsingPercentage(users.size()))); + } +} diff --git a/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/Bucket.java b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/Bucket.java index d8335fc..dbaa7ef 100644 --- a/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/Bucket.java +++ b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/Bucket.java @@ -63,4 +63,10 @@ public class Bucket { public URI createApiURI() { 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) + "%"; + } } diff --git a/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/page/BucketDetailsPage.java b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/page/BucketDetailsPage.java index 2ab5e1e..1a0dccd 100644 --- a/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/page/BucketDetailsPage.java +++ b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/page/BucketDetailsPage.java @@ -18,26 +18,31 @@ import com.vaadin.flow.router.Route; import org.springframework.beans.factory.annotation.Autowired; import ru.dragonestia.loadbalancer.web.component.AddUsers; 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.Node; import ru.dragonestia.loadbalancer.web.repository.BucketRepository; import ru.dragonestia.loadbalancer.web.repository.NodeRepository; +import ru.dragonestia.loadbalancer.web.repository.UserRepository; @Route("/nodes/:nodeId/buckets/:bucketId") public class BucketDetailsPage extends VerticalLayout implements BeforeEnterObserver { private final NodeRepository nodeRepository; private final BucketRepository bucketRepository; + private final UserRepository userRepository; private Node node; private Bucket bucket; private AddUsers addUsers; + private UserList userList; private Button lockBucketButton; private VerticalLayout bucketInfo; @Autowired - public BucketDetailsPage(NodeRepository nodeRepository, BucketRepository bucketRepository) { + public BucketDetailsPage(NodeRepository nodeRepository, BucketRepository bucketRepository, UserRepository userRepository) { this.nodeRepository = nodeRepository; this.bucketRepository = bucketRepository; + this.userRepository = userRepository; } @Override @@ -90,6 +95,7 @@ public class BucketDetailsPage extends VerticalLayout implements BeforeEnterObse add(addUsers = new AddUsers(bucket)); add(new Hr()); add(new H2("Users")); + add(userList = new UserList(bucket, userRepository.all(bucket))); } private void updateBucketInfo() { diff --git a/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/repository/UserRepository.java b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/repository/UserRepository.java new file mode 100644 index 0000000..01cdb9d --- /dev/null +++ b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/repository/UserRepository.java @@ -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 users); + + void unlinkFromBucket(Bucket bucket, Collection users); + + List all(Bucket bucket); +} diff --git a/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/repository/impl/UserRepositoryImpl.java b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/repository/impl/UserRepositoryImpl.java new file mode 100644 index 0000000..2d0cc99 --- /dev/null +++ b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/repository/impl/UserRepositoryImpl.java @@ -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 users) { + // TODO + } + + @Override + public void unlinkFromBucket(Bucket bucket, Collection users) { + // TODO + } + + @Override + public List 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"); + } + } +} diff --git a/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/repository/impl/response/BucketUserListResponse.java b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/repository/impl/response/BucketUserListResponse.java new file mode 100644 index 0000000..22e054c --- /dev/null +++ b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/repository/impl/response/BucketUserListResponse.java @@ -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 users) {} diff --git a/src/main/java/ru/dragonestia/loadbalancer/config/TestConfig.java b/src/main/java/ru/dragonestia/loadbalancer/config/TestConfig.java index a1270ee..88315c0 100644 --- a/src/main/java/ru/dragonestia/loadbalancer/config/TestConfig.java +++ b/src/main/java/ru/dragonestia/loadbalancer/config/TestConfig.java @@ -10,11 +10,16 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import ru.dragonestia.loadbalancer.interceptor.DebugInterceptor; import ru.dragonestia.loadbalancer.model.Bucket; 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.SlotLimit; import ru.dragonestia.loadbalancer.repository.BucketRepository; 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; @Profile("test") @@ -24,6 +29,9 @@ public class TestConfig implements WebMvcConfigurer { private final NodeRepository nodeRepository; private final BucketRepository bucketRepository; + private final UserRepository userRepository; + + private final Random rand = new Random(0); @Override public void addInterceptors(@NonNull InterceptorRegistry registry) { @@ -41,13 +49,26 @@ public class TestConfig implements WebMvcConfigurer { nodeRepository.createNode(node); 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++) { - 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); bucketRepository.createBucket(bucket); } } + + private UUID randomUUID() { + byte[] randomBytes = new byte[16]; + rand.nextBytes(randomBytes); + return UUID.nameUUIDFromBytes(randomBytes); + } } diff --git a/src/main/java/ru/dragonestia/loadbalancer/controller/UserBucketController.java b/src/main/java/ru/dragonestia/loadbalancer/controller/UserBucketController.java new file mode 100644 index 0000000..e06547d --- /dev/null +++ b/src/main/java/ru/dragonestia/loadbalancer/controller/UserBucketController.java @@ -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 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 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(); + 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()); + } +} diff --git a/src/main/java/ru/dragonestia/loadbalancer/controller/UserController.java b/src/main/java/ru/dragonestia/loadbalancer/controller/UserController.java deleted file mode 100644 index ed738b6..0000000 --- a/src/main/java/ru/dragonestia/loadbalancer/controller/UserController.java +++ /dev/null @@ -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 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; - } -} diff --git a/src/main/java/ru/dragonestia/loadbalancer/controller/response/BucketUserListResponse.java b/src/main/java/ru/dragonestia/loadbalancer/controller/response/BucketUserListResponse.java new file mode 100644 index 0000000..da0b124 --- /dev/null +++ b/src/main/java/ru/dragonestia/loadbalancer/controller/response/BucketUserListResponse.java @@ -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 users) {} diff --git a/src/main/java/ru/dragonestia/loadbalancer/controller/response/LinkUsersWithBucketResponse.java b/src/main/java/ru/dragonestia/loadbalancer/controller/response/LinkUsersWithBucketResponse.java new file mode 100644 index 0000000..db453cf --- /dev/null +++ b/src/main/java/ru/dragonestia/loadbalancer/controller/response/LinkUsersWithBucketResponse.java @@ -0,0 +1,3 @@ +package ru.dragonestia.loadbalancer.controller.response; + +public record LinkUsersWithBucketResponse(boolean success, String message) {} diff --git a/src/main/java/ru/dragonestia/loadbalancer/repository/BucketRepository.java b/src/main/java/ru/dragonestia/loadbalancer/repository/BucketRepository.java index d96dcde..ee20e23 100644 --- a/src/main/java/ru/dragonestia/loadbalancer/repository/BucketRepository.java +++ b/src/main/java/ru/dragonestia/loadbalancer/repository/BucketRepository.java @@ -26,8 +26,6 @@ public interface BucketRepository { Optional pickFreeBucket(Node node, Collection users); - void freeBucket(Bucket bucket, Collection users); - void onCreateNode(Node node); void onRemoveNode(Node node); diff --git a/src/main/java/ru/dragonestia/loadbalancer/repository/UserRepository.java b/src/main/java/ru/dragonestia/loadbalancer/repository/UserRepository.java index ae8116f..3840460 100644 --- a/src/main/java/ru/dragonestia/loadbalancer/repository/UserRepository.java +++ b/src/main/java/ru/dragonestia/loadbalancer/repository/UserRepository.java @@ -9,11 +9,13 @@ import java.util.Map; public interface UserRepository { - Map linkWithBucket(Bucket bucket, Collection users); + Map linkWithBucket(Bucket bucket, Collection users, boolean force); int unlinkWithBucket(Bucket bucket, Collection users); List findAllLinkedUserBuckets(User user); void onRemoveBucket(Bucket bucket); + + List usersOf(Bucket bucket); } diff --git a/src/main/java/ru/dragonestia/loadbalancer/repository/impl/BucketRepositoryImpl.java b/src/main/java/ru/dragonestia/loadbalancer/repository/impl/BucketRepositoryImpl.java index a2a7ab0..31a4769 100644 --- a/src/main/java/ru/dragonestia/loadbalancer/repository/impl/BucketRepositoryImpl.java +++ b/src/main/java/ru/dragonestia/loadbalancer/repository/impl/BucketRepositoryImpl.java @@ -98,7 +98,7 @@ public class BucketRepositoryImpl implements BucketRepository { if (container.isPresent()) { 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()); } @@ -106,30 +106,6 @@ public class BucketRepositoryImpl implements BucketRepository { } } - @Override - public void freeBucket(Bucket bucket, Collection 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 public void onCreateNode(Node node) { synchronized (node2bucketsMap) { diff --git a/src/main/java/ru/dragonestia/loadbalancer/repository/impl/UserRepositoryImpl.java b/src/main/java/ru/dragonestia/loadbalancer/repository/impl/UserRepositoryImpl.java index 99e88aa..ff91693 100644 --- a/src/main/java/ru/dragonestia/loadbalancer/repository/impl/UserRepositoryImpl.java +++ b/src/main/java/ru/dragonestia/loadbalancer/repository/impl/UserRepositoryImpl.java @@ -13,17 +13,37 @@ import java.util.concurrent.atomic.AtomicInteger; public class UserRepositoryImpl implements UserRepository { private final Map> usersMap = new ConcurrentHashMap<>(); + private final Map> bucketUsers = new ConcurrentHashMap<>(); @Override - public Map linkWithBucket(Bucket bucket, Collection users) { + public Map linkWithBucket(Bucket bucket, Collection users, boolean force) { var result = new HashMap(); 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) { var set = usersMap.getOrDefault(user, new HashSet<>()); - result.put(user, set.add(bucket)); + set.add(bucket); usersMap.put(user, set); } + + usersSet.addAll(users); + bucketUsers.put(path, usersSet); } return result; @@ -43,6 +63,15 @@ public class UserRepositoryImpl implements UserRepository { 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(); } @@ -65,4 +94,31 @@ public class UserRepositoryImpl implements UserRepository { }); } } + + @Override + public List 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; + } + } } diff --git a/src/main/java/ru/dragonestia/loadbalancer/service/BucketService.java b/src/main/java/ru/dragonestia/loadbalancer/service/BucketService.java index 2da742c..e9a4f91 100644 --- a/src/main/java/ru/dragonestia/loadbalancer/service/BucketService.java +++ b/src/main/java/ru/dragonestia/loadbalancer/service/BucketService.java @@ -24,6 +24,4 @@ public interface BucketService { int countAvailableBuckets(Node node, int requiredSlots); Bucket pickAvailableBucket(Node node, List users); - - void freeBucket(Bucket bucket, List users); } diff --git a/src/main/java/ru/dragonestia/loadbalancer/service/UserService.java b/src/main/java/ru/dragonestia/loadbalancer/service/UserService.java index e2b748e..2ba4306 100644 --- a/src/main/java/ru/dragonestia/loadbalancer/service/UserService.java +++ b/src/main/java/ru/dragonestia/loadbalancer/service/UserService.java @@ -3,9 +3,16 @@ package ru.dragonestia.loadbalancer.service; import ru.dragonestia.loadbalancer.model.Bucket; import ru.dragonestia.loadbalancer.model.User; +import java.util.Collection; import java.util.List; public interface UserService { List getUserBuckets(User user); + + void linkUsersWithBucket(Bucket bucket, Collection users, boolean force); + + void unlinkUsersFromBucket(Bucket bucket, Collection users); + + List getBucketUsers(Bucket bucket); } diff --git a/src/main/java/ru/dragonestia/loadbalancer/service/impl/BucketServiceImpl.java b/src/main/java/ru/dragonestia/loadbalancer/service/impl/BucketServiceImpl.java index 8d8c6a8..7f349c4 100644 --- a/src/main/java/ru/dragonestia/loadbalancer/service/impl/BucketServiceImpl.java +++ b/src/main/java/ru/dragonestia/loadbalancer/service/impl/BucketServiceImpl.java @@ -51,9 +51,4 @@ public class BucketServiceImpl implements BucketService { public Bucket pickAvailableBucket(Node node, List users) { throw new RuntimeException("Not implemented"); } - - @Override - public void freeBucket(Bucket bucket, List users) { - throw new RuntimeException("Not implemented"); - } } diff --git a/src/main/java/ru/dragonestia/loadbalancer/service/impl/UserServiceImpl.java b/src/main/java/ru/dragonestia/loadbalancer/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..8879063 --- /dev/null +++ b/src/main/java/ru/dragonestia/loadbalancer/service/impl/UserServiceImpl.java @@ -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 getUserBuckets(User user) { + return userRepository.findAllLinkedUserBuckets(user); + } + + @Override + public void linkUsersWithBucket(Bucket bucket, Collection users, boolean force) { + userRepository.linkWithBucket(bucket, users, force); + } + + @Override + public void unlinkUsersFromBucket(Bucket bucket, Collection users) { + userRepository.unlinkWithBucket(bucket, users); + } + + @Override + public List getBucketUsers(Bucket bucket) { + return userRepository.usersOf(bucket); + } +} diff --git a/src/main/java/ru/dragonestia/loadbalancer/util/NamingValidator.java b/src/main/java/ru/dragonestia/loadbalancer/util/NamingValidator.java index b71ec04..cb644ce 100644 --- a/src/main/java/ru/dragonestia/loadbalancer/util/NamingValidator.java +++ b/src/main/java/ru/dragonestia/loadbalancer/util/NamingValidator.java @@ -12,4 +12,8 @@ public class NamingValidator { public boolean validateBucketIdentifier(String input) { return input.matches("^[a-z\\d-]+$"); } + + public boolean validateUserIdentifier(String input) { + return input.matches("^[aA-zZ\\d-.\\s:/@%?!~$)(+=_|;*]+$"); + } }