Implemented registration and getting buckets

This commit is contained in:
Andrey Terentev 2023-11-30 21:22:29 +07:00
parent d6d733e8f5
commit 96299218ec
11 changed files with 84 additions and 47 deletions

View File

@ -15,19 +15,21 @@ import com.vaadin.flow.component.notification.NotificationVariant;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout; 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 lombok.Setter;
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;
import java.util.function.Consumer;
public class BucketList extends VerticalLayout { public class BucketList extends VerticalLayout {
private final String nodeIdentifier; private final String nodeIdentifier;
private final Grid<BucketRepository.BucketInfo> bucketsGrid; private final Grid<Bucket> bucketsGrid;
private final TextField searchField; private final TextField searchField;
private List<BucketRepository.BucketInfo> cachedBuckets; private List<Bucket> cachedBuckets;
@Setter private Consumer<Bucket> removeMethod;
public BucketList(String nodeIdentifier, List<BucketRepository.BucketInfo> buckets) { public BucketList(String nodeIdentifier, List<Bucket> buckets) {
this.nodeIdentifier = nodeIdentifier; this.nodeIdentifier = nodeIdentifier;
cachedBuckets = buckets; cachedBuckets = buckets;
@ -51,20 +53,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.identifier().startsWith(temp)) .filter(bucket -> bucket.getIdentifier().startsWith(temp))
.toList()); .toList());
} }
private Grid<BucketRepository.BucketInfo> createGrid() { private Grid<Bucket> createGrid() {
var grid = new Grid<>(BucketRepository.BucketInfo.class, false); var grid = new Grid<>(Bucket.class, false);
grid.addColumn(BucketRepository.BucketInfo::identifier).setHeader("Identifier"); grid.addColumn(Bucket::getIdentifier).setHeader("Identifier");
grid.addComponentColumn(bucket -> { grid.addComponentColumn(bucket -> {
var result = new Span(); var result = new Span();
if (bucket.slots() == -1) { if (bucket.getSlots().isUnlimited()) {
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.slots())); result.setText(Integer.toString(bucket.getSlots().slots()));
} }
return result; return result;
}).setHeader("Slots").setTextAlign(ColumnTextAlign.CENTER); }).setHeader("Slots").setTextAlign(ColumnTextAlign.CENTER);
@ -72,7 +74,7 @@ public class BucketList extends VerticalLayout {
return grid; return grid;
} }
private HorizontalLayout createManageButtons(BucketRepository.BucketInfo bucket) { private HorizontalLayout createManageButtons(Bucket bucket) {
var layout = new HorizontalLayout(); var layout = new HorizontalLayout();
{ {
@ -92,16 +94,16 @@ public class BucketList extends VerticalLayout {
return layout; return layout;
} }
private void clickDetailsButton(BucketRepository.BucketInfo bucket) { private void clickDetailsButton(Bucket bucket) {
getUI().ifPresent(ui -> { getUI().ifPresent(ui -> {
ui.navigate("/nodes/" + nodeIdentifier + ui.navigate("/nodes/" + nodeIdentifier +
"/buckets/" + bucket.identifier()); "/buckets/" + bucket.getIdentifier());
}); });
} }
private void clickRemoveButton(BucketRepository.BucketInfo bucket) { private void clickRemoveButton(Bucket 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.identifier() + "' to field below and confirm.")); dialog.add(new Paragraph("Confirm that you want to delete bucket. Enter '" + bucket.getIdentifier() + "' to field below and confirm."));
var inputField = new TextField(); var inputField = new TextField();
dialog.add(inputField); dialog.add(inputField);
@ -110,14 +112,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.identifier().equals(inputField.getValue())) { if (!bucket.getIdentifier().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.identifier() + "' was successfully removed!", 3000, Notification.Position.TOP_END) Notification.show("Bucket '" + bucket.getIdentifier() + "' was successfully removed!", 3000, Notification.Position.TOP_END)
.addThemeVariants(NotificationVariant.LUMO_SUCCESS); .addThemeVariants(NotificationVariant.LUMO_SUCCESS);
dialog.close(); dialog.close();
}); });
@ -134,12 +136,14 @@ public class BucketList extends VerticalLayout {
dialog.open(); dialog.open();
} }
public void update(List<BucketRepository.BucketInfo> buckets) { public void update(List<Bucket> buckets) {
cachedBuckets = buckets; cachedBuckets = buckets;
applySearch(searchField.getValue()); applySearch(searchField.getValue());
} }
private void removeBucket(BucketRepository.BucketInfo bucket) { private void removeBucket(Bucket bucket) {
// TODO: send remove request and getting list if (removeMethod != null) {
removeMethod.accept(bucket);
}
} }
} }

View File

