From 9adde5462ff9fd281eb5057dbb4c58e4676a9d90 Mon Sep 17 00:00:00 2001 From: Fiete Ostkamp Date: Wed, 12 Oct 2022 09:23:14 +0200 Subject: Enhancement of AAI-traversal healthcheck - enhance the EchoResponse endpoint to check for db connectivity when the myAction parameter is provided Issue-ID: AAI-3547 Signed-off-by: Fiete Ostkamp Change-Id: I4d5686b63efd139b942cee0c222305a17d2a2497 --- .gitignore | 3 + aai-traversal/.classpath | 59 +++++++- .../org/onap/aai/rest/util/AaiGraphChecker.java | 103 +++++++++++++ .../java/org/onap/aai/rest/util/EchoResponse.java | 161 +++++++++++---------- .../onap/aai/rest/util/AaiGraphCheckerTest.java | 43 ++++++ .../org/onap/aai/rest/util/EchoResponseTest.java | 50 +++++-- 6 files changed, 328 insertions(+), 91 deletions(-) create mode 100644 aai-traversal/src/main/java/org/onap/aai/rest/util/AaiGraphChecker.java create mode 100644 aai-traversal/src/test/java/org/onap/aai/rest/util/AaiGraphCheckerTest.java diff --git a/.gitignore b/.gitignore index f586926..5b775f4 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ bundleconfig-local/etc/logback.xml *.iml **/oxm/** **/dbedgerules/** + +.devcontainer +.vscode \ No newline at end of file diff --git a/aai-traversal/.classpath b/aai-traversal/.classpath index 31f5ed6..d9feb1e 100644 --- a/aai-traversal/.classpath +++ b/aai-traversal/.classpath @@ -3,7 +3,64 @@ + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aai-traversal/src/main/java/org/onap/aai/rest/util/AaiGraphChecker.java b/aai-traversal/src/main/java/org/onap/aai/rest/util/AaiGraphChecker.java new file mode 100644 index 0000000..11b412d --- /dev/null +++ b/aai-traversal/src/main/java/org/onap/aai/rest/util/AaiGraphChecker.java @@ -0,0 +1,103 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Bell Canada + * Modification Copyright (C) 2022 Deutsche Telekom SA + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +package org.onap.aai.rest.util; + +import java.util.Iterator; + +import org.janusgraph.core.JanusGraphException; +import org.janusgraph.core.JanusGraphTransaction; +import org.onap.aai.dbmap.AAIGraph; +import org.onap.aai.logging.ErrorLogHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import com.google.common.collect.Iterators; + +/** + * Singleton class responsible to check that AAI service is able to connect to its back-end + * database. + */ +@Component +@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) +public class AaiGraphChecker { + private static final Logger LOGGER = LoggerFactory.getLogger(AaiGraphChecker.class); + + private AaiGraphChecker() { + } + + /** + * Checks whether a connection to the graph database can be made. + * + * @return + *
  • true, if database is available
  • + *
  • false, if database is NOT available
  • + */ + public Boolean isAaiGraphDbAvailable() { + Boolean dbAvailable; + JanusGraphTransaction transaction = null; + try { + transaction = AAIGraph.getInstance().getGraph().newTransaction(); + final Iterator vertexIterator = transaction.query().limit(1).vertices().iterator(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Number of vertices retrieved while checking db: {}", + Iterators.size(vertexIterator)); + } + vertexIterator.hasNext(); + LOGGER.debug("Database is available"); + dbAvailable = Boolean.TRUE; + } catch (JanusGraphException e) { + String message = "Database is not available (after JanusGraph exception)"; + ErrorLogHelper.logError("500", message + ": " + e.getMessage()); + LOGGER.error(message, e); + dbAvailable = Boolean.FALSE; + } catch (Error e) { + // Following error occurs when aai resources is starting: + // - UnsatisfiedLinkError (for org.onap.aai.dbmap.AAIGraph$Helper instantiation) + // Following errors are raised when aai resources is starting and cassandra is not + // running: + // - ExceptionInInitializerError + // - NoClassDefFoundError (definition for org.onap.aai.dbmap.AAIGraph$Helper is not + // found) + String message = "Database is not available (after error)"; + ErrorLogHelper.logError("500", message + ": " + e.getMessage()); + LOGGER.error(message, e); + dbAvailable = Boolean.FALSE; + } catch (Exception e) { + String message = "Database availability can not be determined"; + ErrorLogHelper.logError("500", message + ": " + e.getMessage()); + LOGGER.error(message, e); + dbAvailable = null; + } finally { + if (transaction != null && !transaction.isClosed()) { + // check if transaction is open then closed instead of flag + try { + transaction.rollback(); + } catch (Exception e) { + String message = "Exception occurred while closing transaction"; + LOGGER.error(message, e); + ErrorLogHelper.logError("500", message + ": " + e.getMessage()); + } + } + } + return dbAvailable; + } +} diff --git a/aai-traversal/src/main/java/org/onap/aai/rest/util/EchoResponse.java b/aai-traversal/src/main/java/org/onap/aai/rest/util/EchoResponse.java index d0ba708..2e42e4c 100644 --- a/aai-traversal/src/main/java/org/onap/aai/rest/util/EchoResponse.java +++ b/aai-traversal/src/main/java/org/onap/aai/rest/util/EchoResponse.java @@ -8,7 +8,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -36,85 +36,96 @@ import javax.ws.rs.core.Response.Status; import org.onap.aai.exceptions.AAIException; import org.onap.aai.logging.ErrorLogHelper; import org.onap.aai.restcore.RESTAPI; +import org.springframework.stereotype.Component; /** * The Class EchoResponse. */ @Path("/util") +@Component public class EchoResponse extends RESTAPI { - protected static String authPolicyFunctionName = "util"; - - public static final String ECHO_PATH = "/echo"; - - /** - * Simple health-check API that echos back the X-FromAppId and X-TransactionId to clients. - * If there is a query string, a transaction gets logged into hbase, proving the application is - * connected to the data store. - * If there is no query string, no transacction logging is done to hbase. - * - * @param headers the headers - * @param req the req - * @param myAction if exists will cause transaction to be logged to hbase - * @return the response - */ - @GET - @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) - @Path(ECHO_PATH) - public Response echoResult(@Context HttpHeaders headers, @Context HttpServletRequest req, - @QueryParam("action") String myAction) { - AAIException ex = null; - Response response; - String fromAppId; - String transId; - - try { - fromAppId = getFromAppId(headers); - transId = getTransId(headers); - } catch (AAIException e) { - ArrayList templateVars = new ArrayList<>(); - templateVars.add("PUT uebProvider"); - templateVars.add("addTopic"); - return Response - .status(e.getErrorObject().getHTTPResponseCode()).entity(ErrorLogHelper - .getRESTAPIErrorResponse(headers.getAcceptableMediaTypes(), e, templateVars)) - .build(); - } - - try { - - HashMap> exceptionList = new HashMap<>(); - - ArrayList templateVars = new ArrayList<>(); - templateVars.add(fromAppId); - templateVars.add(transId); - - exceptionList.put(new AAIException("AAI_0002", "OK"), templateVars); - - response = Response - .status(Status.OK).entity(ErrorLogHelper - .getRESTAPIInfoResponse(headers.getAcceptableMediaTypes(), exceptionList)) - .build(); - - } catch (Exception e) { - ex = new AAIException("AAI_4000", e); - ArrayList templateVars = new ArrayList<>(); - templateVars.add(Action.GET.name()); - templateVars.add(fromAppId + " " + transId); - - response = Response - .status(Status.INTERNAL_SERVER_ERROR).entity(ErrorLogHelper - .getRESTAPIErrorResponse(headers.getAcceptableMediaTypes(), ex, templateVars)) - .build(); - - } finally { - if (ex != null) { - ErrorLogHelper.logException(ex); - } - - } - - return response; - } - + protected static String authPolicyFunctionName = "util"; + + public static final String ECHO_PATH = "/echo"; + + private AaiGraphChecker aaiGraphChecker; + + public EchoResponse(AaiGraphChecker aaiGraphChecker) { + this.aaiGraphChecker = aaiGraphChecker; + } + + /** + * Simple health-check API that echos back the X-FromAppId and X-TransactionId + * to clients. + * If there is a query string, the healthcheck will also check for database connectivity. + * + * @param headers the headers + * @param req the request + * @param myAction if exists will cause database connectivity check + * @return the response + */ + @GET + @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) + @Path(ECHO_PATH) + public Response echoResult(@Context HttpHeaders headers, @Context HttpServletRequest req, + @QueryParam("action") String myAction) { + + String fromAppId; + String transId; + ArrayList templateVars = new ArrayList<>(); + + try { + fromAppId = getFromAppId(headers); + transId = getTransId(headers); + } catch (AAIException aaiException) { + templateVars.add("PUT uebProvider"); + templateVars.add("addTopic"); + ErrorLogHelper.logException(aaiException); + return generateFailureResponse(headers, templateVars, aaiException); + } + + templateVars.add(fromAppId); + templateVars.add(transId); + if (myAction != null) { + try { + if (!aaiGraphChecker.isAaiGraphDbAvailable()) { + throw new AAIException("AAI_5105", "Error establishing a database connection"); + } + return generateSuccessResponse(headers, templateVars); + } catch (AAIException aaiException) { + ErrorLogHelper.logException(aaiException); + return generateFailureResponse(headers, templateVars, aaiException); + } catch (Exception e) { + AAIException aaiException = new AAIException("AAI_4000", e); + ErrorLogHelper.logException(aaiException); + return generateFailureResponse(headers, templateVars, aaiException); + } + } + return generateSuccessResponse(headers, templateVars); + } + + private Response generateSuccessResponse(HttpHeaders headers, ArrayList templateVariables) { + HashMap> exceptionList = new HashMap<>(); + exceptionList.put(new AAIException("AAI_0002", "OK"), templateVariables); + try { + return Response.status(Status.OK) + .entity( + ErrorLogHelper.getRESTAPIInfoResponse(headers.getAcceptableMediaTypes(), exceptionList)) + .build(); + } catch (Exception e) { + AAIException aaiException = new AAIException("AAI_4000", e); + ErrorLogHelper.logException(aaiException); + return generateFailureResponse(headers, templateVariables, aaiException); + } + } + + private Response generateFailureResponse(HttpHeaders headers, ArrayList templateVariables, + AAIException aaiException) { + return Response.status(aaiException.getErrorObject().getHTTPResponseCode()) + .entity( + ErrorLogHelper.getRESTAPIErrorResponseWithLogging( + headers.getAcceptableMediaTypes(), aaiException, templateVariables)) + .build(); + } } diff --git a/aai-traversal/src/test/java/org/onap/aai/rest/util/AaiGraphCheckerTest.java b/aai-traversal/src/test/java/org/onap/aai/rest/util/AaiGraphCheckerTest.java new file mode 100644 index 0000000..409c021 --- /dev/null +++ b/aai-traversal/src/test/java/org/onap/aai/rest/util/AaiGraphCheckerTest.java @@ -0,0 +1,43 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Bell Canada + * Modification Copyright (C) 2022 Deutsche Telekom SA + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.aai.rest.util; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.onap.aai.AAISetup; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; + +@ContextConfiguration(classes = {AaiGraphChecker.class}) +public class AaiGraphCheckerTest extends AAISetup { + + @Autowired + private AaiGraphChecker graphChecker; + + @Test + public void testIsAaiGraphDbAvailable() { + Boolean result = graphChecker.isAaiGraphDbAvailable(); + + assertNotNull(result); + assertTrue(result); + } +} diff --git a/aai-traversal/src/test/java/org/onap/aai/rest/util/EchoResponseTest.java b/aai-traversal/src/test/java/org/onap/aai/rest/util/EchoResponseTest.java index 97702fa..eb67799 100644 --- a/aai-traversal/src/test/java/org/onap/aai/rest/util/EchoResponseTest.java +++ b/aai-traversal/src/test/java/org/onap/aai/rest/util/EchoResponseTest.java @@ -8,7 +8,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -21,7 +21,7 @@ package org.onap.aai.rest.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.mockito.ArgumentMatchers.anyObject; +import static org.mockito.ArgumentMatchers.anyObject;import static org.mockito.ArgumentMatchers.anyObject; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -59,6 +59,8 @@ public class EchoResponseTest { private EchoResponse echoResponse; + private final AaiGraphChecker aaiGraphCheckerMock = mock(AaiGraphChecker.class); + private HttpHeaders httpHeaders; private UriInfo uriInfo; @@ -73,15 +75,15 @@ public class EchoResponseTest { private static final Logger logger = LoggerFactory.getLogger(EchoResponseTest.class.getName()); @Before - public void setup() { + public void setup(){ logger.info("Starting the setup for the integration tests of Rest Endpoints"); - echoResponse = new EchoResponse(); - httpHeaders = mock(HttpHeaders.class); - uriInfo = mock(UriInfo.class); + echoResponse = new EchoResponse(aaiGraphCheckerMock); + httpHeaders = mock(HttpHeaders.class); + uriInfo = mock(UriInfo.class); - headersMultiMap = new MultivaluedHashMap<>(); - queryParameters = Mockito.spy(new MultivaluedHashMap<>()); + headersMultiMap = new MultivaluedHashMap<>(); + queryParameters = Mockito.spy(new MultivaluedHashMap<>()); headersMultiMap.add("X-FromAppId", "JUNIT"); headersMultiMap.add("X-TransactionId", UUID.randomUUID().toString()); @@ -102,11 +104,11 @@ public class EchoResponseTest { when(httpHeaders.getRequestHeader("aai-request-context")).thenReturn(aaiRequestContextList); + when(uriInfo.getQueryParameters()).thenReturn(queryParameters); when(uriInfo.getQueryParameters(false)).thenReturn(queryParameters); - // TODO - Check if this is valid since RemoveDME2QueryParameters seems to be very - // unreasonable + // TODO - Check if this is valid since RemoveDME2QueryParameters seems to be very unreasonable Mockito.doReturn(null).when(queryParameters).remove(anyObject()); when(httpHeaders.getMediaType()).thenReturn(APPLICATION_JSON); @@ -115,17 +117,35 @@ public class EchoResponseTest { @Test public void testEchoResultWhenValidHeaders() throws Exception { - Response response = echoResponse.echoResult(httpHeaders, null, ""); + Response response = echoResponse.echoResult(httpHeaders, null, null); assertNotNull(response); assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); } + @Test + public void testEchoResultWhenActionIsProvidedDbAvailable() throws Exception { + when(aaiGraphCheckerMock.isAaiGraphDbAvailable()).thenReturn(true); + Response response = echoResponse.echoResult(httpHeaders, null, "myAction"); + + assertNotNull(response); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + } + + @Test + public void testEchoResultWhenActionIsProvidedDbNotAvailable() throws Exception { + when(aaiGraphCheckerMock.isAaiGraphDbAvailable()).thenReturn(false); + Response response = echoResponse.echoResult(httpHeaders, null, "myAction"); + + assertNotNull(response); + assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), response.getStatus()); + } + @Test public void testEchoResultWhenInValidHeadersThrowsBadRequest() throws Exception { httpHeaders = mock(HttpHeaders.class); - Response response = echoResponse.echoResult(httpHeaders, null, ""); + Response response = echoResponse.echoResult(httpHeaders, null, null); assertNotNull(response); assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); @@ -135,11 +155,11 @@ public class EchoResponseTest { public void testEchoResultWhenValidHeadersButMediaTypeWrong() throws Exception { when(httpHeaders.getAcceptableMediaTypes()).thenThrow(new IllegalStateException()) - .thenReturn(outputMediaTypes); + .thenReturn(outputMediaTypes); - Response response = echoResponse.echoResult(httpHeaders, null, ""); + Response response = echoResponse.echoResult(httpHeaders, null, null); assertNotNull(response); assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), response.getStatus()); } -} +} \ No newline at end of file -- cgit 1.2.3-korg