Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Development: Add health indicator api #15

Merged
merged 2 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.tum.cit.artemis.push.apns;

import com.eatthepath.pushy.apns.*;
import com.eatthepath.pushy.apns.server.RejectionReason;
import com.eatthepath.pushy.apns.util.SimpleApnsPayloadBuilder;
import com.eatthepath.pushy.apns.util.SimpleApnsPushNotification;
import com.eatthepath.pushy.apns.util.concurrent.PushNotificationFuture;
Expand All @@ -20,6 +21,7 @@
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.ExecutionException;

@Service
Expand All @@ -38,6 +40,8 @@ public class ApnsSendService implements SendService<NotificationRequest> {

private ApnsClient apnsClient;

private boolean isConnected;

@EventListener(ApplicationReadyEvent.class)
public void applicationReady() {
log.info("apnsCertificatePwd: {}", apnsCertificatePwd);
Expand All @@ -52,8 +56,10 @@ public void applicationReady() {
.setApnsServer(apnsProdEnvironment ? ApnsClientBuilder.PRODUCTION_APNS_HOST : ApnsClientBuilder.DEVELOPMENT_APNS_HOST)
.setClientCredentials(new File(apnsCertificatePath), apnsCertificatePwd)
.build();
isConnected = true;
log.info("Started APNS client successfully!");
} catch (IOException e) {
isConnected = false;
log.error("Could not init APNS service", e);
}
}
Expand Down Expand Up @@ -94,10 +100,14 @@ private ResponseEntity<Void> sendApnsRequest(NotificationRequest request) {
final PushNotificationResponse<SimpleApnsPushNotification> pushNotificationResponse = responsePushNotificationFuture.get();
if (pushNotificationResponse.isAccepted()) {
log.info("Send notification to {}", request.token());
isConnected = true;
return ResponseEntity.ok().build();
} else {
var rejectionReasons = List.of(RejectionReason.BAD_CERTIFICATE.toString(), RejectionReason.BAD_CERTIFICATE_ENVIRONMENT.toString());
if(pushNotificationResponse.getRejectionReason().isPresent() && rejectionReasons.contains(pushNotificationResponse.getRejectionReason().get())) {
isConnected = false;
}
log.error("Notification rejected by the APNs gateway: {}", pushNotificationResponse.getRejectionReason());

pushNotificationResponse.getTokenInvalidationTimestamp().ifPresent(timestamp -> log.error("\t... and the token is invalid as of {}", timestamp));
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).build();
}
Expand All @@ -106,4 +116,9 @@ private ResponseEntity<Void> sendApnsRequest(NotificationRequest request) {
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).build();
}
}

@Override
public boolean isHealthy() {
return isConnected;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@
public interface SendService<T> {

ResponseEntity<Void> send(T request);

boolean isHealthy();
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.FirebaseMessagingException;
import com.google.firebase.messaging.Message;
import com.google.firebase.messaging.MessagingErrorCode;
import de.tum.cit.artemis.push.common.NotificationRequest;
import de.tum.cit.artemis.push.common.SendService;
import org.slf4j.Logger;
Expand All @@ -25,6 +26,8 @@ public class FirebaseSendService implements SendService<List<NotificationRequest

private Optional<FirebaseApp> firebaseApp = Optional.empty();

private boolean isConnected;

public FirebaseSendService() {
try {
FirebaseOptions options = FirebaseOptions
Expand All @@ -34,8 +37,11 @@ public FirebaseSendService() {
.build();

firebaseApp = Optional.of(FirebaseApp.initializeApp(options));

isConnected = true;
} catch (IOException e) {
log.error("Exception while loading Firebase credentials", e);
isConnected = false;
}
}

Expand All @@ -61,11 +67,24 @@ public ResponseEntity<Void> send(List<NotificationRequest> requests) {

try {
FirebaseMessaging.getInstance(firebaseApp.get()).sendEach(batch);
isConnected = true;
} catch (FirebaseMessagingException e) {
// In case the certificate is invalid, the THIRD_PARTY_AUTH_ERROR error code will be returned
var errorCodes = List.of(MessagingErrorCode.THIRD_PARTY_AUTH_ERROR);

if(errorCodes.contains(e.getMessagingErrorCode())) {
isConnected = false;
}

return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).build();
}
}

return ResponseEntity.ok().build();
}

@Override
public boolean isHealthy() {
return isConnected;
}
}
4 changes: 4 additions & 0 deletions hermes/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ dependencies {
tasks.named('test') {
useJUnitPlatform()
}

springBoot {
buildInfo()
}
20 changes: 20 additions & 0 deletions hermes/src/main/java/de/tum/cit/artemis/push/HealthController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package de.tum.cit.artemis.push;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class HealthController {
private final NotificationHealthService healthService;

public HealthController(NotificationHealthService healthService) {
this.healthService = healthService;
}

@GetMapping("/health")
public HealthReport getHealth() {
return healthService.getHealthReport();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package de.tum.cit.artemis.push;

public record HealthReport(boolean isApnsConnected, boolean isFirebaseConnected, String versionNumber) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package de.tum.cit.artemis.push;

import de.tum.cit.artemis.push.apns.ApnsSendService;
import de.tum.cit.artemis.push.firebase.FirebaseSendService;
import org.springframework.boot.info.BuildProperties;
import org.springframework.stereotype.Service;

@Service
public class NotificationHealthService {
private final FirebaseSendService firebaseSendService;
private final ApnsSendService apnsSendService;
private final BuildProperties buildProperties;

NotificationHealthService(FirebaseSendService firebaseSendService, ApnsSendService apnsSendService, BuildProperties buildProperties) {
this.firebaseSendService = firebaseSendService;
this.apnsSendService = apnsSendService;
this.buildProperties = buildProperties;
}

public HealthReport getHealthReport() {
return new HealthReport(apnsSendService.isHealthy(), firebaseSendService.isHealthy(), buildProperties.getVersion());
}
}
Loading