From e55a16265ab126355b519fce873046bb756f478e Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Thu, 16 Nov 2023 20:27:20 +0700 Subject: [PATCH] Added Node control page (without implementation) --- .../loadbalancer/web/component/NodeList.java | 44 +++++++ .../web/component/RegisterNode.java | 109 ++++++++++++++++++ .../loadbalancer/web/model/Bucket.java | 46 ++++++++ .../loadbalancer/web/model/Node.java | 22 ++++ .../loadbalancer/web/model/User.java | 21 ++++ .../web/model/type/LoadBalancingMethod.java | 14 +++ .../web/model/type/SlotLimit.java | 27 +++++ .../loadbalancer/web/page/NodesPage.java | 36 ++++++ 8 files changed, 319 insertions(+) create mode 100644 LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/NodeList.java create mode 100644 LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/RegisterNode.java create mode 100644 LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/Bucket.java create mode 100644 LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/Node.java create mode 100644 LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/User.java create mode 100644 LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/type/LoadBalancingMethod.java create mode 100644 LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/type/SlotLimit.java create mode 100644 LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/page/NodesPage.java diff --git a/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/NodeList.java b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/NodeList.java new file mode 100644 index 0000000..efaaa50 --- /dev/null +++ b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/NodeList.java @@ -0,0 +1,44 @@ +package ru.dragonestia.loadbalancer.web.component; + +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import ru.dragonestia.loadbalancer.web.model.Node; + +import java.util.List; + +public class NodeList extends VerticalLayout { + + private final Grid nodesGrid; + + public NodeList(List nodes) { + super(); + + add(new H2("Nodes")); + add(nodesGrid = createGrid()); + + update(nodes); + } + + private Grid createGrid() { + var grid = new Grid<>(Node.class, false); + grid.addColumn(Node::identifier).setHeader("Identifier"); + grid.addColumn(node -> node.method().getName()).setHeader("Mode"); + return grid; + } + + public void update(List nodes) { + nodesGrid.setItems(nodes); + } + + private void registerNode(Node node) { + // TODO: send request for register node and get all nodes + + update(List.of(node)); + } + + public interface OnRegister { + + List register(Node node); + } +} diff --git a/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/RegisterNode.java b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/RegisterNode.java new file mode 100644 index 0000000..d643ff9 --- /dev/null +++ b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/component/RegisterNode.java @@ -0,0 +1,109 @@ +package ru.dragonestia.loadbalancer.web.component; + +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.Unit; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; +import com.vaadin.flow.component.html.H2; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.notification.NotificationVariant; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.radiobutton.RadioButtonGroup; +import com.vaadin.flow.component.radiobutton.RadioGroupVariant; +import com.vaadin.flow.component.textfield.Autocomplete; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.renderer.ComponentRenderer; +import org.springframework.lang.Nullable; +import ru.dragonestia.loadbalancer.web.model.Node; +import ru.dragonestia.loadbalancer.web.model.type.LoadBalancingMethod; + +import java.util.function.Function; + +public class RegisterNode extends VerticalLayout { + + private final Function onSubmit; + private final TextField identifierField; + private final RadioButtonGroup modeRadio; + + public RegisterNode(Function onSubmit) { + this.onSubmit = onSubmit; + + add(new H2("Register node")); + add(identifierField = createNodeIdentifierField()); + add(modeRadio = createModeRadio()); + add(createSubmitButton()); + } + + private TextField createNodeIdentifierField() { + var field = new TextField("Identifier"); + field.setMinWidth(20, Unit.REM); + field.setPlaceholder("example-node-id"); + field.setHelperText("The field can contain only lowercase letters, numbers and a dash character"); + field.setPattern("^[a-z\\d-]+$"); + field.setRequired(true); + field.setAutocomplete(Autocomplete.OFF); + field.addValueChangeListener(event -> field.setValue(event.getValue().trim())); + return field; + } + + private Button createSubmitButton() { + var button = new Button("Register"); + button.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + button.addClickListener(event -> onClick()); + return button; + } + + private RadioButtonGroup createModeRadio() { + var radio = new RadioButtonGroup("Mode"); + radio.addThemeVariants(RadioGroupVariant.LUMO_VERTICAL); + radio.setRenderer(new ComponentRenderer(mode -> new Span(mode.getName()))); + radio.setItems(LoadBalancingMethod.SEQUENTIAL_FILLING, + LoadBalancingMethod.ROUND_ROBIN, + LoadBalancingMethod.LEAST_PICKED); + + radio.setValue(LoadBalancingMethod.SEQUENTIAL_FILLING); + return radio; + } + + public void clear() { + identifierField.clear(); + } + + private @Nullable String validateForm(String identifier) { + if (identifier.isEmpty()) { + return "Node identifier cannot be empty"; + } + + return null; + } + + private void onClick() { + String nodeIdentifier = identifierField.getValue(); + + String error = null; + if (identifierField.isInvalid() || (error = validateForm(nodeIdentifier)) != null) { + if (identifierField.isInvalid()) { + error = "Invalid node identifier format"; + } + + Notification.show(error, 3000, Notification.Position.TOP_END) + .addThemeVariants(NotificationVariant.LUMO_ERROR); + return; + } + + var node = new Node(nodeIdentifier, modeRadio.getValue()); + var response = onSubmit.apply(node); + clear(); + if (response.error()) { + Notification.show(response.reason(), 3000, Notification.Position.TOP_END) + .addThemeVariants(NotificationVariant.LUMO_ERROR); + return; + } + + Notification.show("Node was successfully registered", 3000, Notification.Position.TOP_END) + .addThemeVariants(NotificationVariant.LUMO_SUCCESS); + } + + public record Response(boolean error, @Nullable String reason) {} +} \ No newline at end of file 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 new file mode 100644 index 0000000..8c5e6e1 --- /dev/null +++ b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/Bucket.java @@ -0,0 +1,46 @@ +package ru.dragonestia.loadbalancer.web.model; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import ru.dragonestia.loadbalancer.web.model.type.SlotLimit; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class Bucket { + + private final String identifier; + private final String nodeIdentifier; + private final SlotLimit slots; + private final String payload; + private boolean locked = false; + + public static Bucket create(String identifier, Node node, SlotLimit limit, String payload) { + return new Bucket(identifier, node.identifier(), limit, payload); + } + + public void setLocked(boolean value) { + locked = value; + } + + public boolean isAvailable(int usedSlots, int requiredSlots) { + if (locked) return false; + if (slots.isUnlimited()) return true; + return slots.getSlots() >= usedSlots + requiredSlots; + } + + @Override + public int hashCode() { + return identifier.hashCode(); + } + + @Override + public boolean equals(Object object) { + if (object == this) return true; + if (object == null) return false; + if (object instanceof Bucket other) { + return identifier.equals(other.identifier); + } + return false; + } +} diff --git a/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/Node.java b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/Node.java new file mode 100644 index 0000000..9de58a9 --- /dev/null +++ b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/Node.java @@ -0,0 +1,22 @@ +package ru.dragonestia.loadbalancer.web.model; + +import lombok.NonNull; +import ru.dragonestia.loadbalancer.web.model.type.LoadBalancingMethod; + +public record Node(@NonNull String identifier, @NonNull LoadBalancingMethod method) { + + @Override + public int hashCode() { + return identifier.hashCode(); + } + + @Override + public boolean equals(Object object) { + if (object == this) return true; + if (object == null) return false; + if (object instanceof Node other) { + return identifier.equals(other.identifier); + } + return false; + } +} diff --git a/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/User.java b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/User.java new file mode 100644 index 0000000..1f9d90c --- /dev/null +++ b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/User.java @@ -0,0 +1,21 @@ +package ru.dragonestia.loadbalancer.web.model; + +import lombok.NonNull; + +public record User(@NonNull String identifier) { + + @Override + public int hashCode() { + return identifier.hashCode(); + } + + @Override + public boolean equals(Object object) { + if (object == this) return true; + if (object == null) return false; + if (object instanceof User other) { + return identifier.equals(other.identifier); + } + return false; + } +} diff --git a/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/type/LoadBalancingMethod.java b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/type/LoadBalancingMethod.java new file mode 100644 index 0000000..a92839f --- /dev/null +++ b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/type/LoadBalancingMethod.java @@ -0,0 +1,14 @@ +package ru.dragonestia.loadbalancer.web.model.type; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum LoadBalancingMethod { + SEQUENTIAL_FILLING("Sequential filling"), + ROUND_ROBIN("Round Robin"), + LEAST_PICKED("Least Picked"); + + private final String name; +} diff --git a/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/type/SlotLimit.java b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/type/SlotLimit.java new file mode 100644 index 0000000..8da7618 --- /dev/null +++ b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/model/type/SlotLimit.java @@ -0,0 +1,27 @@ +package ru.dragonestia.loadbalancer.web.model.type; + +import lombok.Getter; + +@Getter +public class SlotLimit { + + private final static int UNLIMITED_VALUE = -1; + + private final int slots; + + private SlotLimit(int slots) { + this.slots = slots; + } + + public static SlotLimit unlimited() { + return new SlotLimit(UNLIMITED_VALUE); + } + + public static SlotLimit of(int slots) { + return new SlotLimit(slots); + } + + public boolean isUnlimited() { + return slots == UNLIMITED_VALUE; + } +} diff --git a/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/page/NodesPage.java b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/page/NodesPage.java new file mode 100644 index 0000000..2ad00de --- /dev/null +++ b/LoadBalancerWeb/src/main/java/ru/dragonestia/loadbalancer/web/page/NodesPage.java @@ -0,0 +1,36 @@ +package ru.dragonestia.loadbalancer.web.page; + +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.Route; +import lombok.Getter; +import ru.dragonestia.loadbalancer.web.component.NodeList; +import ru.dragonestia.loadbalancer.web.component.RegisterNode; + +import java.util.List; + +@Getter +@Route("/nodes") +public class NodesPage extends VerticalLayout { + + private final RegisterNode registerNode; + private final NodeList nodeList; + + public NodesPage() { + super(); + + add(registerNode = createRegisterNodeElement()); + add(nodeList = createNodeListElement()); + } + + protected RegisterNode createRegisterNodeElement() { + return new RegisterNode(node -> { + nodeList.update(List.of(node)); // remove it later + return new RegisterNode.Response(false, "Some error"); // TODO + }); + } + + protected NodeList createNodeListElement() { + // TODO: getting nodes list + return new NodeList(List.of()); + } +}