Skip to content

Commit f4e6964

Browse files
committed
feat: Add Reboot action to server
1 parent 920c7eb commit f4e6964

File tree

7 files changed

+86
-7
lines changed

7 files changed

+86
-7
lines changed

src/main/java/dev/tomr/hcloud/listener/ServerChangeListener.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ public void propertyChange(PropertyChangeEvent evt) {
3939
logger.info("Server power on has been called. Instructing Hetzner to power up the server");
4040
HetznerCloud.getInstance().getServiceManager().getServerService().powerOnServer(server);
4141
}
42+
case "reboot" -> {
43+
logger.info("Server reboot has been called. Instructing Hetzner to reboot the server");
44+
}
4245
default -> {
4346
logger.info("Server changed: {}", evt.getPropertyName());
4447
logger.info("Server: {} -> {}", evt.getOldValue(), evt.getNewValue());

src/main/java/dev/tomr/hcloud/resources/server/Server.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ public void delete() {
107107
}
108108

109109
/**
110-
* Sends a Shutdown signal to the server, which will instruct the OS to shut it down. Note that if you <b>must</b>> ensure the server is completely offline, you should also call .powerOff() to ensure the 'plug is pulled'
110+
* Sends a Shutdown request to the server by sending an ACPI request, which will instruct the OS to shut it down. Note that if you <b>must</b>> ensure the server is completely offline, you should also call .powerOff() to ensure the 'plug is pulled'.
111+
* The server OS must support ACPI
111112
*/
112113
public void shutdown() {
113114
propertyChangeSupport.firePropertyChange("shutdown", null, null);
@@ -121,12 +122,18 @@ public void powerOff() {
121122
}
122123

123124
/**
124-
* Starts the Server by turning it's power on
125+
* Starts the Server by turning its power on
125126
*/
126127
public void powerOn() {
127128
propertyChangeSupport.firePropertyChange("poweron", null, null);
128129
}
129130

131+
/**
132+
* Sends a reboot request to the server by sending an ACPI request. The server OS must support ACPI
133+
*/
134+
public void reboot() {
135+
propertyChangeSupport.firePropertyChange("reboot", null, null);
136+
}
130137

131138
// 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
132139

src/main/java/dev/tomr/hcloud/service/action/Action.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
public enum Action {
44
SHUTDOWN("shutdown"),
55
POWEROFF("poweroff"),
6-
POWERON("poweron");
6+
POWERON("poweron"),
7+
REBOOT("reboot");
78

89
public final String path;
910

src/main/java/dev/tomr/hcloud/service/server/ServerService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ public void powerOnServer(Server server) {
139139
sendServerAction(server, POWERON);
140140
}
141141

142+
public void rebootServer(Server server) {
143+
sendServerAction(server, REBOOT);
144+
}
145+
142146
private void sendServerAction(Server server, dev.tomr.hcloud.service.action.Action givenAction) {
143147
List<String> hostAndKey = HetznerCloud.getInstance().getHttpDetails();
144148
String httpUrl = String.format("%sservers/%d/actions/%s", hostAndKey.get(0), server.getId(), givenAction.path);

src/test/java/dev/tomr/hcloud/resources/server/ServerTest.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,6 @@ void callingSetNameSendsAnEventToTheServerChangeListener() {
193193
assertNull(captor.getValue().getOldValue());
194194
assertEquals("test", captor.getValue().getNewValue());
195195
}
196-
197196
}
198197

199198
@Test
@@ -219,7 +218,6 @@ void callingDeleteSendsAnEventToTheServerChangeListener() {
219218
verify(serverChangeListener, times(1)).propertyChange(captor.capture());
220219
assertEquals("delete", captor.getValue().getPropertyName());
221220
}
222-
223221
}
224222

225223
@Test
@@ -245,7 +243,6 @@ void callingShutdownSendsAnEventToTheServerChangeListener() {
245243
verify(serverChangeListener, times(1)).propertyChange(captor.capture());
246244
assertEquals("shutdown", captor.getValue().getPropertyName());
247245
}
248-
249246
}
250247

251248
@Test
@@ -271,7 +268,6 @@ void callingPoweroffSendsAnEventToTheServerChangeListener() {
271268
verify(serverChangeListener, times(1)).propertyChange(captor.capture());
272269
assertEquals("poweroff", captor.getValue().getPropertyName());
273270
}
274-
275271
}
276272

