diff --git a/src/main/java/dev/tomr/hcloud/listener/ServerChangeListener.java b/src/main/java/dev/tomr/hcloud/listener/ServerChangeListener.java index af85a78..b434b31 100644 --- a/src/main/java/dev/tomr/hcloud/listener/ServerChangeListener.java +++ b/src/main/java/dev/tomr/hcloud/listener/ServerChangeListener.java @@ -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; @@ -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()); diff --git a/src/main/java/dev/tomr/hcloud/resources/common/Protection.java b/src/main/java/dev/tomr/hcloud/resources/common/Protection.java index 54ae08d..50cf525 100644 --- a/src/main/java/dev/tomr/hcloud/resources/common/Protection.java +++ b/src/main/java/dev/tomr/hcloud/resources/common/Protection.java @@ -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 + + '}'; + } } diff --git a/src/main/java/dev/tomr/hcloud/resources/server/Server.java b/src/main/java/dev/tomr/hcloud/resources/server/Server.java index 725c481..660784c 100644 --- a/src/main/java/dev/tomr/hcloud/resources/server/Server.java +++ b/src/main/java/dev/tomr/hcloud/resources/server/Server.java @@ -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 /** diff --git a/src/main/java/dev/tomr/hcloud/service/action/Action.java b/src/main/java/dev/tomr/hcloud/service/action/Action.java index 18a757b..99ddee4 100644 --- a/src/main/java/dev/tomr/hcloud/service/action/Action.java +++ b/src/main/java/dev/tomr/hcloud/service/action/Action.java @@ -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; diff --git a/src/main/java/dev/tomr/hcloud/service/action/model/ChangeProtectionBody.java b/src/main/java/dev/tomr/hcloud/service/action/model/ChangeProtectionBody.java new file mode 100644 index 0000000..ff19356 --- /dev/null +++ b/src/main/java/dev/tomr/hcloud/service/action/model/ChangeProtectionBody.java @@ -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; + } +} diff --git a/src/main/java/dev/tomr/hcloud/service/action/model/PlacementGroupBody.java b/src/main/java/dev/tomr/hcloud/service/action/model/PlacementGroupBody.java new file mode 100644 index 0000000..a64b6b8 --- /dev/null +++ b/src/main/java/dev/tomr/hcloud/service/action/model/PlacementGroupBody.java @@ -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; + } +} diff --git a/src/main/java/dev/tomr/hcloud/service/server/ServerService.java b/src/main/java/dev/tomr/hcloud/service/server/ServerService.java index 5fc097f..55af3b6 100644 --- a/src/main/java/dev/tomr/hcloud/service/server/ServerService.java +++ b/src/main/java/dev/tomr/hcloud/service/server/ServerService.java @@ -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; @@ -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 hostAndKey = HetznerCloud.getInstance().getHttpDetails(); String httpUrl = String.format("%sservers/%d/actions/%s", hostAndKey.get(0), server.getId(), givenAction.path); AtomicReference 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 completedActionFuture = serviceManager.getActionService().waitForActionToComplete(action).thenApplyAsync((completedAction) -> { if (completedAction == null) { throw new NullPointerException(); @@ -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 newServerMap = new HashMap<>(); List hostAndKey = HetznerCloud.getInstance().getHttpDetails(); diff --git a/src/test/java/dev/tomr/hcloud/listener/ServerChangeListenerTest.java b/src/test/java/dev/tomr/hcloud/listener/ServerChangeListenerTest.java index 9c021da..473a12b 100644 --- a/src/test/java/dev/tomr/hcloud/listener/ServerChangeListenerTest.java +++ b/src/test/java/dev/tomr/hcloud/listener/ServerChangeListenerTest.java @@ -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; diff --git a/src/test/java/dev/tomr/hcloud/resources/server/ServerTest.java b/src/test/java/dev/tomr/hcloud/resources/server/ServerTest.java index a8ff57e..fbeba91 100644 --- a/src/test/java/dev/tomr/hcloud/resources/server/ServerTest.java +++ b/src/test/java/dev/tomr/hcloud/resources/server/ServerTest.java @@ -344,4 +344,79 @@ void callingResetSendsAnEventToTheServerChangeListener() { } } + @Test + @DisplayName("calling addServerToPlacementGroup sends an event to the ServerChangeListener") + void callingAddServerToPlacementGroupSendsAnEventToTheServerChangeListener() { + try (MockedStatic 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 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 = 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 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 = 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 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()); + } + } + } \ No newline at end of file diff --git a/src/test/java/dev/tomr/hcloud/service/action/model/ChangeProtectionBodyTest.java b/src/test/java/dev/tomr/hcloud/service/action/model/ChangeProtectionBodyTest.java new file mode 100644 index 0000000..0d941ec --- /dev/null +++ b/src/test/java/dev/tomr/hcloud/service/action/model/ChangeProtectionBodyTest.java @@ -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()); + } +} diff --git a/src/test/java/dev/tomr/hcloud/service/action/model/PlacementGroupBodyTest.java b/src/test/java/dev/tomr/hcloud/service/action/model/PlacementGroupBodyTest.java new file mode 100644 index 0000000..d7023ad --- /dev/null +++ b/src/test/java/dev/tomr/hcloud/service/action/model/PlacementGroupBodyTest.java @@ -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()); + } +} \ No newline at end of file diff --git a/src/test/java/dev/tomr/hcloud/service/server/ServerServiceTest.java b/src/test/java/dev/tomr/hcloud/service/server/ServerServiceTest.java index 38d8c22..7e245d2 100644 --- a/src/test/java/dev/tomr/hcloud/service/server/ServerServiceTest.java +++ b/src/test/java/dev/tomr/hcloud/service/server/ServerServiceTest.java @@ -1,5 +1,8 @@ package dev.tomr.hcloud.service.server; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; import dev.tomr.hcloud.HetznerCloud; import dev.tomr.hcloud.http.HetznerCloudHttpClient; import dev.tomr.hcloud.http.RequestVerb; @@ -12,6 +15,8 @@ import dev.tomr.hcloud.resources.server.Server; import dev.tomr.hcloud.service.ServiceManager; import dev.tomr.hcloud.service.action.ActionService; +import dev.tomr.hcloud.service.action.model.ChangeProtectionBody; +import dev.tomr.hcloud.service.action.model.PlacementGroupBody; import org.junit.jupiter.api.*; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -587,6 +592,7 @@ void whenActionReturnedFromHetznerIsNullServerServiceThrowsANullPointer() throws hetznerCloudHttpClientMockedStatic.when(HetznerCloudHttpClient::getInstance).thenReturn(hetznerCloudHttpClient); hetznerCloudMockedStatic.when(HetznerCloud::getInstance).thenReturn(hetznerCloud); + hetznerCloudMockedStatic.when(HetznerCloud::getObjectMapper).thenReturn(JsonMapper.builder().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true).build()); when(hetznerCloud.getListenerManager()).thenReturn(listenerManager); when(hetznerCloud.getHttpDetails()).thenReturn(List.of("http://host/", "key1234")); when(serviceManager.getActionService()).thenReturn(actionService); @@ -618,6 +624,7 @@ void deleteServerFromHetznerHandlesException() throws IOException, InterruptedEx hetznerCloudHttpClientMockedStatic.when(HetznerCloudHttpClient::getInstance).thenReturn(hetznerCloudHttpClient); hetznerCloudMockedStatic.when(HetznerCloud::getInstance).thenReturn(hetznerCloud); + hetznerCloudMockedStatic.when(HetznerCloud::getObjectMapper).thenReturn(JsonMapper.builder().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true).build()); when(hetznerCloud.getListenerManager()).thenReturn(listenerManager); when(hetznerCloud.getHttpDetails()).thenReturn(List.of("http://host/", "key1234")); @@ -650,6 +657,7 @@ void shutdownServerCallsHetznerAndTracksTheAction() throws IOException, Interrup hetznerCloudHttpClientMockedStatic.when(HetznerCloudHttpClient::getInstance).thenReturn(hetznerCloudHttpClient); hetznerCloudMockedStatic.when(HetznerCloud::getInstance).thenReturn(hetznerCloud); + hetznerCloudMockedStatic.when(HetznerCloud::getObjectMapper).thenReturn(JsonMapper.builder().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true).build()); when(hetznerCloud.getListenerManager()).thenReturn(listenerManager); when(hetznerCloud.getHttpDetails()).thenReturn(List.of("http://host/", "key1234")); when(serviceManager.getActionService()).thenReturn(actionService); @@ -677,6 +685,7 @@ void shutdownServerHandlesException() throws IOException, InterruptedException, hetznerCloudHttpClientMockedStatic.when(HetznerCloudHttpClient::getInstance).thenReturn(hetznerCloudHttpClient); hetznerCloudMockedStatic.when(HetznerCloud::getInstance).thenReturn(hetznerCloud); + hetznerCloudMockedStatic.when(HetznerCloud::getObjectMapper).thenReturn(JsonMapper.builder().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true).build()); when(hetznerCloud.getListenerManager()).thenReturn(listenerManager); when(hetznerCloud.getHttpDetails()).thenReturn(List.of("http://host/", "key1234")); @@ -706,6 +715,7 @@ void whenShutdownActionReturnedFromHetznerIsNullServerServiceThrowsANullPointer( hetznerCloudHttpClientMockedStatic.when(HetznerCloudHttpClient::getInstance).thenReturn(hetznerCloudHttpClient); hetznerCloudMockedStatic.when(HetznerCloud::getInstance).thenReturn(hetznerCloud); + hetznerCloudMockedStatic.when(HetznerCloud::getObjectMapper).thenReturn(JsonMapper.builder().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true).build()); when(hetznerCloud.getListenerManager()).thenReturn(listenerManager); when(hetznerCloud.getHttpDetails()).thenReturn(List.of("http://host/", "key1234")); when(serviceManager.getActionService()).thenReturn(actionService); @@ -742,6 +752,7 @@ void powerOffServerCallsHetznerAndTracksTheAction() throws IOException, Interrup hetznerCloudHttpClientMockedStatic.when(HetznerCloudHttpClient::getInstance).thenReturn(hetznerCloudHttpClient); hetznerCloudMockedStatic.when(HetznerCloud::getInstance).thenReturn(hetznerCloud); + hetznerCloudMockedStatic.when(HetznerCloud::getObjectMapper).thenReturn(JsonMapper.builder().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true).build()); when(hetznerCloud.getListenerManager()).thenReturn(listenerManager); when(hetznerCloud.getHttpDetails()).thenReturn(List.of("http://host/", "key1234")); when(serviceManager.getActionService()).thenReturn(actionService); @@ -774,6 +785,7 @@ void powerOnServerCallsHetznerAndTracksTheAction() throws IOException, Interrupt hetznerCloudHttpClientMockedStatic.when(HetznerCloudHttpClient::getInstance).thenReturn(hetznerCloudHttpClient); hetznerCloudMockedStatic.when(HetznerCloud::getInstance).thenReturn(hetznerCloud); + hetznerCloudMockedStatic.when(HetznerCloud::getObjectMapper).thenReturn(JsonMapper.builder().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true).build()); when(hetznerCloud.getListenerManager()).thenReturn(listenerManager); when(hetznerCloud.getHttpDetails()).thenReturn(List.of("http://host/", "key1234")); when(serviceManager.getActionService()).thenReturn(actionService); @@ -806,6 +818,7 @@ void RebootServerCallsHetznerAndTracksTheAction() throws IOException, Interrupte hetznerCloudHttpClientMockedStatic.when(HetznerCloudHttpClient::getInstance).thenReturn(hetznerCloudHttpClient); hetznerCloudMockedStatic.when(HetznerCloud::getInstance).thenReturn(hetznerCloud); + hetznerCloudMockedStatic.when(HetznerCloud::getObjectMapper).thenReturn(JsonMapper.builder().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true).build()); when(hetznerCloud.getListenerManager()).thenReturn(listenerManager); when(hetznerCloud.getHttpDetails()).thenReturn(List.of("http://host/", "key1234")); when(serviceManager.getActionService()).thenReturn(actionService); @@ -837,6 +850,7 @@ void ResetServerCallsHetznerAndTracksTheAction() throws IOException, Interrupted action.setFinished(Date.from(Instant.now()).toString()); hetznerCloudHttpClientMockedStatic.when(HetznerCloudHttpClient::getInstance).thenReturn(hetznerCloudHttpClient); + hetznerCloudMockedStatic.when(HetznerCloud::getObjectMapper).thenReturn(JsonMapper.builder().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true).build()); hetznerCloudMockedStatic.when(HetznerCloud::getInstance).thenReturn(hetznerCloud); when(hetznerCloud.getListenerManager()).thenReturn(listenerManager); when(hetznerCloud.getHttpDetails()).thenReturn(List.of("http://host/", "key1234")); @@ -852,4 +866,103 @@ void ResetServerCallsHetznerAndTracksTheAction() throws IOException, Interrupted verify(actionService, times(1)).waitForActionToComplete(any(Action.class)); } } + + @Test + @DisplayName("Add Server to Placement Group calls Hetzner and tracks the action") + void addServerToPlacementGroupCallsHetznerAndTracksTheAction() throws IOException, InterruptedException, IllegalAccessException { + HetznerCloud hetznerCloud = mock(HetznerCloud.class); + HetznerCloudHttpClient hetznerCloudHttpClient = mock(HetznerCloudHttpClient.class); + ListenerManager listenerManager = mock(ListenerManager.class); + ServiceManager serviceManager = mock(ServiceManager.class); + ActionService actionService = mock(ActionService.class); + + try (MockedStatic hetznerCloudMockedStatic = mockStatic(HetznerCloud.class); + MockedStatic hetznerCloudHttpClientMockedStatic = mockStatic(HetznerCloudHttpClient.class)) { + + Action action = new Action(); + action.setFinished(Date.from(Instant.now()).toString()); + + hetznerCloudHttpClientMockedStatic.when(HetznerCloudHttpClient::getInstance).thenReturn(hetznerCloudHttpClient); + hetznerCloudMockedStatic.when(HetznerCloud::getObjectMapper).thenReturn(JsonMapper.builder().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true).build()); + hetznerCloudMockedStatic.when(HetznerCloud::getInstance).thenReturn(hetznerCloud); + when(hetznerCloud.getListenerManager()).thenReturn(listenerManager); + when(hetznerCloud.getHttpDetails()).thenReturn(List.of("http://host/", "key1234")); + when(serviceManager.getActionService()).thenReturn(actionService); + when(actionService.waitForActionToComplete(any(Action.class))).thenReturn(CompletableFuture.completedFuture(action)); + + when(hetznerCloudHttpClient.sendHttpRequest(any(), anyString(), any(RequestVerb.class), anyString(), anyString())).thenReturn(new ActionWrapper(action)); + + ServerService serverService = new ServerService(serviceManager); + serverService.addServerToPlacementGroup(new Server(), 1); + + verify(hetznerCloudHttpClient, times(1)).sendHttpRequest(any(), anyString(), eq(RequestVerb.POST), eq("key1234"), eq(new ObjectMapper().writeValueAsString(new PlacementGroupBody(1)))); + verify(actionService, times(1)).waitForActionToComplete(any(Action.class)); + } + } + + @Test + @DisplayName("Remove Server from a Placement Group calls Hetzner and tracks the action") + void removeServerFromPlacementGroupCallsHetznerAndTracksTheAction() throws IOException, InterruptedException, IllegalAccessException { + HetznerCloud hetznerCloud = mock(HetznerCloud.class); + HetznerCloudHttpClient hetznerCloudHttpClient = mock(HetznerCloudHttpClient.class); + ListenerManager listenerManager = mock(ListenerManager.class); + ServiceManager serviceManager = mock(ServiceManager.class); + ActionService actionService = mock(ActionService.class); + + try (MockedStatic hetznerCloudMockedStatic = mockStatic(HetznerCloud.class); + MockedStatic hetznerCloudHttpClientMockedStatic = mockStatic(HetznerCloudHttpClient.class)) { + + Action action = new Action(); + action.setFinished(Date.from(Instant.now()).toString()); + + hetznerCloudHttpClientMockedStatic.when(HetznerCloudHttpClient::getInstance).thenReturn(hetznerCloudHttpClient); + hetznerCloudMockedStatic.when(HetznerCloud::getObjectMapper).thenReturn(JsonMapper.builder().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true).build()); + hetznerCloudMockedStatic.when(HetznerCloud::getInstance).thenReturn(hetznerCloud); + when(hetznerCloud.getListenerManager()).thenReturn(listenerManager); + when(hetznerCloud.getHttpDetails()).thenReturn(List.of("http://host/", "key1234")); + when(serviceManager.getActionService()).thenReturn(actionService); + when(actionService.waitForActionToComplete(any(Action.class))).thenReturn(CompletableFuture.completedFuture(action)); + + when(hetznerCloudHttpClient.sendHttpRequest(any(), anyString(), any(RequestVerb.class), anyString(), anyString())).thenReturn(new ActionWrapper(action)); + + ServerService serverService = new ServerService(serviceManager); + serverService.removeServerFromPlacementGroup(new Server()); + + verify(hetznerCloudHttpClient, times(1)).sendHttpRequest(any(), anyString(), eq(RequestVerb.POST), eq("key1234"), eq("")); + verify(actionService, times(1)).waitForActionToComplete(any(Action.class)); + } + } + + @Test + @DisplayName("Change Server Protection calls Hetzner and tracks the action") + void changeServerProtectionCallsHetznerAndTracksTheAction() throws IOException, InterruptedException, IllegalAccessException { + HetznerCloud hetznerCloud = mock(HetznerCloud.class); + HetznerCloudHttpClient hetznerCloudHttpClient = mock(HetznerCloudHttpClient.class); + ListenerManager listenerManager = mock(ListenerManager.class); + ServiceManager serviceManager = mock(ServiceManager.class); + ActionService actionService = mock(ActionService.class); + + try (MockedStatic hetznerCloudMockedStatic = mockStatic(HetznerCloud.class); + MockedStatic hetznerCloudHttpClientMockedStatic = mockStatic(HetznerCloudHttpClient.class)) { + + Action action = new Action(); + action.setFinished(Date.from(Instant.now()).toString()); + + hetznerCloudHttpClientMockedStatic.when(HetznerCloudHttpClient::getInstance).thenReturn(hetznerCloudHttpClient); + hetznerCloudMockedStatic.when(HetznerCloud::getObjectMapper).thenReturn(JsonMapper.builder().configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true).build()); + hetznerCloudMockedStatic.when(HetznerCloud::getInstance).thenReturn(hetznerCloud); + when(hetznerCloud.getListenerManager()).thenReturn(listenerManager); + when(hetznerCloud.getHttpDetails()).thenReturn(List.of("http://host/", "key1234")); + when(serviceManager.getActionService()).thenReturn(actionService); + when(actionService.waitForActionToComplete(any(Action.class))).thenReturn(CompletableFuture.completedFuture(action)); + + when(hetznerCloudHttpClient.sendHttpRequest(any(), anyString(), any(RequestVerb.class), anyString(), anyString())).thenReturn(new ActionWrapper(action)); + + ServerService serverService = new ServerService(serviceManager); + serverService.changeServerProtection(new Server(), new Protection(true, false)); + + verify(hetznerCloudHttpClient, times(1)).sendHttpRequest(any(), anyString(), eq(RequestVerb.POST), eq("key1234"), eq(new ObjectMapper().writeValueAsString(new ChangeProtectionBody(true, false)))); + verify(actionService, times(1)).waitForActionToComplete(any(Action.class)); + } + } } \ No newline at end of file