diff --git a/.gitignore b/.gitignore index 6f1e7f4f..c491e959 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ terraform.tfstate* ### .DS_Store + +# VSCode +.vscode/ diff --git a/common/swagger/v1/jdbc-swagger.yaml b/common/swagger/v1/jdbc-swagger.yaml index f3ecd121..3d42f8ed 100644 --- a/common/swagger/v1/jdbc-swagger.yaml +++ b/common/swagger/v1/jdbc-swagger.yaml @@ -49,6 +49,20 @@ paths: description: Successful operation schema: $ref: '#/definitions/Model' + '/readyz': + get: + tags: + - health + summary: Readiness check + description: Checks if the model schema can be retrieved successfully. + operationId: getReadyz + produces: + - application/json + responses: + '200': + description: Model is ready + '500': + description: Model not ready '/model/score': post: tags: diff --git a/common/swagger/v1/swagger.yaml b/common/swagger/v1/swagger.yaml index be87a7b8..15bccdab 100644 --- a/common/swagger/v1/swagger.yaml +++ b/common/swagger/v1/swagger.yaml @@ -49,6 +49,21 @@ paths: description: Successful operation schema: $ref: '#/definitions/Model' + '/readyz': + get: + tags: + - health + summary: Readiness check + description: Checks if the model schema can be retrieved successfully. + operationId: getReadyz + produces: + - application/json + responses: + '200': + description: Model is ready + '500': + description: Model not ready + '/model/sample_request': get: tags: diff --git a/common/swagger/v1openapi3/swagger.json b/common/swagger/v1openapi3/swagger.json index 67e979eb..2fe46371 100644 --- a/common/swagger/v1openapi3/swagger.json +++ b/common/swagger/v1openapi3/swagger.json @@ -67,6 +67,38 @@ } } }, + "/readyz": { + "get": { + "tags": [ + "heath" + ], + "summary": "Readiness check", + "description": "Checks if the model schema can be retrieved successfully.", + "operationId": "getReadyz", + "responses": { + "200": { + "description": "Model is ready", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "500": { + "description": "Model not ready", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/model/sample_request": { "get": { "tags": [ diff --git a/common/swagger/v1openapi3/swagger.yaml b/common/swagger/v1openapi3/swagger.yaml index ee4e18bc..add8cd69 100644 --- a/common/swagger/v1openapi3/swagger.yaml +++ b/common/swagger/v1openapi3/swagger.yaml @@ -44,6 +44,26 @@ paths: application/json: schema: $ref: '#/components/schemas/Model' + /readyz: + get: + tags: + - health + summary: Readiness check + description: Checks if the model schema can be retrieved successfully. + operationId: getReadyz + responses: + "200": + description: Model is ready + content: + application/json: + schema: + type: string + "500": + description: Model not ready + content: + application/json: + schema: + type: string /model/sample_request: get: tags: diff --git a/local-rest-scorer/build.gradle b/local-rest-scorer/build.gradle index a8dcbe9a..e9fa5bde 100644 --- a/local-rest-scorer/build.gradle +++ b/local-rest-scorer/build.gradle @@ -1,6 +1,8 @@ plugins { id 'org.springframework.boot' + id 'com.diffplug.spotless' version '6.22.0' } + apply from: project(":").file('gradle/java.gradle') dependencies { @@ -57,3 +59,11 @@ rootProject.distributionZip { from bootJar.archivePath } } + +// Spotless Configuration +spotless { + java { + googleJavaFormat() + target 'src/**/*.java' + } +} diff --git a/local-rest-scorer/src/main/java/ai/h2o/mojos/deploy/local/rest/controller/ModelsApiController.java b/local-rest-scorer/src/main/java/ai/h2o/mojos/deploy/local/rest/controller/ModelsApiController.java index 10d28128..3a722162 100644 --- a/local-rest-scorer/src/main/java/ai/h2o/mojos/deploy/local/rest/controller/ModelsApiController.java +++ b/local-rest-scorer/src/main/java/ai/h2o/mojos/deploy/local/rest/controller/ModelsApiController.java @@ -20,7 +20,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; diff --git a/local-rest-scorer/src/main/java/ai/h2o/mojos/deploy/local/rest/controller/ReadyzApiController.java b/local-rest-scorer/src/main/java/ai/h2o/mojos/deploy/local/rest/controller/ReadyzApiController.java new file mode 100644 index 00000000..1c2d95b1 --- /dev/null +++ b/local-rest-scorer/src/main/java/ai/h2o/mojos/deploy/local/rest/controller/ReadyzApiController.java @@ -0,0 +1,32 @@ +package ai.h2o.mojos.deploy.local.rest.controller; + +import ai.h2o.mojos.deploy.common.rest.api.ReadyzApi; +import ai.h2o.mojos.deploy.common.rest.model.ModelSchema; +import ai.h2o.mojos.deploy.common.transform.MojoScorer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class ReadyzApiController implements ReadyzApi { + + private final MojoScorer scorer; + private static final Logger log = LoggerFactory.getLogger(ReadyzApiController.class); + + public ReadyzApiController(MojoScorer scorer) { + this.scorer = scorer; + } + + @Override + public ResponseEntity getReadyz() { + try { + ModelSchema modelSchema = scorer.getModelInfo().getSchema(); + log.trace("model is ready: {}", modelSchema); + return ResponseEntity.ok("Ready"); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Not ready"); + } + } +} diff --git a/local-rest-scorer/src/main/resources/application.properties b/local-rest-scorer/src/main/resources/application.properties index 332ceeb8..35ffcfef 100644 --- a/local-rest-scorer/src/main/resources/application.properties +++ b/local-rest-scorer/src/main/resources/application.properties @@ -1,2 +1,3 @@ logging.level.ai.h2o.mojos.deploy.common.transform.MojoScorer=${LOGLEVEL} logging.level.ai.h2o.mojos.deploy.local.rest.controller.ModelsApiController=${LOGLEVEL} +logging.level.ai.h2o.mojos.deploy.local.rest.controller.ReadyzApiController=${LOGLEVEL} diff --git a/local-rest-scorer/src/test/java/ai/h2o/mojos/deploy/local/rest/controller/ModelsApiControllerTest.java b/local-rest-scorer/src/test/java/ai/h2o/mojos/deploy/local/rest/controller/ModelsApiControllerTest.java index 70ddfdb9..af7de0fc 100644 --- a/local-rest-scorer/src/test/java/ai/h2o/mojos/deploy/local/rest/controller/ModelsApiControllerTest.java +++ b/local-rest-scorer/src/test/java/ai/h2o/mojos/deploy/local/rest/controller/ModelsApiControllerTest.java @@ -31,7 +31,6 @@ import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.multipart.MultipartFile; @@ -39,8 +38,7 @@ @ExtendWith(MockitoExtension.class) class ModelsApiControllerTest { - @Mock - private SampleRequestBuilder sampleRequestBuilder; + @Mock private SampleRequestBuilder sampleRequestBuilder; @BeforeAll static void setup() throws IOException { diff --git a/local-rest-scorer/src/test/java/ai/h2o/mojos/deploy/local/rest/controller/ReadyzApiControllerTest.java b/local-rest-scorer/src/test/java/ai/h2o/mojos/deploy/local/rest/controller/ReadyzApiControllerTest.java new file mode 100644 index 00000000..6a90f5e9 --- /dev/null +++ b/local-rest-scorer/src/test/java/ai/h2o/mojos/deploy/local/rest/controller/ReadyzApiControllerTest.java @@ -0,0 +1,45 @@ +package ai.h2o.mojos.deploy.local.rest.controller; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +import ai.h2o.mojos.deploy.common.rest.model.Model; +import ai.h2o.mojos.deploy.common.rest.model.ModelSchema; +import ai.h2o.mojos.deploy.common.transform.MojoScorer; +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; + +@ExtendWith(MockitoExtension.class) +class ReadyzApiControllerTest { + + @Mock private MojoScorer scorer; + + @InjectMocks private ReadyzApiController controller; + + @Test + void getReadyz_WhenModelSchemaSucceeds_ReturnsOk() { + Model model = new Model(); + model.setSchema(new ModelSchema()); + when(scorer.getModelInfo()).thenReturn(model); + + ResponseEntity response = controller.getReadyz(); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("Ready", response.getBody()); + } + + @Test + void getReadyz_WhenModelSchemaFails_ReturnsError() { + when(scorer.getModelInfo()).thenThrow(new RuntimeException("Test error")); + + ResponseEntity response = controller.getReadyz(); + + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); + assertEquals("Not ready", response.getBody()); + } +}