Skip to content

Commit 4dd6ac0

Browse files
authored
Fix /chaosmonkey/assaults/runtime/attack not responding when executing kill application assault (#528)
2 parents 9c39c7b + deb5ef4 commit 4dd6ac0

File tree

7 files changed

+139
-108
lines changed

7 files changed

+139
-108
lines changed

chaos-monkey-docs/src/main/asciidoc/changes.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Built with Spring Boot {spring-boot-version}
55

66
=== Bug Fixes
77
// - https://github.com/codecentric/chaos-monkey-spring-boot/pull/xxx[#xxx] Added example entry. Please don't remove.
8+
- https://github.com/codecentric/chaos-monkey-spring-boot/pull/528[#528] Fix `/chaosmonkey/assaults/runtime/attack` not responding when executing kill application assault
89

910
=== Improvements
1011
// - https://github.com/codecentric/chaos-monkey-spring-boot/pull/xxx[#xxx] Added example entry. Please don't remove.

chaos-monkey-spring-boot/pom.xml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,6 @@
124124
<groupId>org.springframework.boot</groupId>
125125
<artifactId>spring-boot-starter-test</artifactId>
126126
<scope>test</scope>
127-
<exclusions>
128-
<exclusion>
129-
<groupId>org.hamcrest</groupId>
130-
<artifactId>hamcrest</artifactId>
131-
</exclusion>
132-
</exclusions>
133127
</dependency>
134128
<dependency>
135129
<groupId>org.springframework.boot</groupId>
Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2018-2023 the original author or authors.
2+
* Copyright 2018-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,27 +19,34 @@
1919
import de.codecentric.spring.boot.chaos.monkey.component.MetricType;
2020
import de.codecentric.spring.boot.chaos.monkey.configuration.AssaultProperties;
2121
import de.codecentric.spring.boot.chaos.monkey.configuration.ChaosMonkeySettings;
22-
import java.util.concurrent.TimeUnit;
2322
import org.slf4j.Logger;
2423
import org.slf4j.LoggerFactory;
2524
import org.springframework.boot.SpringApplication;
2625
import org.springframework.context.ApplicationContext;
2726
import org.springframework.context.ApplicationContextAware;
27+
import org.springframework.lang.NonNull;
2828

29-
/** @author Thorsten Deelmann */
30-
public class KillAppAssault implements ChaosMonkeyRuntimeAssault, ApplicationContextAware {
31-
32-
private static final Logger Logger = LoggerFactory.getLogger(KillAppAssault.class);
29+
import java.util.concurrent.TimeUnit;
3330

34-
private final ChaosMonkeySettings settings;
31+
/**
32+
* @author Thorsten Deelmann, Dennis Effing
33+
*/
34+
public class KillAppAssault implements ChaosMonkeyRuntimeAssault {
3535

36-
private ApplicationContext context;
36+
private static final Logger Logger = LoggerFactory.getLogger(KillAppAssault.class);
3737

3838
private final MetricEventPublisher metricEventPublisher;
39+
private final ChaosMonkeySettings settings;
40+
private final ExitHelper exitHelper;
3941

4042
public KillAppAssault(ChaosMonkeySettings settings, MetricEventPublisher metricEventPublisher) {
43+
this(settings, metricEventPublisher, new ExitHelper());
44+
}
45+
46+
public KillAppAssault(ChaosMonkeySettings settings, MetricEventPublisher metricEventPublisher, ExitHelper exitHelper) {
4147
this.settings = settings;
4248
this.metricEventPublisher = metricEventPublisher;
49+
this.exitHelper = exitHelper;
4350
}
4451

4552
@Override
@@ -56,32 +63,50 @@ public void attack() {
5663
metricEventPublisher.publishMetricEvent(MetricType.KILLAPP_ASSAULT);
5764
}
5865

59-
int exit = SpringApplication.exit(context, () -> 0);
60-
61-
long remaining = 5000;
62-
long end = System.currentTimeMillis() + remaining;
63-
while (true) {
64-
try {
65-
TimeUnit.MILLISECONDS.sleep(remaining); // wait before kill to deliver some metrics
66-
break;
67-
} catch (InterruptedException ignored) {
68-
remaining = end - System.currentTimeMillis();
69-
}
70-
}
71-
72-
System.exit(exit);
66+
Thread thread = new Thread(this::killApplication);
67+
thread.setContextClassLoader(this.getClass().getClassLoader());
68+
thread.start();
7369
} catch (Exception e) {
7470
Logger.info("Chaos Monkey - Unable to kill the App, I am not the BOSS!");
7571
}
7672
}
7773

78-
@Override
79-
public void setApplicationContext(ApplicationContext applicationContext) {
80-
this.context = applicationContext;
74+
private void killApplication() {
75+
int exitCode = exitHelper.exitSpringApplication(0);
76+
77+
long remaining = 5000;
78+
long end = System.currentTimeMillis() + remaining;
79+
while (true) {
80+
try {
81+
TimeUnit.MILLISECONDS.sleep(remaining); // wait before kill to deliver some metrics
82+
break;
83+
} catch (InterruptedException ignored) {
84+
remaining = end - System.currentTimeMillis();
85+
}
86+
}
87+
88+
exitHelper.exitJvm(exitCode);
8189
}
8290

8391
@Override
8492
public String getCronExpression(AssaultProperties assaultProperties) {
8593
return assaultProperties.getKillApplicationCronExpression();
8694
}
95+
96+
public static class ExitHelper implements ApplicationContextAware {
97+
private ApplicationContext context;
98+
99+
public int exitSpringApplication(int code) {
100+
return SpringApplication.exit(context, () -> code);
101+
}
102+
103+
public void exitJvm(int code) {
104+
System.exit(code);
105+
}
106+
107+
@Override
108+
public void setApplicationContext(@NonNull ApplicationContext context) {
109+
this.context = context;
110+
}
111+
}
87112
}

chaos-monkey-spring-boot/src/main/java/de/codecentric/spring/boot/chaos/monkey/configuration/ChaosMonkeyConfiguration.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2018-2023 the original author or authors.
2+
* Copyright 2018-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -109,8 +109,14 @@ public ExceptionAssault exceptionAssault(ChaosMonkeySettings settings, MetricEve
109109

110110
@Bean
111111
@ConditionalOnMissingBean
112-
public KillAppAssault killAppAssault(ChaosMonkeySettings settings, MetricEventPublisher publisher) {
113-
return new KillAppAssault(settings, publisher);
112+
public KillAppAssault killAppAssault(ChaosMonkeySettings settings, MetricEventPublisher publisher, KillAppAssault.ExitHelper exitHelper) {
113+
return new KillAppAssault(settings, publisher, exitHelper);
114+
}
115+
116+
@Bean
117+
@ConditionalOnMissingBean
118+
public KillAppAssault.ExitHelper exitHelper() {
119+
return new KillAppAssault.ExitHelper();
114120
}
115121

116122
@Bean
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2018-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package de.codecentric.spring.boot.chaos.monkey.assaults;
17+
18+
import de.codecentric.spring.boot.demo.chaos.monkey.ChaosDemoApplication;
19+
import org.junit.jupiter.api.Test;
20+
import org.springframework.beans.factory.annotation.Autowired;
21+
import org.springframework.boot.test.context.SpringBootTest;
22+
import org.springframework.test.annotation.DirtiesContext;
23+
import org.springframework.test.context.bean.override.mockito.MockitoBean;
24+
25+
import static org.awaitility.Awaitility.await;
26+
import static org.mockito.Mockito.verify;
27+
28+
/**
29+
* @author Thorsten Deelmann, Dennis Effing
30+
*/
31+
@SpringBootTest(classes = ChaosDemoApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {
32+
"chaos.monkey.assaults.kill-application-active=true", "spring.profiles.active=chaos-monkey"})
33+
class KillAppAssaultIntegrationTest {
34+
35+
@Autowired
36+
private KillAppAssault subject;
37+
38+
@MockitoBean
39+
private KillAppAssault.ExitHelper exitHelper;
40+
41+
@Test
42+
@DirtiesContext
43+
void killsSpringBootApplication() {
44+
subject.attack();
45+
46+
await().untilAsserted(() -> verify(exitHelper).exitJvm(0));
47+
}
48+
}

chaos-monkey-spring-boot/src/test/java/de/codecentric/spring/boot/chaos/monkey/assaults/KillAppAssaultTest.java

Lines changed: 0 additions & 69 deletions
This file was deleted.

chaos-monkey-spring-boot/src/test/java/de/codecentric/spring/boot/chaos/monkey/endpoints/ChaosMonkeyRequestScopeRestEndpointIntegrationTest.java

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2018-2024 the original author or authors.
2+
* Copyright 2018-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,7 +18,12 @@
1818
import com.fasterxml.jackson.annotation.JsonInclude;
1919
import com.fasterxml.jackson.core.JsonProcessingException;
2020
import com.fasterxml.jackson.databind.ObjectMapper;
21-
import de.codecentric.spring.boot.chaos.monkey.configuration.*;
21+
import de.codecentric.spring.boot.chaos.monkey.assaults.KillAppAssault;
22+
import de.codecentric.spring.boot.chaos.monkey.configuration.AssaultException;
23+
import de.codecentric.spring.boot.chaos.monkey.configuration.AssaultProperties;
24+
import de.codecentric.spring.boot.chaos.monkey.configuration.ChaosMonkeyProperties;
25+
import de.codecentric.spring.boot.chaos.monkey.configuration.ChaosMonkeySettings;
26+
import de.codecentric.spring.boot.chaos.monkey.configuration.WatcherProperties;
2227
import de.codecentric.spring.boot.chaos.monkey.endpoints.dto.AssaultPropertiesUpdate;
2328
import de.codecentric.spring.boot.chaos.monkey.endpoints.dto.ChaosMonkeyStatusResponseDto;
2429
import de.codecentric.spring.boot.chaos.monkey.endpoints.dto.WatcherPropertiesUpdate;
@@ -31,8 +36,14 @@
3136
import org.springframework.boot.test.context.SpringBootTest;
3237
import org.springframework.boot.test.web.client.TestRestTemplate;
3338
import org.springframework.boot.test.web.server.LocalServerPort;
34-
import org.springframework.http.*;
39+
import org.springframework.http.HttpEntity;
40+
import org.springframework.http.HttpHeaders;
41+
import org.springframework.http.HttpStatus;
42+
import org.springframework.http.MediaType;
43+
import org.springframework.http.ResponseEntity;
44+
import org.springframework.test.annotation.DirtiesContext;
3545
import org.springframework.test.context.TestPropertySource;
46+
import org.springframework.test.context.bean.override.mockito.MockitoBean;
3647

3748
import java.time.OffsetDateTime;
3849
import java.util.Collections;
@@ -46,14 +57,19 @@
4657
@SpringBootTest(classes = ChaosDemoApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
4758
@TestPropertySource("classpath:application-test-chaos-monkey-profile.properties")
4859
class ChaosMonkeyRequestScopeRestEndpointIntegrationTest {
60+
61+
private final ObjectMapper objectMapper = new ObjectMapper();
62+
63+
// NB: We don't want to kill the JVM the tests are running in
64+
@MockitoBean
65+
private KillAppAssault.ExitHelper exitHelper;
66+
4967
@Autowired
5068
private ChaosMonkeySettings chaosMonkeySettings;
5169

5270
@Autowired
5371
private TestRestTemplate testRestTemplate;
5472

55-
private final ObjectMapper objectMapper = new ObjectMapper();
56-
5773
private String baseUrl;
5874

5975
@BeforeEach
@@ -293,6 +309,16 @@ void postToDisableAfterEnableChaosMonkey() {
293309
assertThat(Objects.requireNonNull(result.getBody()).getDisabledAt()).isAfterOrEqualTo(disabledAt);
294310
}
295311

312+
@Test
313+
void postToAttackRespondsIfKillAppAssaultIsConfigured() {
314+
chaosMonkeySettings.getChaosMonkeyProperties().setEnabled(true);
315+
chaosMonkeySettings.getAssaultProperties().setKillApplicationActive(true);
316+
317+
ResponseEntity<String> result = testRestTemplate.postForEntity(baseUrl + "/assaults/runtime/attack", null, String.class);
318+
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
319+
assertThat(result.getBody()).isEqualTo("Started runtime assaults");
320+
}
321+
296322
private ResponseEntity<String> postChaosMonkeySettings(ChaosMonkeySettings chaosMonkeySettings) {
297323

298324
return this.testRestTemplate.postForEntity(baseUrl, chaosMonkeySettings, String.class);

0 commit comments

Comments
 (0)