Skip to content

Commit

Permalink
Development: Make Telemetry service async (#9287)
Browse files Browse the repository at this point in the history
  • Loading branch information
SimonEntholzer authored Sep 14, 2024
1 parent ab3a249 commit fc616dc
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -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<String> 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<String> 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<String> requestEntity = new HttpEntity<>(telemetryJson, headers);
var response = restTemplate.postForEntity(destination + "/api/telemetry", requestEntity, String.class);
log.info("Successfully sent telemetry data. {}", response.getBody());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> 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;
}

/**
Expand All @@ -82,39 +46,13 @@ public void sendTelemetry() {

log.info("Sending telemetry information");
try {
sendTelemetryByPostRequest();
telemetrySendingService.sendTelemetryByPostRequest();
}
catch (JsonProcessingException e) {
log.warn("JsonProcessingException in sendTelemetry.", e);
}
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<String> 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<String> requestEntity = new HttpEntity<>(telemetryJson, headers);
var response = restTemplate.postForEntity(destination + "/api/telemetry", requestEntity, String.class);
log.info("Successfully sent telemetry data. {}", response.getBody());
}
}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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);
Expand All @@ -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
Expand All @@ -65,14 +67,14 @@ 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
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());
}
}

0 comments on commit fc616dc

Please sign in to comment.