Add performance test for resources 84/138284/2
authorFiete Ostkamp <Fiete.Ostkamp@telekom.de>
Wed, 19 Jun 2024 11:06:14 +0000 (13:06 +0200)
committerFiete Ostkamp <Fiete.Ostkamp@telekom.de>
Wed, 19 Jun 2024 12:17:04 +0000 (14:17 +0200)
- add a K6-based performance test
- exact thresholds are not important for now, it's rather meant to
  assist development

Issue-ID: AAI-3892
Change-Id: I1387f97acaa593ae8be84a0782f42274b1b100a7
Signed-off-by: Fiete Ostkamp <Fiete.Ostkamp@telekom.de>
aai-resources/pom.xml
aai-resources/src/main/resources/application.properties
aai-resources/src/test/java/org/onap/aai/it/performance/K6PerformanceTest.java [new file with mode: 0644]
aai-resources/src/test/resources/k6/test.js [new file with mode: 0644]
aai-resources/src/test/resources/logback.xml

index 8ef378c..d50695d 100644 (file)
                     <groupId>org.springframework</groupId>
                     <artifactId>spring-web</artifactId>
                 </exclusion>
+                <exclusion>
+                    <groupId>junit</groupId>
+                    <artifactId>junit</artifactId>
+                </exclusion>
             </exclusions>
         </dependency>
         <dependency>
             <version>${groovy.version}</version>
         </dependency>
         <dependency>
-            <groupId>org.springframework</groupId>
-            <artifactId>spring-test</artifactId>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-test</artifactId>
             <scope>test</scope>
         </dependency>
