Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/main/java/dev/tomr/hcloud/listener/ServerChangeListener.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.tomr.hcloud.listener;

import dev.tomr.hcloud.HetznerCloud;
import dev.tomr.hcloud.resources.common.Protection;
import dev.tomr.hcloud.resources.server.Server;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand Down Expand Up @@ -48,6 +49,18 @@ public void propertyChange(PropertyChangeEvent evt) {
logger.warn("This is a potentially destructive action!");
HetznerCloud.getInstance().getServiceManager().getServerService().resetServer(server);
}
case "addToPlacementGroup" -> {
logger.info("Add Server to placement group has been called. Instructing Hetzner to add the server to the given placement group {}", evt.getNewValue());
HetznerCloud.getInstance().getServiceManager().getServerService().addServerToPlacementGroup(server, (Integer) evt.getNewValue());
}
case "removeFromPlacementGroup" -> {
logger.info("Remove Server from placement group has been called. Instructing Hetzner to remove the server from a placement group");
HetznerCloud.getInstance().getServiceManager().getServerService().removeServerFromPlacementGroup(server);
}
case "changeServerProtection" -> {
logger.info("Change Server protection has been called. Instructin Hetzner to update the server protection to {}", evt.getNewValue().toString());
HetznerCloud.getInstance().getServiceManager().getServerService().changeServerProtection(server, (Protection) evt.getNewValue());
}
default -> {
logger.info("Server changed: {}", evt.getPropertyName());
logger.info("Server: {} -> {}", evt.getOldValue(), evt.getNewValue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,12 @@ public boolean isRebuild() {
public void setRebuild(boolean rebuild) {
this.rebuild = rebuild;
}

@Override
public String toString() {
return "Protection{" +
"delete=" + delete +
", rebuild=" + rebuild +
'}';
}
}
23 changes: 23 additions & 0 deletions src/main/java/dev/tomr/hcloud/resources/server/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,29 @@ public void reset() {
propertyChangeSupport.firePropertyChange("reset", null, null);
}

/**
* Adds the server to a given placement group
* @param placementGroup The Placement group to add the server to
*/
public void addToPlacementGroup(PlacementGroup placementGroup) {
propertyChangeSupport.firePropertyChange("addToPlacementGroup", null, placementGroup.getId());
}

/**
* Removes the server from any placement groups it may be in
*/
public void removeFromPlacementGroup() {
propertyChangeSupport.firePropertyChange("removeFromPlacementGroup", null, null);
}

/**
* Updates the Protection of a Server
* @param protection The protection values to be applied
*/
public void changeServerProtection(Protection protection) {
propertyChangeSupport.firePropertyChange("changeServerProtection", null, protection);
}

// These are the current setters that will send an API request (PUT /servers) when actions begin to be added, they will also likely be triggered by setters

/**
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/dev/tomr/hcloud/service/action/Action.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ public enum Action {
POWEROFF("poweroff"),
POWERON("poweron"),
REBOOT("reboot"),
RESET("reset"),;
RESET("reset"),
ADD_PLACEMENT_GROUP("add_to_placement_group"),
REMOVE_PLACEMENT_GROUP("remove_from_placement_group"),
CHANGE_SERVER_PROTECTION("change_protection");

public final String path;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package dev.tomr.hcloud.service.action.model;

import dev.tomr.hcloud.http.HetznerJsonObject;

public class ChangeProtectionBody extends HetznerJsonObject {
private boolean delete;
private boolean rebuild;

public ChangeProtectionBody(boolean delete, boolean rebuild) {
this.delete = delete;
this.rebuild = rebuild;
}

public boolean isDelete() {
return delete;
}

public void setDelete(boolean delete) {
this.delete = delete;
}

public boolean isRebuild() {
return rebuild;
}

public void setRebuild(boolean rebuild) {
this.rebuild = rebuild;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package dev.tomr.hcloud.service.action.model;

import dev.tomr.hcloud.http.HetznerJsonObject;

public class PlacementGroupBody extends HetznerJsonObject {
private Integer id;

public PlacementGroupBody(Integer id) {
this.id = id;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}
}
29 changes: 23 additions & 6 deletions src/main/java/dev/tomr/hcloud/service/server/ServerService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@

import dev.tomr.hcloud.HetznerCloud;
import dev.tomr.hcloud.http.HetznerCloudHttpClient;
import dev.tomr.hcloud.http.HetznerJsonObject;
import dev.tomr.hcloud.http.converter.ServerConverterUtil;
import dev.tomr.hcloud.http.model.Action;
import dev.tomr.hcloud.http.model.ActionWrapper;
import dev.tomr.hcloud.http.model.ServerDTO;
import dev.tomr.hcloud.http.model.ServerDTOList;
import dev.tomr.hcloud.resources.common.Protection;
import dev.tomr.hcloud.resources.server.Server;
import dev.tomr.hcloud.service.ServiceManager;
import dev.tomr.hcloud.service.action.model.ChangeProtectionBody;
import dev.tomr.hcloud.service.action.model.PlacementGroupBody;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.IOException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -147,12 +148,24 @@ public void resetServer(Server server) {
sendServerAction(server, RESET);
}

private void sendServerAction(Server server, dev.tomr.hcloud.service.action.Action givenAction) {
public void addServerToPlacementGroup(Server server, Integer serverId) {
sendServerAction(server, ADD_PLACEMENT_GROUP, new PlacementGroupBody(serverId));
}

public void removeServerFromPlacementGroup(Server server) {
sendServerAction(server, REMOVE_PLACEMENT_GROUP);
}

public void changeServerProtection(Server server, Protection protection) {
sendServerAction(server, CHANGE_SERVER_PROTECTION, new ChangeProtectionBody(protection.isDelete(), protection.isRebuild()));
}

private void sendServerAction(Server server, dev.tomr.hcloud.service.action.Action givenAction, HetznerJsonObject body) {
List<String> hostAndKey = HetznerCloud.getInstance().getHttpDetails();
String httpUrl = String.format("%sservers/%d/actions/%s", hostAndKey.get(0), server.getId(), givenAction.path);
AtomicReference<String> exceptionMsg = new AtomicReference<>();
try {
Action action = client.sendHttpRequest(ActionWrapper.class, httpUrl, POST, hostAndKey.get(1), "").getAction();
Action action = client.sendHttpRequest(ActionWrapper.class, httpUrl, POST, hostAndKey.get(1), body != null ? HetznerCloud.getObjectMapper().writeValueAsString(body) : "").getAction();
CompletableFuture<Action> completedActionFuture = serviceManager.getActionService().waitForActionToComplete(action).thenApplyAsync((completedAction) -> {
if (completedAction == null) {
throw new NullPointerException();
Expand All @@ -174,6 +187,10 @@ private void sendServerAction(Server server, dev.tomr.hcloud.service.action.Acti
}
}

private void sendServerAction(Server server, dev.tomr.hcloud.service.action.Action givenAction) {
sendServerAction(server, givenAction, null);
}

private void updateAllRemoteServers() {
Map<Date, Server> newServerMap = new HashMap<>();
List<String> hostAndKey = HetznerCloud.getInstance().getHttpDetails();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.MockedStatic;
import org.mockito.Spy;

import java.beans.PropertyChangeEvent;
import java.util.Map;
Expand Down
75 changes: 75 additions & 0 deletions src/test/java/dev/tomr/hcloud/resources/server/ServerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -344,4 +344,79 @@ void callingResetSendsAnEventToTheServerChangeListener() {
}
}

@Test
@DisplayName("calling addServerToPlacementGroup sends an event to the ServerChangeListener")
void callingAddServerToPlacementGroupSendsAnEventToTheServerChangeListener() {
try (MockedStatic<HetznerCloud> hetznerCloud = mockStatic(HetznerCloud.class)) {
HetznerCloud hetznerCloudMock = mock(HetznerCloud.class);
ServerChangeListener scl = new ServerChangeListener();
ServerChangeListener serverChangeListener = spy(scl);
ListenerManager listenerManager = mock(ListenerManager.class);
ServiceManager serviceManager = mock(ServiceManager.class);
ServerService serverService = mock(ServerService.class);
ArgumentCaptor<PropertyChangeEvent> captor = ArgumentCaptor.forClass(PropertyChangeEvent.class);

hetznerCloud.when(HetznerCloud::getInstance).thenReturn(hetznerCloudMock);
when(hetznerCloudMock.getListenerManager()).thenReturn(listenerManager);
when(hetznerCloudMock.getServiceManager()).thenReturn(serviceManager);
when(listenerManager.getServerChangeListener()).thenReturn(serverChangeListener);
when(serviceManager.getServerService()).thenReturn(serverService);

Server server = new Server();
server.addToPlacementGroup(new PlacementGroup());
verify(serverChangeListener, times(1)).propertyChange(captor.capture());
assertEquals("addToPlacementGroup", captor.getValue().getPropertyName());
}
}

@Test
@DisplayName("calling removeServerFromPlacementGroup sends an event to the ServerChangeListener")
void callingRemoveServerFromPlacementGroupSendsAnEventToTheServerChangeListener() {
try (MockedStatic<HetznerCloud> hetznerCloud = mockStatic(HetznerCloud.class)) {
HetznerCloud hetznerCloudMock = mock(HetznerCloud.class);
ServerChangeListener scl = new ServerChangeListener();
ServerChangeListener serverChangeListener = spy(scl);
ListenerManager listenerManager = mock(ListenerManager.class);
ServiceManager serviceManager = mock(ServiceManager.class);
ServerService serverService = mock(ServerService.class);
ArgumentCaptor<PropertyChangeEvent> captor = ArgumentCaptor.forClass(PropertyChangeEvent.class);

hetznerCloud.when(HetznerCloud::getInstance).thenReturn(hetznerCloudMock);
when(hetznerCloudMock.getListenerManager()).thenReturn(listenerManager);
when(hetznerCloudMock.getServiceManager()).thenReturn(serviceManager);
when(listenerManager.getServerChangeListener()).thenReturn(serverChangeListener);
when(serviceManager.getServerService()).thenReturn(serverService);

Server server = new Server();
server.removeFromPlacementGroup();
verify(serverChangeListener, times(1)).propertyChange(captor.capture());
assertEquals("removeFromPlacementGroup", captor.getValue().getPropertyName());
}
}

@Test
@DisplayName("calling changeServerProtection sends an event to the ServerChangeListener")
void callingChangeServerProtectionSendsAnEventToTheServerChangeListener() {
try (MockedStatic<HetznerCloud> hetznerCloud = mockStatic(HetznerCloud.class)) {
HetznerCloud hetznerCloudMock = mock(HetznerCloud.class);
ServerChangeListener scl = new ServerChangeListener();
ServerChangeListener serverChangeListener = spy(scl);
ListenerManager listenerManager = mock(ListenerManager.class);
ServiceManager serviceManager = mock(ServiceManager.class);
ServerService serverService = mock(ServerService.class);
ArgumentCaptor<PropertyChangeEvent> captor = ArgumentCaptor.forClass(PropertyChangeEvent.class);

hetznerCloud.when(HetznerCloud::getInstance).thenReturn(hetznerCloudMock);
when(hetznerCloudMock.getListenerManager()).thenReturn(listenerManager);
when(hetznerCloudMock.getServiceManager()).thenReturn(serviceManager);
when(listenerManager.getServerChangeListener()).thenReturn(serverChangeListener);
when(serviceManager.getServerService()).thenReturn(serverService);

Server server = new Server();
server.changeServerProtection(new Protection(false, false));
verify(serverChangeListener, times(1)).propertyChange(captor.capture());
assertEquals("changeServerProtection", captor.getValue().getPropertyName());
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package dev.tomr.hcloud.service.action.model;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class ChangeProtectionBodyTest {

@Test
void isDelete() {
ChangeProtectionBody changeProtectionBody = new ChangeProtectionBody(true, true);
assertTrue(changeProtectionBody.isDelete());
}

@Test
void isRebuild() {
ChangeProtectionBody changeProtectionBody = new ChangeProtectionBody(true, true);
assertTrue(changeProtectionBody.isRebuild());
}

@Test
void setDelete() {
ChangeProtectionBody changeProtectionBody = new ChangeProtectionBody(true, true);
changeProtectionBody.setDelete(false);
assertFalse(changeProtectionBody.isDelete());
}

@Test
void setRebuild() {
ChangeProtectionBody changeProtectionBody = new ChangeProtectionBody(true, true);
changeProtectionBody.setRebuild(false);
assertFalse(changeProtectionBody.isRebuild());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package dev.tomr.hcloud.service.action.model;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class PlacementGroupBodyTest {

@Test
void getId() {
PlacementGroupBody pl = new PlacementGroupBody(1);
assertEquals(1, pl.getId());
}

@Test
void setId() {
PlacementGroupBody pl = new PlacementGroupBody(1);
pl.setId(2);
assertEquals(2, pl.getId());
}
}
Loading
Loading