Enhancement of AAI-traversal healthcheck 41/131441/2
authorFiete Ostkamp <Fiete.Ostkamp@telekom.de>
Wed, 12 Oct 2022 07:23:14 +0000 (09:23 +0200)
committerFiete Ostkamp <Fiete.Ostkamp@telekom.de>
Wed, 12 Oct 2022 09:15:00 +0000 (09:15 +0000)
- enhance the EchoResponse endpoint to check for db connectivity when the myAction parameter is provided

Issue-ID: AAI-3547

Signed-off-by: Fiete Ostkamp <Fiete.Ostkamp@telekom.de>
Change-Id: I4d5686b63efd139b942cee0c222305a17d2a2497

.gitignore
aai-traversal/.classpath
aai-traversal/src/main/java/org/onap/aai/rest/util/AaiGraphChecker.java [new file with mode: 0644]
aai-traversal/src/main/java/org/onap/aai/rest/util/EchoResponse.java
aai-traversal/src/test/java/org/onap/aai/rest/util/AaiGraphCheckerTest.java [new file with mode: 0644]
aai-traversal/src/test/java/org/onap/aai/rest/util/EchoResponseTest.java

index f586926..5b775f4 100644 (file)
@@ -13,3 +13,6 @@ bundleconfig-local/etc/logback.xml
 *.iml
 **/oxm/**
 **/dbedgerules/**
+
+.devcontainer
+.vscode
\ No newline at end of file
index 31f5ed6..d9feb1e 100644 (file)
@@ -3,7 +3,64 @@
        <classpathentry kind="src" path="target/generated-sources/annotations">
                <attributes>
                        <attribute name="optional" value="true"/>
+                       <attribute name="maven.pomderived" value="true"/>
+                       <attribute name="ignore_optional_problems" value="true"/>
+                       <attribute name="m2e-apt" value="true"/>
                </attributes>
        </classpathentry>
-       <classpathentry kind="output" path="bin"/>
+       <classpathentry kind="src" output="target/classes" path="src/main/java">
+               <attributes>
+                       <attribute name="optional" value="true"/>
+                       <attribute name="maven.pomderived" value="true"/>
+               </attributes>
+       </classpathentry>
+       <classpathentry kind="src" output="target/classes" path="target/generated-sources/antlr4">
+               <attributes>
+                       <attribute name="optional" value="true"/>
+                       <attribute name="maven.pomderived" value="true"/>
+               </attributes>
+       </classpathentry>
+       <classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
+               <attributes>
+                       <attribute name="maven.pomderived" value="true"/>
+               </attributes>
+       </classpathentry>
+       <classpathentry excluding="**" kind="src" output="target/classes" path="src/main/docker">
+               <attributes>
+                       <attribute name="maven.pomderived" value="true"/>
+               </attributes>
+       </classpathentry>
+       <classpathentry kind="src" output="target/test-classes" path="src/test/java">
+               <attributes>
+                       <attribute name="optional" value="true"/>
+                       <attribute name="maven.pomderived" value="true"/>
+                       <attribute name="test" value="true"/>
+               </attributes>
+       </classpathentry>
+       <classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
+               <attributes>
+                       <attribute name="maven.pomderived" value="true"/>
+                       <attribute name="test" value="true"/>
+               </attributes>
+       </classpathentry>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
+               <attributes>
+                       <attribute name="maven.pomderived" value="true"/>
+               </attributes>
+       </classpathentry>
+       <classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+               <attributes>
+                       <attribute name="maven.pomderived" value="true"/>
+               </attributes>
+       </classpathentry>
+       <classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
+               <attributes>
+                       <attribute name="optional" value="true"/>
+                       <attribute name="maven.pomderived" value="true"/>
+                       <attribute name="ignore_optional_problems" value="true"/>
+                       <attribute name="m2e-apt" value="true"/>
+                       <attribute name="test" value="true"/>
+               </attributes>
+       </classpathentry>
+       <classpathentry kind="output" path="target/classes"/>
 </classpath>
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 (file)
index 0000000..11b412d
--- /dev/null
@@ -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
+     *         <li>true, if database is available</li>
+     *         <li>false, if database is NOT available</li>
+     */
+    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;
+    }
+}
index d0ba708..2e42e4c 100644 (file)
@@ -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<String> 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<AAIException, ArrayList<String>> exceptionList = new HashMap<>();
-
-            ArrayList<String> 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<String> 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<String> 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<String> templateVariables) {
+               HashMap<AAIException, ArrayList<String>> 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<String> 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 (file)
index 0000000..409c021
--- /dev/null
@@ -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);
+  }
+}
index 97702fa..eb67799 100644 (file)
@@ -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