Implemented getting buckets

This commit is contained in:
Andrey Terentev 2023-11-27 01:24:22 +07:00
parent e034bad71e
commit ea5ff314ed
10 changed files with 182 additions and 34 deletions

View File

@ -16,16 +16,19 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.component.textfield.TextField;
import ru.dragonestia.loadbalancer.web.model.Bucket; import ru.dragonestia.loadbalancer.web.model.Bucket;
import ru.dragonestia.loadbalancer.web.repository.BucketRepository;
import java.util.List; import java.util.List;
public class BucketList extends VerticalLayout { public class BucketList extends VerticalLayout {
private final Grid<Bucket> bucketsGrid; private final String nodeIdentifier;
private final Grid<BucketRepository.BucketInfo> bucketsGrid;
private final TextField searchField; private final TextField searchField;
private List<Bucket> cachedBuckets; private List<BucketRepository.BucketInfo> cachedBuckets;
public BucketList(List<Bucket> buckets) { public BucketList(String nodeIdentifier, List<BucketRepository.BucketInfo> buckets) {
this.nodeIdentifier = nodeIdentifier;
cachedBuckets = buckets; cachedBuckets = buckets;
add(new H2("Buckets")); add(new H2("Buckets"));
@ -48,20 +51,20 @@ public class BucketList extends VerticalLayout {
var temp = input.trim(); var temp = input.trim();
bucketsGrid.setItems(cachedBuckets.stream() bucketsGrid.setItems(cachedBuckets.stream()
.filter(bucket -> bucket.getIdentifier().startsWith(temp)) .filter(bucket -> bucket.identifier().startsWith(temp))
.toList()); .toList());
} }
private Grid<Bucket> createGrid() { private Grid<BucketRepository.BucketInfo> createGrid() {
var grid = new Grid<>(Bucket.class, false); var grid = new Grid<>(BucketRepository.BucketInfo.class, false);
grid.addColumn(Bucket::getIdentifier).setHeader("Identifier"); grid.addColumn(BucketRepository.BucketInfo::identifier).setHeader("Identifier");
grid.addComponentColumn(bucket -> { grid.addComponentColumn(bucket -> {
var result = new Span(); var result = new Span();
if (bucket.getSlots().isUnlimited()) { if (bucket.slots() == -1) {
result.setText("Unlimited"); result.setText("Unlimited");
result.getElement().getThemeList().add("badge contrast"); result.getElement().getThemeList().add("badge contrast");
} else { } else {
result.setText(Integer.toString(bucket.getSlots().getSlots())); result.setText(Integer.toString(bucket.slots()));
} }
return result; return result;
}).setHeader("Slots").setTextAlign(ColumnTextAlign.CENTER); }).setHeader("Slots").setTextAlign(ColumnTextAlign.CENTER);
@ -69,7 +72,7 @@ public class BucketList extends VerticalLayout {
return grid; return grid;
} }
private HorizontalLayout createManageButtons(Bucket bucket) { private HorizontalLayout createManageButtons(BucketRepository.BucketInfo bucket) {
var layout = new HorizontalLayout(); var layout = new HorizontalLayout();
{ {
@ -89,16 +92,16 @@ public class BucketList extends VerticalLayout {
return layout; return layout;
} }
private void clickDetailsButton(Bucket bucket) { private void clickDetailsButton(BucketRepository.BucketInfo bucket) {
getUI().ifPresent(ui -> { getUI().ifPresent(ui -> {
ui.navigate("/nodes/" + bucket.getNodeIdentifier() + ui.navigate("/nodes/" + nodeIdentifier +
"/buckets/" + bucket.getIdentifier()); "/buckets/" + bucket.identifier());
}); });
} }
private void clickRemoveButton(Bucket bucket) { private void clickRemoveButton(BucketRepository.BucketInfo bucket) {
var dialog = new Dialog("Confirm bucket deletion"); var dialog = new Dialog("Confirm bucket deletion");
dialog.add(new Paragraph("Confirm that you want to delete bucket. Enter '" + bucket.getIdentifier() + "' to field below and confirm.")); dialog.add(new Paragraph("Confirm that you want to delete bucket. Enter '" + bucket.identifier() + "' to field below and confirm."));
var inputField = new TextField(); var inputField = new TextField();
dialog.add(inputField); dialog.add(inputField);
@ -107,14 +110,14 @@ public class BucketList extends VerticalLayout {
var button = new Button("Confirm"); var button = new Button("Confirm");
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR); button.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
button.addClickListener(event -> { button.addClickListener(event -> {
if (!bucket.getIdentifier().equals(inputField.getValue())) { if (!bucket.identifier().equals(inputField.getValue())) {
Notification.show("Invalid input", 3000, Notification.Position.TOP_END) Notification.show("Invalid input", 3000, Notification.Position.TOP_END)
.addThemeVariants(NotificationVariant.LUMO_ERROR); .addThemeVariants(NotificationVariant.LUMO_ERROR);
return; return;
} }
removeBucket(bucket); removeBucket(bucket);
Notification.show("Bucket '" + bucket.getIdentifier() + "' was successfully removed!", 3000, Notification.Position.TOP_END) Notification.show("Bucket '" + bucket.identifier() + "' was successfully removed!", 3000, Notification.Position.TOP_END)
.addThemeVariants(NotificationVariant.LUMO_SUCCESS); .addThemeVariants(NotificationVariant.LUMO_SUCCESS);
dialog.close(); dialog.close();
}); });
@ -131,12 +134,12 @@ public class BucketList extends VerticalLayout {
dialog.open(); dialog.open();
} }
public void update(List<Bucket> buckets) { public void update(List<BucketRepository.BucketInfo> buckets) {
cachedBuckets = buckets; cachedBuckets = buckets;
applySearch(searchField.getValue()); applySearch(searchField.getValue());
} }
private void removeBucket(Bucket bucket) { private void removeBucket(BucketRepository.BucketInfo bucket) {
// TODO: send remove request and getting list // TODO: send remove request and getting list
} }
} }

View File

@ -20,6 +20,7 @@ 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.model.type.LoadBalancingMethod; import ru.dragonestia.loadbalancer.web.model.type.LoadBalancingMethod;
import ru.dragonestia.loadbalancer.web.model.type.SlotLimit; import ru.dragonestia.loadbalancer.web.model.type.SlotLimit;
import ru.dragonestia.loadbalancer.web.repository.BucketRepository;
import ru.dragonestia.loadbalancer.web.repository.NodeRepository; import ru.dragonestia.loadbalancer.web.repository.NodeRepository;
import java.util.List; import java.util.List;
@ -30,12 +31,16 @@ import java.util.List;
public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserver { public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserver {
private final NodeRepository nodeRepository; private final NodeRepository nodeRepository;
private final BucketRepository bucketRepository;
private Node node; private Node node;
private RegisterBucket registerBucket; private RegisterBucket registerBucket;
private BucketList bucketList; private BucketList bucketList;
public NodeDetailsPage(@Autowired NodeRepository nodeRepository) { public NodeDetailsPage(@Autowired NodeRepository nodeRepository,
@Autowired BucketRepository bucketRepository) {
this.nodeRepository = nodeRepository; this.nodeRepository = nodeRepository;
this.bucketRepository = bucketRepository;
} }
@Override @Override
@ -56,19 +61,12 @@ public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserv
.addThemeVariants(NotificationVariant.LUMO_ERROR); .addThemeVariants(NotificationVariant.LUMO_ERROR);
return; return;
} }
node = nodeOpt.get();
node = nodeOpt.get(); // TODO: getting node initComponents(node, bucketRepository.all(node));
// TODO: getting buckets
initComponents(node, List.of(
Bucket.create("test-1", node, SlotLimit.unlimited(), "Hello world!"),
Bucket.create("test-2", node, SlotLimit.of(12), "Hello world!"),
Bucket.create("test-3", node, SlotLimit.unlimited(), "Hello world!"),
Bucket.create("test-4", node, SlotLimit.of(32), "Hello world!"),
Bucket.create("test-5", node, SlotLimit.of(54), "Hello world!")));
} }
private void initComponents(Node node, List<Bucket> buckets) { private void initComponents(Node node, List<BucketRepository.BucketInfo> buckets) {
printNodeDetails(node); printNodeDetails(node);
add(new Hr()); add(new Hr());
add(registerBucket = new RegisterBucket(node, (bucket) -> { add(registerBucket = new RegisterBucket(node, (bucket) -> {
@ -76,7 +74,7 @@ public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserv
return new RegisterBucket.Response(false, ""); return new RegisterBucket.Response(false, "");
})); }));
add(new Hr()); add(new Hr());
add(bucketList = new BucketList(buckets)); add(bucketList = new BucketList(node.identifier(), buckets));
} }
private void printNodeDetails(Node node) { private void printNodeDetails(Node node) {

View File

@ -0,0 +1,12 @@
package ru.dragonestia.loadbalancer.web.repository;
import ru.dragonestia.loadbalancer.web.model.Node;
import java.util.List;
public interface BucketRepository {
List<BucketInfo> all(Node node);
record BucketInfo(String identifier, int slots) {}
}

View File

@ -0,0 +1,37 @@
package ru.dragonestia.loadbalancer.web.repository.impl;
import com.vaadin.flow.spring.annotation.SpringComponent;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
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.impl.response.BucketListResponse;
import java.net.URI;
import java.util.List;
import java.util.Objects;
@Log4j2
@RequiredArgsConstructor
@SpringComponent
public class BucketRepositoryImpl implements BucketRepository {
private final RestUtil rest;
@Override
public List<BucketInfo> all(Node node) {
var entity = rest.getEntity(URI.create("/nodes/" + node.identifier() + "/buckets"),
BucketListResponse.class);
if (entity.getStatusCode().value() == 404) {
throw new Error("Node with identifier '" + node.identifier() + "' does not exists'");
}
if (!entity.hasBody()) {
throw new Error("Bucket list did not present");
}
return Objects.requireNonNull(entity.getBody()).buckets();
}
}

View File

@ -1,6 +1,7 @@
package ru.dragonestia.loadbalancer.web.repository.impl; package ru.dragonestia.loadbalancer.web.repository.impl;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
@ -24,6 +25,11 @@ public class RestUtil {
return Objects.requireNonNull(template.getForObject(serverUrl.resolve(uri), responseType)); return Objects.requireNonNull(template.getForObject(serverUrl.resolve(uri), responseType));
} }
public <T> ResponseEntity<T> getEntity(URI uri, Class<T> responseType) {
var template = restTemplate.get();
return template.getForEntity(serverUrl.resolve(uri), responseType);
}
public <T> T get(URI uri, Class<T> responseType, Consumer<Map<String, String>> paramsConsumer) { public <T> T get(URI uri, Class<T> responseType, Consumer<Map<String, String>> paramsConsumer) {
var params = new HashMap<String, String>(); var params = new HashMap<String, String>();
paramsConsumer.accept(params); paramsConsumer.accept(params);

View File

@ -0,0 +1,7 @@
package ru.dragonestia.loadbalancer.web.repository.impl.response;
import ru.dragonestia.loadbalancer.web.repository.BucketRepository;
import java.util.List;
public record BucketListResponse(String node, List<BucketRepository.BucketInfo> buckets) {}

View File

@ -8,16 +8,22 @@ import org.springframework.lang.NonNull;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 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.Node; import ru.dragonestia.loadbalancer.model.Node;
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.repository.BucketRepository;
import ru.dragonestia.loadbalancer.repository.NodeRepository; import ru.dragonestia.loadbalancer.repository.NodeRepository;
import java.util.UUID;
@Profile("test") @Profile("test")
@Configuration @Configuration
@RequiredArgsConstructor @RequiredArgsConstructor
public class TestConfig implements WebMvcConfigurer { public class TestConfig implements WebMvcConfigurer {
private final NodeRepository nodeRepository; private final NodeRepository nodeRepository;
private final BucketRepository bucketRepository;
@Override @Override
public void addInterceptors(@NonNull InterceptorRegistry registry) { public void addInterceptors(@NonNull InterceptorRegistry registry) {
@ -26,8 +32,20 @@ public class TestConfig implements WebMvcConfigurer {
@Bean @Bean
void createNodes() { void createNodes() {
nodeRepository.createNode(new Node("game-servers", LoadBalancingMethod.ROUND_ROBIN)); createNodeWithContent(new Node("game-servers", LoadBalancingMethod.ROUND_ROBIN));
nodeRepository.createNode(new Node("game-lobbies", LoadBalancingMethod.LEAST_PICKED)); createNodeWithContent(new Node("game-lobbies", LoadBalancingMethod.LEAST_PICKED));
nodeRepository.createNode(new Node("hub", LoadBalancingMethod.SEQUENTIAL_FILLING)); createNodeWithContent(new Node("hub", LoadBalancingMethod.SEQUENTIAL_FILLING));
}
private void createNodeWithContent(Node node) {
nodeRepository.createNode(node);
for (int i = 1; i <= 5; i++) {
bucketRepository.createBucket(Bucket.create("test-" + i, node, SlotLimit.of(5 * i), "Some payload"));
}
for (int i = 0; i < 5; i++) {
bucketRepository.createBucket(Bucket.create(UUID.randomUUID().toString(), node, SlotLimit.unlimited(), "Some payload"));
}
} }
} }

View File

@ -0,0 +1,30 @@
package ru.dragonestia.loadbalancer.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ru.dragonestia.loadbalancer.controller.response.BucketListResponse;
import ru.dragonestia.loadbalancer.service.BucketService;
import ru.dragonestia.loadbalancer.service.NodeService;
@RestController
@RequestMapping("/nodes/{nodeIdentifier}/buckets")
@RequiredArgsConstructor
public class BucketController {
private final NodeService nodeService;
private final BucketService bucketService;
@GetMapping
ResponseEntity<BucketListResponse> allBuckets(@PathVariable(name = "nodeIdentifier") String nodeId) {
var nodeOpt = nodeService.findNode(nodeId);
return nodeOpt.map(node -> ResponseEntity.ok(new BucketListResponse(nodeId,
bucketService.allBuckets(node).stream()
.map(bucket -> new BucketListResponse.BucketInfo(bucket.getIdentifier(), bucket.getSlots().getSlots()))
.toList()
))).orElseGet(() -> ResponseEntity.notFound().build());
}
}

View File

@ -0,0 +1,29 @@
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;
}
}

View File

@ -0,0 +1,8 @@
package ru.dragonestia.loadbalancer.controller.response;
import java.util.List;
public record BucketListResponse(String node, List<BucketInfo> buckets) {
public record BucketInfo(String identifier, int slots) {}
}