Skip to content
Open
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
10 changes: 10 additions & 0 deletions src/main/java/com/igot/cb/cache/RedisCacheMgr.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.Map;
import java.util.concurrent.TimeUnit;

import com.igot.cb.util.Constants;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

Expand Down Expand Up @@ -142,4 +143,13 @@ public String getFromCache(String key, int ttlSeconds) {
}
}

public boolean isRedisHealthy() {
try (Jedis jedis = jedisPool.getResource()) {
return Constants.REDIS_PONG_RESPONSE.equalsIgnoreCase(jedis.ping());
} catch (Exception e) {
log.error("Redis health check failed", e);
return false;
}
}

}
27 changes: 27 additions & 0 deletions src/main/java/com/igot/cb/health/controller/HealthController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.igot.cb.health.controller;

import com.igot.cb.health.service.HealthService;
import com.igot.cb.model.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class HealthController {

private final HealthService healthService;

@GetMapping("/health")
public ResponseEntity<ApiResponse> healthCheck() {
ApiResponse response = healthService.checkHealthStatus();
return ResponseEntity.status(response.getResponseCode()).body(response);
}

@GetMapping("/liveness")
public ResponseEntity<String> livenessCheck() {
return new ResponseEntity<>("Status ok", HttpStatus.OK);
}
}
9 changes: 9 additions & 0 deletions src/main/java/com/igot/cb/health/service/HealthService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.igot.cb.health.service;

import com.igot.cb.model.ApiResponse;

public interface HealthService {

ApiResponse checkHealthStatus();

}
82 changes: 82 additions & 0 deletions src/main/java/com/igot/cb/health/service/HealthServiceImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.igot.cb.health.service;

import com.igot.cb.cache.RedisCacheMgr;
import com.igot.cb.cassandra.CassandraOperation;
import com.igot.cb.model.ApiResponse;
import com.igot.cb.util.Constants;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
@Slf4j
@RequiredArgsConstructor
public class HealthServiceImpl implements HealthService {

private final CassandraOperation cassandraOperation;

private final RedisCacheMgr redisCacheService;

@Override
public ApiResponse checkHealthStatus() {
ApiResponse response = ApiResponse.createDefaultResponse(Constants.API_HEALTH_CHECK);
try {
response.put(Constants.HEALTHY, true);
List<Map<String, Object>> healthResults = new ArrayList<>();
response.put(Constants.CHECKS, healthResults);
cassandraHealthStatus(response);
redisHealthStatus(response);
} catch (Exception e) {
log.error("Failed to process health check. Exception: ", e);
response.getParams().setStatus(Constants.FAILED);
response.getParams().setErr(e.getMessage());
response.setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR);
}
return response;
}

public void cassandraHealthStatus(ApiResponse response) {
Map<String, Object> result = new HashMap<>();
result.put(Constants.NAME, Constants.CASSANDRA_DB);

boolean isHealthy = false;
try {
List<Map<String, Object>> cassandraQueryResponse = cassandraOperation.getRecordsByProperties(
Constants.KEYSPACE_SUNBIRD, Constants.TABLE_SYSTEM_SETTINGS,
null, null, 1);
isHealthy = !cassandraQueryResponse.isEmpty();
} catch (Exception e) {
log.error("Cassandra health check failed: {}", e.getMessage(), e);
}

result.put(Constants.HEALTHY, isHealthy);
((List<Map<String, Object>>) response.get(Constants.CHECKS)).add(result);
if (!isHealthy) {
response.put(Constants.HEALTHY, false);
}
}

private void redisHealthStatus(ApiResponse response) {

Map<String, Object> result = new HashMap<>();
result.put(Constants.NAME, Constants.REDIS_CACHE);

boolean isHealthy = redisCacheService.isRedisHealthy();

result.put(Constants.HEALTHY, isHealthy);

((List<Map<String, Object>>) response.get(Constants.CHECKS)).add(result);

if (!isHealthy) {
response.put(Constants.HEALTHY, false);
}
}

}