@ -1,12 +1,13 @@
package ru.dragonestia.loadbalancer.web.model; package ru.dragonestia.loadbalancer.web.model;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import ru.dragonestia.loadbalancer.web.model.type.SlotLimit; import ru.dragonestia.loadbalancer.web.model.type.SlotLimit;
@Getter @Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class Bucket { public class Bucket {
private final String identifier; private final String identifier;
@ -15,8 +16,21 @@ public class Bucket {
private final String payload; private final String payload;
private boolean locked = false; private boolean locked = false;
@JsonCreator
private Bucket(@JsonProperty("identifier") String identifier,
@JsonProperty("nodeIdentifier") String nodeIdentifier,
@JsonProperty("slots") SlotLimit slots,
@JsonProperty("payload") String payload,
@JsonProperty("locked") boolean locked) {
this.identifier = identifier;
this.nodeIdentifier = nodeIdentifier;
this.slots = slots;
this.payload = payload;
this.locked = locked;
}
public static Bucket create(String identifier, Node node, SlotLimit limit, String payload) { public static Bucket create(String identifier, Node node, SlotLimit limit, String payload) {
return new Bucket(identifier, node.identifier(), limit, payload); return new Bucket(identifier, node.identifier(), limit, payload, false);
} }
public void setLocked(boolean value) { public void setLocked(boolean value) {
@ -26,7 +40,7 @@ public class Bucket {
public boolean isAvailable(int usedSlots, int requiredSlots) { public boolean isAvailable(int usedSlots, int requiredSlots) {
if (locked) return false; if (locked) return false;
if (slots.isUnlimited()) return true; if (slots.isUnlimited()) return true;
return slots.getSlots() >= usedSlots + requiredSlots; return slots.slots() >= usedSlots + requiredSlots;
} }
@Override @Override

View File

@ -1,18 +1,11 @@
package ru.dragonestia.loadbalancer.web.model.type; package ru.dragonestia.loadbalancer.web.model.type;
import lombok.Getter; import java.beans.Transient;
@Getter public record SlotLimit(int slots) {
public class SlotLimit {
private final static int UNLIMITED_VALUE = -1; private final static int UNLIMITED_VALUE = -1;
private final int slots;
private SlotLimit(int slots) {
this.slots = slots;
}
public static SlotLimit unlimited() { public static SlotLimit unlimited() {
return new SlotLimit(UNLIMITED_VALUE); return new SlotLimit(UNLIMITED_VALUE);
} }
@ -21,6 +14,7 @@ public class SlotLimit {
return new SlotLimit(slots); return new SlotLimit(slots);
} }
@Transient
public boolean isUnlimited() { public boolean isUnlimited() {
return slots == UNLIMITED_VALUE; return slots == UNLIMITED_VALUE;
} }

View File

@ -50,6 +50,6 @@ public class BucketsPage extends VerticalLayout implements BeforeEnterObserver {
private void printBucketDetails() { private void printBucketDetails() {
add(new Html("<span>Node identifier: <b>" + bucket.getNodeIdentifier() + "</b></span>")); add(new Html("<span>Node identifier: <b>" + bucket.getNodeIdentifier() + "</b></span>"));
add(new Html("<span>Bucket identifier: <b>" + bucket.getIdentifier() + "</b></span>")); add(new Html("<span>Bucket identifier: <b>" + bucket.getIdentifier() + "</b></span>"));
add(new Html("<span>Slots: <b>" + (bucket.getSlots().isUnlimited()? "Unlimited" : bucket.getSlots().getSlots()) + "</b></span>")); add(new Html("<span>Slots: <b>" + (bucket.getSlots().isUnlimited()? "Unlimited" : bucket.getSlots().slots()) + "</b></span>"));
} }
} }

View File

@ -66,7 +66,7 @@ public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserv
initComponents(node, bucketRepository.all(node)); initComponents(node, bucketRepository.all(node));
} }
private void initComponents(Node node, List<BucketRepository.BucketInfo> buckets) { private void initComponents(Node node, List<Bucket> buckets) {
printNodeDetails(node); printNodeDetails(node);
add(new Hr()); add(new Hr());
add(registerBucket = new RegisterBucket(node, (bucket) -> { add(registerBucket = new RegisterBucket(node, (bucket) -> {
@ -81,6 +81,10 @@ public class NodeDetailsPage extends VerticalLayout implements BeforeEnterObserv
})); }));
add(new Hr()); add(new Hr());
add(bucketList = new BucketList(node.identifier(), buckets)); add(bucketList = new BucketList(node.identifier(), buckets));
bucketList.setRemoveMethod(bucket -> {
bucketRepository.remove(bucket);
bucketList.update(bucketRepository.all(node));
});
} }
private void printNodeDetails(Node node) { private void printNodeDetails(Node node) {

View File

@ -7,9 +7,9 @@ import java.util.List;
public interface BucketRepository { public interface BucketRepository {
List<BucketInfo> all(Node node); List<Bucket> all(Node node);
void register(Bucket bucket); void register(Bucket bucket);
record BucketInfo(String identifier, int slots) {} void remove(Bucket bucket);
} }

View File

@ -22,7 +22,7 @@ public class BucketRepositoryImpl implements BucketRepository {
private final RestUtil rest; private final RestUtil rest;
@Override @Override
public List<BucketInfo> all(Node node) { public List<Bucket> all(Node node) {
var entity = rest.getEntity(URI.create("/nodes/" + node.identifier() + "/buckets"), var entity = rest.getEntity(URI.create("/nodes/" + node.identifier() + "/buckets"),
BucketListResponse.class); BucketListResponse.class);
@ -44,12 +44,13 @@ public class BucketRepositoryImpl implements BucketRepository {
BucketRegisterResponse.class, BucketRegisterResponse.class,
params -> { params -> {
params.put("identifier", bucket.getIdentifier()); params.put("identifier", bucket.getIdentifier());
params.put("slots", Integer.toString(bucket.getSlots().getSlots())); params.put("slots", Integer.toString(bucket.getSlots().slots()));
params.put("payload", bucket.getPayload()); params.put("payload", bucket.getPayload());
params.put("locked", Boolean.toString(bucket.isLocked())); params.put("locked", Boolean.toString(bucket.isLocked()));
}); });
if (response.success()) return; if (response.success()) return;
throw new Error(response.message());
} catch (HttpClientErrorException ex) { } catch (HttpClientErrorException ex) {
var response = ex.getResponseBodyAs(BucketRegisterResponse.class); var response = ex.getResponseBodyAs(BucketRegisterResponse.class);
@ -61,4 +62,9 @@ public class BucketRepositoryImpl implements BucketRepository {
throw new Error("Internal error. Check logs"); throw new Error("Internal error. Check logs");
} }
} }
@Override
public void remove(Bucket bucket) {
rest.delete(URI.create("/nodes/" + bucket.getNodeIdentifier() + "/buckets/" + bucket.getIdentifier()), params -> {});
}
} }

View File

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

View File

@ -11,6 +11,7 @@ import ru.dragonestia.loadbalancer.model.Bucket;
import ru.dragonestia.loadbalancer.model.type.SlotLimit; import ru.dragonestia.loadbalancer.model.type.SlotLimit;
import ru.dragonestia.loadbalancer.service.BucketService; import ru.dragonestia.loadbalancer.service.BucketService;
import ru.dragonestia.loadbalancer.service.NodeService; import ru.dragonestia.loadbalancer.service.NodeService;
import ru.dragonestia.loadbalancer.util.NamingValidator;
@Log4j2 @Log4j2
@RestController @RestController
@ -25,9 +26,7 @@ public class BucketController {
ResponseEntity<BucketListResponse> allBuckets(@PathVariable(name = "nodeIdentifier") String nodeId) { ResponseEntity<BucketListResponse> allBuckets(@PathVariable(name = "nodeIdentifier") String nodeId) {
var nodeOpt = nodeService.findNode(nodeId); var nodeOpt = nodeService.findNode(nodeId);
return nodeOpt.map(node -> ResponseEntity.ok(new BucketListResponse(nodeId, return nodeOpt.map(node -> ResponseEntity.ok(new BucketListResponse(nodeId,
bucketService.allBuckets(node).stream() bucketService.allBuckets(node).stream().toList()
.map(bucket -> new BucketListResponse.BucketInfo(bucket.getIdentifier(), bucket.getSlots().getSlots()))
.toList()
))).orElseGet(() -> ResponseEntity.notFound().build()); ))).orElseGet(() -> ResponseEntity.notFound().build());
} }
@ -56,4 +55,18 @@ public class BucketController {
return ResponseEntity.status(500).body(new BucketRegisterResponse(false, ex.getMessage())); return ResponseEntity.status(500).body(new BucketRegisterResponse(false, ex.getMessage()));
} }
} }
@DeleteMapping("/{identifier}")
ResponseEntity<?> removeBucket(@PathVariable("nodeIdentifier") String nodeId,
@PathVariable("identifier") String bucketId) {
if (!NamingValidator.validateNodeIdentifier(nodeId) || !NamingValidator.validateBucketIdentifier(bucketId)) {
return ResponseEntity.ok().build();
}
var nodeOpt = nodeService.findNode(nodeId);
nodeOpt.flatMap(node -> bucketService.findBucket(node, bucketId))
.ifPresent(bucketService::removeBucket);
return ResponseEntity.ok().build();
}
} }

View File

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

View File

@ -2,6 +2,8 @@ package ru.dragonestia.loadbalancer.model.type;
import lombok.Getter; import lombok.Getter;
import java.beans.Transient;
@Getter @Getter
public class SlotLimit { public class SlotLimit {
@ -21,6 +23,7 @@ public class SlotLimit {
return new SlotLimit(slots); return new SlotLimit(slots);
} }
@Transient
public boolean isUnlimited() { public boolean isUnlimited() {
return slots == UNLIMITED_VALUE; return slots == UNLIMITED_VALUE;
} }