+        <!-- Only used for the WebTestClient -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-test</artifactId>
+            <artifactId>spring-boot-starter-webflux</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
             <version>${keycloak.version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>testcontainers</artifactId>
+            <version>1.19.8</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <version>1.19.8</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>k6</artifactId>
+            <version>1.19.8</version>
+            <scope>test</scope>
+        </dependency>
         <dependency>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
index cc0c46a..960db1b 100644 (file)
@@ -8,7 +8,7 @@ spring.application.name=aai-resources
 spring.jersey.type=filter
 spring.main.allow-bean-definition-overriding=true
 
-spring.sleuth.enabled=true
+spring.sleuth.enabled=false
 spring.zipkin.baseUrl=http://jaeger-collector.istio-system:9411
 spring.sleuth.messaging.jms.enabled = false
 spring.sleuth.trace-id128=true
@@ -43,6 +43,7 @@ server.certs.location=${server.local.startpath}/etc/auth/
 server.keystore.name=aai_keystore
 server.truststore.name=aai_keystore
 server.port=8447
+server.ssl.enabled=false
 server.ssl.enabled-protocols=TLSv1.1,TLSv1.2
 server.ssl.key-store=${server.certs.location}${server.keystore.name}
 server.ssl.key-store-password=password(OBF:1vn21ugu1saj1v9i1v941sar1ugw1vo0)
@@ -60,7 +61,7 @@ jms.bind.address=tcp://localhost:61647
 # dmaap.ribbon.listOfServers=localhost:3904
 spring.kafka.producer.bootstrap-servers=${BOOTSTRAP_SERVERS}
 spring.kafka.producer.properties.security.protocol=SASL_PLAINTEXT
-spring.kafka.producer.properties.sasl.mechanism=SCRAM-SHA-512 
+spring.kafka.producer.properties.sasl.mechanism=SCRAM-SHA-512
 spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
 spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
 spring.kafka.producer.properties.sasl.jaas.config = ${JAAS_CONFIG}
@@ -148,3 +149,8 @@ validation.service.node-types=generic-vnf,lag-interface,l-interface,logical-link
 # List of X-FromAppId regexes seperated by comma to ignore the pre validation for
 # Note: please don't add any client id here as this is only for testing tools such as robot
 validation.service.exclusion-regexes=
+
+BOOTSTRAP_SERVERS=localhost:9092
+JAAS_CONFIG=""
+BUNDLECONFIG_DIR=src/main/resources/
+AJSC_HOME=./
diff --git a/aai-resources/src/test/java/org/onap/aai/it/performance/K6PerformanceTest.java b/aai-resources/src/test/java/org/onap/aai/it/performance/K6PerformanceTest.java
new file mode 100644 (file)
index 0000000..8428cb5
--- /dev/null
@@ -0,0 +1,139 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2024 Deutsche Telekom. All rights reserved.
+ * ================================================================================
+ * 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.it.performance;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
+import org.janusgraph.core.JanusGraph;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.onap.aai.ResourcesApp;
+import org.onap.aai.db.props.AAIProperties;
+import org.onap.aai.dbmap.AAIGraph;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.testcontainers.containers.output.WaitingConsumer;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.k6.K6Container;
+import org.testcontainers.utility.MountableFile;
+
+import lombok.SneakyThrows;
+
+@Testcontainers
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
+public class K6PerformanceTest {
+
+  private static final Logger logger = LoggerFactory.getLogger(ResourcesApp.class.getName());
+  private static final long nPservers = 10;
+
+  @LocalServerPort
+  private int port;
+
+
+  private boolean initialized = false;
+
+  @BeforeEach
+  public void setup() {
+    if (!initialized) {
+      initialized = true;
+      AAIGraph.getInstance();
+
+      long startTime = System.currentTimeMillis();
+      logger.info("Creating pserver nodes");
+      loadPerformanceTestData();
+      long endTime = System.currentTimeMillis();
+      logger.info("Created pserver nodes in {} seconds", (endTime - startTime) / 1000);
+    }
+  }
+
+  @AfterAll
+  public static void cleanup() {
+    JanusGraph graph = AAIGraph.getInstance().getGraph();
+    graph.traversal().V().has("aai-node-type", "pserver").drop().iterate();
+    graph.tx().commit();
+  }
+
+  @Test
+  public void k6StandardTest() throws Exception {
+    int testDuration = 5;
+
+    try (
+        K6Container container = new K6Container("grafana/k6:0.49.0")
+            .withNetworkMode("host")
+            .withAccessToHost(true)
+            .withTestScript(MountableFile.forClasspathResource("k6/test.js"))
+            .withScriptVar("API_PORT", String.valueOf(port))
+            .withScriptVar("API_VERSION", "v29")
+            .withScriptVar("DURATION_SECONDS", String.valueOf(testDuration))
+            .withScriptVar("N_PSERVERS", String.valueOf(nPservers))
+            .withCmdOptions("--quiet", "--no-usage-report");) {
+      container.start();
+
+      WaitingConsumer consumer = new WaitingConsumer();
+      container.followOutput(consumer);
+
+      // Wait for test script results to be collected
+      consumer.waitUntil(
+          frame -> {
+            return frame.getUtf8String().contains("iteration_duration");
+          },
+          testDuration + 30,
+          TimeUnit.SECONDS);
+
+      logger.debug(container.getLogs());
+      assertThat(container.getLogs(), containsString("✓ status was 200"));
+      assertThat(container.getLogs(), containsString("✓ returned correct number of results"));
+      assertThat(container.getLogs(), containsString("✓ http_req_duration"));
+      assertThat(container.getLogs(), containsString("✓ http_req_failed"));
+    }
+  }
+
+  @SneakyThrows
+  public static void loadPerformanceTestData() {
+    JanusGraph graph = AAIGraph.getInstance().getGraph();
+    GraphTraversalSource g = graph.traversal();
+    long n = nPservers;
+    for (long i = 0; i < n; i++) {
+      createPServer(g, i);
+    }
+    graph.tx().commit();
+  }
+
+  private static void createPServer(GraphTraversalSource g, long i) {
+    String hostname = "hostname" + i;
+    String uri = "/cloud-infrastructure/pservers/pserver/" + hostname;
+    g.addV()
+        .property("aai-node-type", "pserver")
+        .property("hostname", hostname)
+        .property("resource-version", UUID.randomUUID().toString())
+        .property(AAIProperties.AAI_URI, uri)
+        .next();
+  }
+}
diff --git a/aai-resources/src/test/resources/k6/test.js b/aai-resources/src/test/resources/k6/test.js
new file mode 100644 (file)
index 0000000..8e55214
--- /dev/null
@@ -0,0 +1,66 @@
+/**
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2024 Deutsche Telekom. All rights reserved.
+ * ================================================================================
+ * 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=========================================================
+ */
+import http from "k6/http";
+import { check } from "k6";
+
+export const options = {
+  vus: 3,
+  duration: `${__ENV.DURATION_SECONDS}s`,
+  thresholds: {
+    http_req_failed: ["rate<0.01"], // http errors should be less than 1%
+    http_req_duration: [
+      "p(99)<3000",
+      "p(90)<2000",
+      "avg<1000",
+      "med<1000",
+      "min<1000",
+    ],
+  },
+  insecureSkipTLSVerify: true,
+};
+
+export default function () {
+  const encodedCredentials = 'QUFJOkFBSQ==';
+  const options = {
+    headers: {
+      Accept: "application/json",
+      Authorization: `Basic ${encodedCredentials}`,
+      "X-FromAppId": "k6",
+      "X-TransactionId": "someTransaction",
+    },
+  };
+  const pserverCount = parseInt(`${__ENV.N_PSERVERS}`, 10);
+  const baseUrl = `http://localhost:${__ENV.API_PORT}/aai/${__ENV.API_VERSION}`;
+  const url = `/cloud-infrastructure/pservers`;
+  const res = http.get(baseUrl + url, options);
+
+  if (res.status != 200) {
+    console.error(res);
+  }
+
+  const parsedResponse = JSON.parse(res.body);
+  if (parsedResponse.pserver.length != pserverCount) {
+    console.error(`Expected ${pserverCount} results, got ${parsedResponse.pserver.length}`);
+  }
+  check(res, {
+    "status was 200": (r) => r.status == 200,
+    "returned correct number of results": () => parsedResponse.pserver.length == pserverCount,
+  });
+}
index 7d35439..a550336 100644 (file)
                        <pattern>${transLogPattern}</pattern>
                </encoder>
        </appender>
-       
+
        <appender name="asynctranslog" class="ch.qos.logback.classic.AsyncAppender">
                <queueSize>${queueSize}</queueSize>
                <includeCallerData>true</includeCallerData>
                <appender-ref ref="asyncMETRIC" />
        </logger>
 
+       <logger name="org.testcontainers" level="INFO"/>
+       <logger name="com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.wire" level="OFF"/>
+
        <root level="DEBUG">
                <appender-ref ref="external" />
                <appender-ref ref="STDOUT" />