From fc616dcf56598779081724e53b6fb00d3c3833ce Mon Sep 17 00:00:00 2001 From: Simon Entholzer <33342534+SimonEntholzer@users.noreply.github.com> Date: Sat, 14 Sep 2024 13:24:18 +0200 Subject: [PATCH] Development: Make Telemetry service async (#9287) --- .../core/service/TelemetrySendingService.java | 89 +++++++++++++++++++ .../core/service/TelemetryService.java | 72 ++------------- .../telemetry/TelemetryServiceTest.java | 20 +++-- 3 files changed, 105 insertions(+), 76 deletions(-) create mode 100644 src/main/java/de/tum/cit/aet/artemis/core/service/TelemetrySendingService.java diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/TelemetrySendingService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/TelemetrySendingService.java new file mode 100644 index 000000000000..f3d50f64722c --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/TelemetrySendingService.java @@ -0,0 +1,89 @@ +package de.tum.cit.aet.artemis.core.service; + +import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_SCHEDULING; + +import java.util.Arrays; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Profile; +import org.springframework.core.env.Environment; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; + +@Service +@Profile(PROFILE_SCHEDULING) +public class TelemetrySendingService { + + private static final Logger log = LoggerFactory.getLogger(TelemetrySendingService.class); + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public record TelemetryData(String version, String serverUrl, String operator, String contact, List profiles, String adminName) { + } + + private final Environment env; + + private final RestTemplate restTemplate; + + public TelemetrySendingService(Environment env, RestTemplate restTemplate) { + this.env = env; + this.restTemplate = restTemplate; + } + + @Value("${artemis.version}") + private String version; + + @Value("${server.url}") + private String serverUrl; + + @Value("${info.operatorName}") + private String operator; + + @Value("${info.operatorAdminName}") + private String operatorAdminName; + + @Value("${info.contact}") + private String contact; + + @Value("${artemis.telemetry.sendAdminDetails}") + private boolean sendAdminDetails; + + @Value("${artemis.telemetry.destination}") + private String destination; + + /** + * Assembles the telemetry data, and sends it to the external telemetry server. + * + * @throws Exception if the writing the telemetry data to a json format fails, or the connection to the telemetry server fails + */ + @Async + public void sendTelemetryByPostRequest() throws Exception { + List activeProfiles = Arrays.asList(env.getActiveProfiles()); + TelemetryData telemetryData; + if (sendAdminDetails) { + telemetryData = new TelemetryData(version, serverUrl, operator, contact, activeProfiles, operatorAdminName); + } + else { + telemetryData = new TelemetryData(version, serverUrl, operator, null, activeProfiles, null); + } + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + ObjectWriter objectWriter = new ObjectMapper().writer().withDefaultPrettyPrinter(); + + var telemetryJson = objectWriter.writeValueAsString(telemetryData); + HttpEntity requestEntity = new HttpEntity<>(telemetryJson, headers); + var response = restTemplate.postForEntity(destination + "/api/telemetry", requestEntity, String.class); + log.info("Successfully sent telemetry data. {}", response.getBody()); + } +} diff --git a/src/main/java/de/tum/cit/aet/artemis/core/service/TelemetryService.java b/src/main/java/de/tum/cit/aet/artemis/core/service/TelemetryService.java index 1b50d15131e3..408e6c3dd514 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/service/TelemetryService.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/service/TelemetryService.java @@ -2,71 +2,35 @@ import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_SCHEDULING; -import java.util.Arrays; -import java.util.List; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.annotation.Profile; import org.springframework.context.event.EventListener; -import org.springframework.core.env.Environment; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; -import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectWriter; @Service @Profile(PROFILE_SCHEDULING) public class TelemetryService { - @JsonInclude(JsonInclude.Include.NON_EMPTY) - public record TelemetryData(String version, String serverUrl, String operator, String contact, List profiles, String adminName) { - } - private static final Logger log = LoggerFactory.getLogger(TelemetryService.class); - private final Environment env; - - private final RestTemplate restTemplate; - private final ProfileService profileService; + private final TelemetrySendingService telemetrySendingService; + @Value("${artemis.telemetry.enabled}") public boolean useTelemetry; - @Value("${artemis.telemetry.sendAdminDetails}") - private boolean sendAdminDetails; - @Value("${artemis.telemetry.destination}") private String destination; - @Value("${artemis.version}") - private String version; - - @Value("${server.url}") - private String serverUrl; - - @Value("${info.operatorName}") - private String operator; - - @Value("${info.operatorAdminName}") - private String operatorAdminName; - - @Value("${info.contact}") - private String contact; - - public TelemetryService(Environment env, RestTemplate restTemplate, ProfileService profileService) { - this.env = env; - this.restTemplate = restTemplate; + public TelemetryService(ProfileService profileService, TelemetrySendingService telemetrySendingService) { this.profileService = profileService; + this.telemetrySendingService = telemetrySendingService; } /** @@ -82,7 +46,7 @@ public void sendTelemetry() { log.info("Sending telemetry information"); try { - sendTelemetryByPostRequest(); + telemetrySendingService.sendTelemetryByPostRequest(); } catch (JsonProcessingException e) { log.warn("JsonProcessingException in sendTelemetry.", e); @@ -90,31 +54,5 @@ public void sendTelemetry() { catch (Exception e) { log.warn("Exception in sendTelemetry, with dst URI: {}", destination, e); } - - } - - /** - * Assembles the telemetry data, and sends it to the external telemetry server. - * - * @throws Exception if the writing the telemetry data to a json format fails, or the connection to the telemetry server fails - */ - public void sendTelemetryByPostRequest() throws Exception { - List activeProfiles = Arrays.asList(env.getActiveProfiles()); - TelemetryData telemetryData; - if (sendAdminDetails) { - telemetryData = new TelemetryData(version, serverUrl, operator, contact, activeProfiles, operatorAdminName); - } - else { - telemetryData = new TelemetryData(version, serverUrl, operator, null, activeProfiles, null); - } - - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - ObjectWriter objectWriter = new ObjectMapper().writer().withDefaultPrettyPrinter(); - - var telemetryJson = objectWriter.writeValueAsString(telemetryData); - HttpEntity requestEntity = new HttpEntity<>(telemetryJson, headers); - var response = restTemplate.postForEntity(destination + "/api/telemetry", requestEntity, String.class); - log.info("Successfully sent telemetry data. {}", response.getBody()); } } diff --git a/src/test/java/de/tum/cit/aet/artemis/telemetry/TelemetryServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/telemetry/TelemetryServiceTest.java index f653d48702a1..715456049f53 100644 --- a/src/test/java/de/tum/cit/aet/artemis/telemetry/TelemetryServiceTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/telemetry/TelemetryServiceTest.java @@ -1,10 +1,12 @@ package de.tum.cit.aet.artemis.telemetry; +import static java.util.concurrent.TimeUnit.SECONDS; import static org.mockito.Mockito.spy; import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; import static org.springframework.test.web.client.response.MockRestResponseCreators.withServerError; import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus; +import static org.testcontainers.shaded.org.awaitility.Awaitility.await; import java.net.URI; @@ -29,21 +31,21 @@ @ExtendWith(MockitoExtension.class) class TelemetryServiceTest extends AbstractSpringIntegrationIndependentTest { - @Value("${artemis.telemetry.destination}") - private String destination; - @Autowired private RestTemplate restTemplate; + @Autowired + private TelemetryService telemetryService; + private MockRestServiceServer mockServer; private final ObjectMapper mapper = new ObjectMapper(); - @Autowired - private TelemetryService telemetryService; - private TelemetryService telemetryServiceSpy; + @Value("${artemis.telemetry.destination}") + private String destination; + @BeforeEach void init() { telemetryServiceSpy = spy(telemetryService); @@ -56,7 +58,7 @@ void testSendTelemetry_TelemetryEnabled() throws Exception { mockServer.expect(ExpectedCount.once(), requestTo(new URI(destination + "/api/telemetry"))).andExpect(method(HttpMethod.POST)) .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(mapper.writeValueAsString("Success!"))); telemetryServiceSpy.sendTelemetry(); - mockServer.verify(); + await().atMost(1, SECONDS).untilAsserted(() -> mockServer.verify()); } @Test @@ -65,7 +67,7 @@ void testSendTelemetry_TelemetryDisabled() throws Exception { .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(mapper.writeValueAsString("Success!"))); telemetryServiceSpy.useTelemetry = false; telemetryServiceSpy.sendTelemetry(); - mockServer.verify(); + await().atMost(1, SECONDS).untilAsserted(() -> mockServer.verify()); } @Test @@ -73,6 +75,6 @@ void testSendTelemetry_ExceptionHandling() throws Exception { mockServer.expect(ExpectedCount.once(), requestTo(new URI(destination + "/api/telemetry"))).andExpect(method(HttpMethod.POST)) .andRespond(withServerError().body(mapper.writeValueAsString("Failure!"))); telemetryServiceSpy.sendTelemetry(); - mockServer.verify(); + await().atMost(1, SECONDS).untilAsserted(() -> mockServer.verify()); } }