11 changes: 11 additions & 0 deletions src/main/java/com/igot/cb/util/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,17 @@ public class Constants {
public static final String LEAF_NODES = "leafNodes";
public static final String COURSE_UNITS = "courseUnits";

//Health Check
public static final String API_HEALTH_CHECK = "api.health.check";
public static final String HEALTHY = "healthy";
public static final String CHECKS = "checks";
public static final String CASSANDRA_DB = "cassandra db";
public static final String TABLE_SYSTEM_SETTINGS = "system_settings";

// Redis
public static final String REDIS_CACHE = "redis cache";
public static final String REDIS_PONG_RESPONSE = "PONG";

private Constants() {
}
}
25 changes: 25 additions & 0 deletions src/test/java/com/igot/cb/cache/RedisCacheMgrTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import java.util.Map;

import com.igot.cb.util.Constants;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
Expand Down Expand Up @@ -169,4 +170,28 @@ void testPutInCache_exception() {

verify(jedisPool).getResource();
}

@Test
void isRedisHealthy_ShouldReturnTrue_WhenPingSuccessful() {

when(jedisPool.getResource()).thenReturn(jedis);
when(jedis.ping()).thenReturn(Constants.REDIS_PONG_RESPONSE);

boolean result = redisCacheMgr.isRedisHealthy();

assertTrue(result);

verify(jedis).ping();
verify(jedis).close();
}

@Test
void isRedisHealthy_ShouldReturnFalse_WhenExceptionOccurs() {

when(jedisPool.getResource()).thenThrow(new RuntimeException("Redis Down"));

boolean result = redisCacheMgr.isRedisHealthy();

assertFalse(result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.igot.cb.health.controller;

import com.igot.cb.health.service.HealthService;
import com.igot.cb.model.ApiResponse;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class HealthControllerTest {

@Mock
private HealthService healthService;

@InjectMocks
private HealthController healthController;

@Test
void healthCheck_ShouldReturnApiResponse() {

ApiResponse response = new ApiResponse();
response.setResponseCode(HttpStatus.OK);

when(healthService.checkHealthStatus()).thenReturn(response);

ResponseEntity<ApiResponse> result = healthController.healthCheck();

assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals(response, result.getBody());

verify(healthService, times(1)).checkHealthStatus();
}

@Test
void livenessCheck_ShouldReturnStatusOk() {

ResponseEntity<String> response = healthController.livenessCheck();

assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("Status ok", response.getBody());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.igot.cb.health.service;

import com.igot.cb.cache.RedisCacheMgr;
import com.igot.cb.cassandra.CassandraOperation;
import com.igot.cb.model.ApiResponse;
import com.igot.cb.util.Constants;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class HealthServiceImplTest {

@Mock
private CassandraOperation cassandraOperation;

@Mock
private RedisCacheMgr redisCacheService;

@InjectMocks
private HealthServiceImpl healthService;

@Test
void checkHealthStatus_AllServicesHealthy() {

when(redisCacheService.isRedisHealthy()).thenReturn(true);
when(cassandraOperation.getRecordsByProperties(
anyString(), anyString(), any(), any(), anyInt()))
.thenReturn(List.of(Map.of("key", "value")));

ApiResponse response = healthService.checkHealthStatus();

assertTrue((Boolean) response.get(Constants.HEALTHY));

List<Map<String, Object>> checks =
(List<Map<String, Object>>) response.get(Constants.CHECKS);

assertEquals(2, checks.size());
}

@Test
void checkHealthStatus_RedisUnhealthy() {

when(redisCacheService.isRedisHealthy()).thenReturn(false);
when(cassandraOperation.getRecordsByProperties(
anyString(), anyString(), any(), any(), anyInt()))
.thenReturn(List.of(Map.of("key", "value")));

ApiResponse response = healthService.checkHealthStatus();

assertFalse((Boolean) response.get(Constants.HEALTHY));
}

@Test
void checkHealthStatus_CassandraUnhealthy() {

when(redisCacheService.isRedisHealthy()).thenReturn(true);
when(cassandraOperation.getRecordsByProperties(
anyString(), anyString(), any(), any(), anyInt()))
.thenReturn(List.of());

ApiResponse response = healthService.checkHealthStatus();

assertFalse((Boolean) response.get(Constants.HEALTHY));
}
}