277273
@Test
@@ -297,6 +293,30 @@ void callingPowerOnSendsAnEventToTheServerChangeListener() {
297293
verify(serverChangeListener, times(1)).propertyChange(captor.capture());
298294
assertEquals("poweron", captor.getValue().getPropertyName());
299295
}
296+
}
297+
@Test
298+
@DisplayName("calling reboot sends an event to the ServerChangeListener")
299+
void callingRebootSendsAnEventToTheServerChangeListener() {
300+
try (MockedStatic<HetznerCloud> hetznerCloud = mockStatic(HetznerCloud.class)) {
301+
HetznerCloud hetznerCloudMock = mock(HetznerCloud.class);
302+
ServerChangeListener scl = new ServerChangeListener();
303+
ServerChangeListener serverChangeListener = spy(scl);
304+
ListenerManager listenerManager = mock(ListenerManager.class);
305+
ServiceManager serviceManager = mock(ServiceManager.class);
306+
ServerService serverService = mock(ServerService.class);
307+
ArgumentCaptor<PropertyChangeEvent> captor = ArgumentCaptor.forClass(PropertyChangeEvent.class);
308+
309+
hetznerCloud.when(HetznerCloud::getInstance).thenReturn(hetznerCloudMock);
310+
when(hetznerCloudMock.getListenerManager()).thenReturn(listenerManager);
311+
when(hetznerCloudMock.getServiceManager()).thenReturn(serviceManager);
312+
when(listenerManager.getServerChangeListener()).thenReturn(serverChangeListener);
313+
when(serviceManager.getServerService()).thenReturn(serverService);
300314

315+
Server server = new Server();
316+
server.reboot();
317+
verify(serverChangeListener, times(1)).propertyChange(captor.capture());
318+
assertEquals("reboot", captor.getValue().getPropertyName());
319+
}
301320
}
321+
302322
}

src/test/java/dev/tomr/hcloud/service/action/ActionEnumTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,16 @@ void shutdown() {
1818
void poweroff() {
1919
assertEquals("poweroff", Action.POWEROFF.path);
2020
}
21+
22+
@Test
23+
@DisplayName("POWERON enum returns 'poweron' for the path")
24+
void poweron() {
25+
assertEquals("poweron", Action.POWERON.path);
26+
}
27+
28+
@Test
29+
@DisplayName("REBOOT enum returns 'reboot' for the path")
30+
void reboot() {
31+
assertEquals("reboot", Action.REBOOT.path);
32+
}
2133
}

src/test/java/dev/tomr/hcloud/service/server/ServerServiceTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,4 +821,36 @@ void powerOnServerCallsHetznerAndTracksTheAction() throws IOException, Interrupt
821821
verify(actionService, times(1)).waitForActionToComplete(any(Action.class));
822822
}
823823
}
824+
825+
@Test
826+
@DisplayName("Reboot Server calls Hetzner and tracks the action")
827+
void RebootServerCallsHetznerAndTracksTheAction() throws IOException, InterruptedException, IllegalAccessException {
828+
HetznerCloud hetznerCloud = mock(HetznerCloud.class);
829+
HetznerCloudHttpClient hetznerCloudHttpClient = mock(HetznerCloudHttpClient.class);
830+
ListenerManager listenerManager = mock(ListenerManager.class);
831+
ServiceManager serviceManager = mock(ServiceManager.class);
832+
ActionService actionService = mock(ActionService.class);
833+
834+
try (MockedStatic<HetznerCloud> hetznerCloudMockedStatic = mockStatic(HetznerCloud.class);
835+
MockedStatic<HetznerCloudHttpClient> hetznerCloudHttpClientMockedStatic = mockStatic(HetznerCloudHttpClient.class)) {
836+
837+
Action action = new Action();
838+
action.setFinished(Date.from(Instant.now()).toString());
839+
840+
hetznerCloudHttpClientMockedStatic.when(HetznerCloudHttpClient::getInstance).thenReturn(hetznerCloudHttpClient);
841+
hetznerCloudMockedStatic.when(HetznerCloud::getInstance).thenReturn(hetznerCloud);
842+
when(hetznerCloud.getListenerManager()).thenReturn(listenerManager);
843+
when(hetznerCloud.getHttpDetails()).thenReturn(List.of("http://host/", "key1234"));
844+
when(serviceManager.getActionService()).thenReturn(actionService);
845+
when(actionService.waitForActionToComplete(any(Action.class))).thenReturn(CompletableFuture.completedFuture(action));
846+
847+
when(hetznerCloudHttpClient.sendHttpRequest(any(), anyString(), any(RequestVerb.class), anyString(), anyString())).thenReturn(new ActionWrapper(action));
848+
849+
ServerService serverService = new ServerService(serviceManager);
850+
serverService.rebootServer(new Server());
851+
852+
verify(hetznerCloudHttpClient, times(1)).sendHttpRequest(any(), anyString(), eq(RequestVerb.POST), eq("key1234"), eq(""));
853+
verify(actionService, times(1)).waitForActionToComplete(any(Action.class));
854+
}
855+
}
824856
}

0 commit comments

Comments
 (0)