From dd1eaefed893f76cc356ffe5c1502efd503dbc7e Mon Sep 17 00:00:00 2001 From: Kaituo Li Date: Mon, 1 May 2023 11:18:00 -0700 Subject: [PATCH] Various fixes This pull request addresses several issues related to the compiler and tests in the current main branch. The first major change involves replacing ImmutableOpenMap with java.util.Map in the core. This modification is implemented in the following pull requests: https://github.com/opensearch-project/OpenSearch/pull/7165 https://github.com/opensearch-project/OpenSearch/pull/7301 To accommodate this change, the codebase of the AD (Anomaly Detection) module has been refactored to utilize JDK maps. As a consequence of this change, passing null to the custom parameters of ClusterState is no longer permissible, as it leads to a NullPointerException. The error stack trace is as follows: java.lang.NullPointerException: Cannot invoke "Object.getClass()" because "m" is null at __randomizedtesting.SeedInfo.seed([60CDDB34427ACD0C:6E72DB4ED18E018D]:0) at java.base/java.util.Collections.unmodifiableMap(Collections.java:1476) at org.opensearch.cluster.ClusterState.(ClusterState.java:219) at org.opensearch.ad.transport.DeleteAnomalyDetectorTests.createClusterState(DeleteAnomalyDetectorTests.java:216) at org.opensearch.ad.transport.DeleteAnomalyDetectorTests.testDeleteADTransportAction_LatestDetectorLevelTask(DeleteAnomalyDetectorTests.java:160) To address this issue, we have replaced the usage of null with new HashMap<>(). The second change in this pull request aligns with the modifications introduced in PR https://github.com/opensearch-project/anomaly-detection/pull/878. The third issue is related to the incompatibility between tests that utilize the @Parameters annotation and those that do not, as explained in https://tinyurl.com/2y265s2w. Specifically, the SearchFeatureDaoTests class runes tests with the @Parameters annotation, whereas SearchFeatureDao tests do not. Testing done: 1. gradle build. Signed-off-by: Kaituo Li --- build.gradle | 13 +- gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 61574 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 269 ++++++----- gradlew.bat | 15 +- .../ad/indices/AnomalyDetectionIndices.java | 21 +- .../org/opensearch/ad/ADIntegTestCase.java | 11 +- .../java/org/opensearch/ad/TestHelpers.java | 26 +- .../feature/SearchFeatureDaoParamTests.java | 440 ++++++++++++++++++ .../ad/feature/SearchFeatureDaoTests.java | 294 ++---------- .../ad/indices/UpdateMappingTests.java | 26 +- .../opensearch/ad/ml/ModelManagerTests.java | 9 +- .../transport/DeleteAnomalyDetectorTests.java | 25 +- ...exAnomalyDetectorTransportActionTests.java | 9 +- ...ewAnomalyDetectorTransportActionTests.java | 8 +- .../opensearch/ad/util/ClusterCreation.java | 9 +- 16 files changed, 734 insertions(+), 444 deletions(-) create mode 100644 src/test/java/org/opensearch/ad/feature/SearchFeatureDaoParamTests.java diff --git a/build.gradle b/build.gradle index 00a55677a..1a41382f6 100644 --- a/build.gradle +++ b/build.gradle @@ -34,7 +34,7 @@ buildscript { js_resource_folder = "src/test/resources/job-scheduler" common_utils_version = System.getProperty("common_utils.version", opensearch_build) job_scheduler_version = System.getProperty("job_scheduler.version", opensearch_build) - bwcVersionShort = "2.7.0" + bwcVersionShort = "2.8.0" bwcVersion = bwcVersionShort + ".0" bwcOpenSearchADDownload = 'https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/' + bwcVersionShort + '/latest/linux/x64/tar/builds/' + 'opensearch/plugins/opensearch-anomaly-detection-' + bwcVersion + '.zip' @@ -67,7 +67,8 @@ plugins { id 'nebula.ospackage' version "8.3.0" apply false id "com.diffplug.gradle.spotless" version "3.26.1" id 'java-library' - id 'org.gradle.test-retry' version '1.3.1' + // Gradle 7.6 support was added in test-retry 1.4.0. + id 'org.gradle.test-retry' version '1.4.1' } tasks.withType(JavaCompile) { @@ -77,6 +78,14 @@ tasks.withType(Test) { systemProperty "file.encoding", "UTF-8" jvmArgs("--add-opens", "java.base/java.time=ALL-UNNAMED") jvmArgs("--add-opens", "java.base/java.util.stream=ALL-UNNAMED") + + // PowerMock related tests like SearchFeatureDaoTests relies on modifying the bytecode of + // classes during runtime, which can conflict with the module system introduced in Java 9. + // To resolve this issue, we use the --add-opens option to explicitly open the java.util + // and java.lang package to PowerMock. This option allows PowerMock to access non-public + // members of the java.util and java.lang package. + jvmArgs('--add-opens', 'java.base/java.util=ALL-UNNAMED') + jvmArgs('--add-opens', 'java.base/java.lang=ALL-UNNAMED') } tasks.withType(Javadoc) { options.encoding = 'UTF-8' diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..943f0cbfa754578e88a3dae77fce6e3dea56edbf 100644 GIT binary patch delta 39316 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 delta 36846 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 CcabXq diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index aa991fcea..508322917 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0c8..65dcd68d6 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,105 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index ac1b06f93..6689b85be 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/src/main/java/org/opensearch/ad/indices/AnomalyDetectionIndices.java b/src/main/java/org/opensearch/ad/indices/AnomalyDetectionIndices.java index acd14a092..4c2bb1f93 100644 --- a/src/main/java/org/opensearch/ad/indices/AnomalyDetectionIndices.java +++ b/src/main/java/org/opensearch/ad/indices/AnomalyDetectionIndices.java @@ -30,7 +30,6 @@ import java.util.Arrays; import java.util.EnumMap; import java.util.HashSet; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -91,8 +90,6 @@ import org.opensearch.threadpool.Scheduler; import org.opensearch.threadpool.ThreadPool; -import com.carrotsearch.hppc.cursors.ObjectCursor; -import com.carrotsearch.hppc.cursors.ObjectObjectCursor; import com.google.common.base.Charsets; import com.google.common.io.Resources; @@ -759,8 +756,8 @@ void deleteOldHistoryIndices() { adminClient.cluster().state(clusterStateRequest, ActionListener.wrap(clusterStateResponse -> { String latestToDelete = null; long latest = Long.MIN_VALUE; - for (ObjectCursor cursor : clusterStateResponse.getState().metadata().indices().values()) { - IndexMetadata indexMetaData = cursor.value; + for (IndexMetadata indexMetaData : clusterStateResponse.getState().metadata().indices().values()) { + // IndexMetadata indexMetaData = cursor.value; long creationTime = indexMetaData.getCreationDate(); if ((Instant.now().toEpochMilli() - creationTime) > historyRetentionPeriod.millis()) { @@ -986,10 +983,10 @@ private void shouldUpdateIndex(ADIndex index, ActionListener thenDo) { .indicesOptions(IndicesOptions.lenientExpandOpenHidden()); adminClient.indices().getAliases(getAliasRequest, ActionListener.wrap(getAliasResponse -> { String concreteIndex = null; - for (ObjectObjectCursor> entry : getAliasResponse.getAliases()) { - if (false == entry.value.isEmpty()) { + for (Map.Entry> entry : getAliasResponse.getAliases().entrySet()) { + if (false == entry.getValue().isEmpty()) { // we assume the alias map to one concrete index, thus we can return after finding one - concreteIndex = entry.key; + concreteIndex = entry.getKey(); break; } } @@ -1137,9 +1134,7 @@ private void updateJobIndexSettingIfNecessary(IndexState jobIndexState, ActionLi private static Integer getIntegerSetting(GetSettingsResponse settingsResponse, String settingKey) { Integer value = null; - Iterator iter = settingsResponse.getIndexToSettings().valuesIt(); - while (iter.hasNext()) { - Settings settings = iter.next(); + for (Settings settings : settingsResponse.getIndexToSettings().values()) { value = settings.getAsInt(settingKey, null); if (value != null) { break; @@ -1150,9 +1145,7 @@ private static Integer getIntegerSetting(GetSettingsResponse settingsResponse, S private static String getStringSetting(GetSettingsResponse settingsResponse, String settingKey) { String value = null; - Iterator iter = settingsResponse.getIndexToSettings().valuesIt(); - while (iter.hasNext()) { - Settings settings = iter.next(); + for (Settings settings : settingsResponse.getIndexToSettings().values()) { value = settings.get(settingKey, null); if (value != null) { break; diff --git a/src/test/java/org/opensearch/ad/ADIntegTestCase.java b/src/test/java/org/opensearch/ad/ADIntegTestCase.java index 68184578c..3a489cd37 100644 --- a/src/test/java/org/opensearch/ad/ADIntegTestCase.java +++ b/src/test/java/org/opensearch/ad/ADIntegTestCase.java @@ -21,7 +21,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.Map; @@ -53,7 +52,6 @@ import org.opensearch.client.node.NodeClient; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.node.DiscoveryNodes; -import org.opensearch.common.collect.ImmutableOpenMap; import org.opensearch.core.xcontent.ToXContent; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.index.query.MatchAllQueryBuilder; @@ -65,7 +63,6 @@ import org.opensearch.test.OpenSearchIntegTestCase; import org.opensearch.test.transport.MockTransportService; -import com.carrotsearch.hppc.cursors.ObjectObjectCursor; import com.google.common.collect.ImmutableMap; public abstract class ADIntegTestCase extends OpenSearchIntegTestCase { @@ -252,7 +249,7 @@ public ClusterUpdateSettingsResponse updateTransientSettings(Map sett return clusterAdmin().updateSettings(updateSettingsRequest).actionGet(timeout); } - public ImmutableOpenMap getDataNodes() { + public Map getDataNodes() { DiscoveryNodes nodes = clusterService().state().getNodes(); return nodes.getDataNodes(); } @@ -268,10 +265,10 @@ public Client getDataNodeClient() { public DiscoveryNode[] getDataNodesArray() { DiscoveryNodes nodes = clusterService().state().getNodes(); - Iterator> iterator = nodes.getDataNodes().iterator(); + Collection nodeCollection = nodes.getDataNodes().values(); List dataNodes = new ArrayList<>(); - while (iterator.hasNext()) { - dataNodes.add(iterator.next().value); + for (DiscoveryNode node : nodeCollection) { + dataNodes.add(node); } return dataNodes.toArray(new DiscoveryNode[0]); } diff --git a/src/test/java/org/opensearch/ad/TestHelpers.java b/src/test/java/org/opensearch/ad/TestHelpers.java index 115a75e84..b5c5693f2 100644 --- a/src/test/java/org/opensearch/ad/TestHelpers.java +++ b/src/test/java/org/opensearch/ad/TestHelpers.java @@ -108,7 +108,6 @@ import org.opensearch.common.UUIDs; import org.opensearch.common.bytes.BytesArray; import org.opensearch.common.bytes.BytesReference; -import org.opensearch.common.collect.ImmutableOpenMap; import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; @@ -1509,9 +1508,10 @@ public static DetectorValidationIssue randomDetectorValidationIssueWithDetectorI } public static ClusterState createClusterState() { - ImmutableOpenMap immutableOpenMap = ImmutableOpenMap - .builder() - .fPut( + final Map immutableOpenMap = new HashMap<>(); + + immutableOpenMap + .put( ANOMALY_DETECTOR_JOB_INDEX, IndexMetadata .builder("test") @@ -1523,10 +1523,20 @@ public static ClusterState createClusterState() { .put("index.version.created", Version.CURRENT.id) ) .build() - ) - .build(); - Metadata metaData = Metadata.builder().indices(immutableOpenMap).build(); - ClusterState clusterState = new ClusterState(new ClusterName("test_name"), 1l, "uuid", metaData, null, null, null, null, 1, true); + ); + Metadata metaData = Metadata.builder().indices(Collections.unmodifiableMap(immutableOpenMap)).build(); + ClusterState clusterState = new ClusterState( + new ClusterName("test_name"), + 1l, + "uuid", + metaData, + null, + null, + null, + new HashMap<>(), + 1, + true + ); return clusterState; } } diff --git a/src/test/java/org/opensearch/ad/feature/SearchFeatureDaoParamTests.java b/src/test/java/org/opensearch/ad/feature/SearchFeatureDaoParamTests.java new file mode 100644 index 000000000..89477aae5 --- /dev/null +++ b/src/test/java/org/opensearch/ad/feature/SearchFeatureDaoParamTests.java @@ -0,0 +1,440 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.ad.feature; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.time.Clock; +import java.time.temporal.ChronoUnit; +import java.util.AbstractMap.SimpleEntry; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.concurrent.ExecutorService; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; + +import org.apache.lucene.search.TotalHits; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opensearch.action.ActionFuture; +import org.opensearch.action.ActionListener; +import org.opensearch.action.search.MultiSearchRequest; +import org.opensearch.action.search.MultiSearchResponse; +import org.opensearch.action.search.MultiSearchResponse.Item; +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.ad.AnomalyDetectorPlugin; +import org.opensearch.ad.NodeStateManager; +import org.opensearch.ad.constant.CommonName; +import org.opensearch.ad.dataprocessor.Interpolator; +import org.opensearch.ad.dataprocessor.LinearUniformInterpolator; +import org.opensearch.ad.dataprocessor.SingleFeatureLinearUniformInterpolator; +import org.opensearch.ad.model.AnomalyDetector; +import org.opensearch.ad.model.IntervalTimeConfiguration; +import org.opensearch.ad.settings.AnomalyDetectorSettings; +import org.opensearch.ad.util.ParseUtils; +import org.opensearch.ad.util.SecurityClientUtil; +import org.opensearch.client.Client; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.xcontent.LoggingDeprecationHandler; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.script.ScriptService; +import org.opensearch.script.TemplateScript; +import org.opensearch.script.TemplateScript.Factory; +import org.opensearch.search.SearchHit; +import org.opensearch.search.SearchHits; +import org.opensearch.search.aggregations.Aggregation; +import org.opensearch.search.aggregations.Aggregations; +import org.opensearch.search.aggregations.metrics.InternalTDigestPercentiles; +import org.opensearch.search.aggregations.metrics.Max; +import org.opensearch.search.aggregations.metrics.Percentile; +import org.opensearch.search.builder.SearchSourceBuilder; +import org.opensearch.threadpool.ThreadPool; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.modules.junit4.PowerMockRunnerDelegate; + +import com.google.gson.Gson; + +/** + * Due to https://tinyurl.com/2y265s2w, tests with and without @Parameters annotation + * are incompatible with each other. This class tests SearchFeatureDao using @Parameters, + * while SearchFeatureDaoTests do not use @Parameters. + * + */ +@PowerMockIgnore("javax.management.*") +@RunWith(PowerMockRunner.class) +@PowerMockRunnerDelegate(JUnitParamsRunner.class) +@PrepareForTest({ ParseUtils.class, Gson.class }) +public class SearchFeatureDaoParamTests { + + private SearchFeatureDao searchFeatureDao; + + @Mock + private Client client; + @Mock + private ScriptService scriptService; + @Mock + private NamedXContentRegistry xContent; + private SecurityClientUtil clientUtil; + + @Mock + private Factory factory; + @Mock + private TemplateScript templateScript; + @Mock + private ActionFuture searchResponseFuture; + @Mock + private ActionFuture multiSearchResponseFuture; + @Mock + private SearchResponse searchResponse; + @Mock + private MultiSearchResponse multiSearchResponse; + @Mock + private Item multiSearchResponseItem; + @Mock + private Aggregations aggs; + @Mock + private Max max; + @Mock + private NodeStateManager stateManager; + + @Mock + private AnomalyDetector detector; + + @Mock + private ThreadPool threadPool; + + @Mock + private ClusterService clusterService; + + @Mock + private Clock clock; + + private SearchRequest searchRequest; + private SearchSourceBuilder searchSourceBuilder; + private MultiSearchRequest multiSearchRequest; + private IntervalTimeConfiguration detectionInterval; + private String detectorId; + private Interpolator interpolator; + private Settings settings; + + @Before + public void setup() throws Exception { + MockitoAnnotations.initMocks(this); + PowerMockito.mockStatic(ParseUtils.class); + + interpolator = new LinearUniformInterpolator(new SingleFeatureLinearUniformInterpolator()); + + ExecutorService executorService = mock(ExecutorService.class); + when(threadPool.executor(AnomalyDetectorPlugin.AD_THREAD_POOL_NAME)).thenReturn(executorService); + doAnswer(invocation -> { + Runnable runnable = invocation.getArgument(0); + runnable.run(); + return null; + }).when(executorService).execute(any(Runnable.class)); + + settings = Settings.EMPTY; + + when(client.threadPool()).thenReturn(threadPool); + NodeStateManager nodeStateManager = mock(NodeStateManager.class); + doAnswer(invocation -> { + ActionListener> listener = invocation.getArgument(1); + listener.onResponse(Optional.of(detector)); + return null; + }).when(nodeStateManager).getAnomalyDetector(any(String.class), any(ActionListener.class)); + clientUtil = new SecurityClientUtil(nodeStateManager, settings); + searchFeatureDao = spy( + new SearchFeatureDao(client, xContent, interpolator, clientUtil, settings, null, AnomalyDetectorSettings.NUM_SAMPLES_PER_TREE) + ); + + detectionInterval = new IntervalTimeConfiguration(1, ChronoUnit.MINUTES); + detectorId = "123"; + + when(detector.getDetectorId()).thenReturn(detectorId); + when(detector.getTimeField()).thenReturn("testTimeField"); + when(detector.getIndices()).thenReturn(Arrays.asList("testIndices")); + when(detector.getDetectionInterval()).thenReturn(detectionInterval); + when(detector.getFilterQuery()).thenReturn(QueryBuilders.matchAllQuery()); + when(detector.getCategoryField()).thenReturn(Collections.singletonList("a")); + + searchSourceBuilder = SearchSourceBuilder + .fromXContent(XContentType.JSON.xContent().createParser(xContent, LoggingDeprecationHandler.INSTANCE, "{}")); + searchRequest = new SearchRequest(detector.getIndices().toArray(new String[0])); + + when(max.getName()).thenReturn(CommonName.AGG_NAME_MAX_TIME); + List list = new ArrayList<>(); + list.add(max); + Aggregations aggregations = new Aggregations(list); + SearchHits hits = new SearchHits(new SearchHit[0], new TotalHits(1L, TotalHits.Relation.EQUAL_TO), 1f); + when(searchResponse.getHits()).thenReturn(hits); + + doAnswer(invocation -> { + Object[] args = invocation.getArguments(); + ActionListener listener = (ActionListener) args[1]; + listener.onResponse(searchResponse); + return null; + }).when(client).search(eq(searchRequest), any()); + when(searchResponse.getAggregations()).thenReturn(aggregations); + + multiSearchRequest = new MultiSearchRequest(); + SearchRequest request = new SearchRequest(detector.getIndices().toArray(new String[0])); + multiSearchRequest.add(request); + + doAnswer(invocation -> { + Object[] args = invocation.getArguments(); + ActionListener listener = (ActionListener) args[1]; + listener.onResponse(multiSearchResponse); + return null; + }).when(client).multiSearch(eq(multiSearchRequest), any()); + when(multiSearchResponse.getResponses()).thenReturn(new Item[] { multiSearchResponseItem }); + when(multiSearchResponseItem.getResponse()).thenReturn(searchResponse); + } + + @Test + @Parameters(method = "getFeaturesForPeriodData") + @SuppressWarnings("unchecked") + public void getFeaturesForPeriod_returnExpectedToListener(List aggs, List featureIds, double[] expected) + throws Exception { + + long start = 100L; + long end = 200L; + when(ParseUtils.generateInternalFeatureQuery(eq(detector), eq(start), eq(end), eq(xContent))).thenReturn(searchSourceBuilder); + when(searchResponse.getAggregations()).thenReturn(new Aggregations(aggs)); + when(detector.getEnabledFeatureIds()).thenReturn(featureIds); + doAnswer(invocation -> { + ActionListener listener = invocation.getArgument(1); + listener.onResponse(searchResponse); + return null; + }).when(client).search(eq(searchRequest), any(ActionListener.class)); + + ActionListener> listener = mock(ActionListener.class); + searchFeatureDao.getFeaturesForPeriod(detector, start, end, listener); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(Optional.class); + verify(listener).onResponse(captor.capture()); + Optional result = captor.getValue(); + assertTrue(Arrays.equals(expected, result.orElse(null))); + } + + @Test + @Parameters(method = "getFeaturesForSampledPeriodsData") + @SuppressWarnings("unchecked") + public void getFeaturesForSampledPeriods_returnExpectedToListener( + Long[][] queryRanges, + double[][] queryResults, + long endTime, + int maxStride, + int maxSamples, + Optional> expected + ) { + doAnswer(invocation -> { + ActionListener> listener = invocation.getArgument(3); + listener.onResponse(Optional.empty()); + return null; + }).when(searchFeatureDao).getFeaturesForPeriod(any(), anyLong(), anyLong(), any(ActionListener.class)); + for (int i = 0; i < queryRanges.length; i++) { + double[] queryResult = queryResults[i]; + doAnswer(invocation -> { + ActionListener> listener = invocation.getArgument(3); + listener.onResponse(Optional.of(queryResult)); + return null; + }) + .when(searchFeatureDao) + .getFeaturesForPeriod(eq(detector), eq(queryRanges[i][0]), eq(queryRanges[i][1]), any(ActionListener.class)); + } + + ActionListener>> listener = mock(ActionListener.class); + searchFeatureDao.getFeaturesForSampledPeriods(detector, maxSamples, maxStride, endTime, listener); + + ArgumentCaptor>> captor = ArgumentCaptor.forClass(Optional.class); + verify(listener).onResponse(captor.capture()); + Optional> result = captor.getValue(); + assertEquals(expected.isPresent(), result.isPresent()); + if (expected.isPresent()) { + assertTrue(Arrays.deepEquals(expected.get().getKey(), result.get().getKey())); + assertEquals(expected.get().getValue(), result.get().getValue()); + } + } + + @SuppressWarnings("unchecked") + private Object[] getFeaturesForPeriodData() { + String maxName = "max"; + double maxValue = 2; + Max max = mock(Max.class); + when(max.value()).thenReturn(maxValue); + when(max.getName()).thenReturn(maxName); + + String percentileName = "percentile"; + double percentileValue = 1; + InternalTDigestPercentiles percentiles = mock(InternalTDigestPercentiles.class); + Iterator percentilesIterator = mock(Iterator.class); + Percentile percentile = mock(Percentile.class); + when(percentiles.iterator()).thenReturn(percentilesIterator); + when(percentilesIterator.hasNext()).thenReturn(true); + when(percentilesIterator.next()).thenReturn(percentile); + when(percentile.getValue()).thenReturn(percentileValue); + when(percentiles.getName()).thenReturn(percentileName); + + String missingName = "missing"; + Max missing = mock(Max.class); + when(missing.value()).thenReturn(Double.NaN); + when(missing.getName()).thenReturn(missingName); + + String infinityName = "infinity"; + Max infinity = mock(Max.class); + when(infinity.value()).thenReturn(Double.POSITIVE_INFINITY); + when(infinity.getName()).thenReturn(infinityName); + + String emptyName = "empty"; + InternalTDigestPercentiles empty = mock(InternalTDigestPercentiles.class); + Iterator emptyIterator = mock(Iterator.class); + when(empty.iterator()).thenReturn(emptyIterator); + when(emptyIterator.hasNext()).thenReturn(false); + when(empty.getName()).thenReturn(emptyName); + + return new Object[] { + new Object[] { asList(max), asList(maxName), new double[] { maxValue }, }, + new Object[] { asList(percentiles), asList(percentileName), new double[] { percentileValue } }, + new Object[] { asList(missing), asList(missingName), null }, + new Object[] { asList(infinity), asList(infinityName), null }, + new Object[] { asList(max, percentiles), asList(maxName, percentileName), new double[] { maxValue, percentileValue } }, + new Object[] { asList(max, percentiles), asList(percentileName, maxName), new double[] { percentileValue, maxValue } }, + new Object[] { asList(max, percentiles, missing), asList(maxName, percentileName, missingName), null }, }; + } + + private Object[] getFeaturesForSampledPeriodsData() { + long endTime = 300_000; + int maxStride = 4; + return new Object[] { + + // No data + + new Object[] { new Long[0][0], new double[0][0], endTime, 1, 1, Optional.empty() }, + + // 1 data point + + new Object[] { + new Long[][] { { 240_000L, 300_000L } }, + new double[][] { { 1, 2 } }, + endTime, + 1, + 1, + Optional.of(new SimpleEntry<>(new double[][] { { 1, 2 } }, 1)) }, + + new Object[] { + new Long[][] { { 240_000L, 300_000L } }, + new double[][] { { 1, 2 } }, + endTime, + 1, + 3, + Optional.of(new SimpleEntry<>(new double[][] { { 1, 2 } }, 1)) }, + + // 2 data points + + new Object[] { + new Long[][] { { 180_000L, 240_000L }, { 240_000L, 300_000L } }, + new double[][] { { 1, 2 }, { 2, 4 } }, + endTime, + 1, + 2, + Optional.of(new SimpleEntry<>(new double[][] { { 1, 2 }, { 2, 4 } }, 1)) }, + + new Object[] { + new Long[][] { { 180_000L, 240_000L }, { 240_000L, 300_000L } }, + new double[][] { { 1, 2 }, { 2, 4 } }, + endTime, + 1, + 1, + Optional.of(new SimpleEntry<>(new double[][] { { 2, 4 } }, 1)) }, + + new Object[] { + new Long[][] { { 180_000L, 240_000L }, { 240_000L, 300_000L } }, + new double[][] { { 1, 2 }, { 2, 4 } }, + endTime, + 4, + 2, + Optional.of(new SimpleEntry<>(new double[][] { { 1, 2 }, { 2, 4 } }, 1)) }, + + new Object[] { + new Long[][] { { 0L, 60_000L }, { 240_000L, 300_000L } }, + new double[][] { { 1, 2 }, { 2, 4 } }, + endTime, + 4, + 2, + Optional.of(new SimpleEntry<>(new double[][] { { 1, 2 }, { 2, 4 } }, 4)) }, + + // 5 data points + + new Object[] { + new Long[][] { + { 0L, 60_000L }, + { 60_000L, 120_000L }, + { 120_000L, 180_000L }, + { 180_000L, 240_000L }, + { 240_000L, 300_000L } }, + new double[][] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 }, { 9, 10 } }, + endTime, + 4, + 10, + Optional.of(new SimpleEntry<>(new double[][] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 }, { 9, 10 } }, 1)) }, + + new Object[] { + new Long[][] { { 0L, 60_000L }, { 60_000L, 120_000L }, { 180_000L, 240_000L }, { 240_000L, 300_000L } }, + new double[][] { { 1, 2 }, { 3, 4 }, { 7, 8 }, { 9, 10 } }, + endTime, + 4, + 10, + Optional.of(new SimpleEntry<>(new double[][] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 }, { 9, 10 } }, 1)) }, + + new Object[] { + new Long[][] { { 0L, 60_000L }, { 120_000L, 180_000L }, { 240_000L, 300_000L } }, + new double[][] { { 1, 2 }, { 5, 6 }, { 9, 10 } }, + endTime, + 4, + 10, + Optional.of(new SimpleEntry<>(new double[][] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 }, { 9, 10 } }, 1)) }, + + new Object[] { + new Long[][] { { 0L, 60_000L }, { 240_000L, 300_000L } }, + new double[][] { { 1, 2 }, { 9, 10 } }, + endTime, + 4, + 10, + Optional.of(new SimpleEntry<>(new double[][] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 }, { 9, 10 } }, 1)) }, }; + } +} diff --git a/src/test/java/org/opensearch/ad/feature/SearchFeatureDaoTests.java b/src/test/java/org/opensearch/ad/feature/SearchFeatureDaoTests.java index 46f2710a3..fbb45cd5e 100644 --- a/src/test/java/org/opensearch/ad/feature/SearchFeatureDaoTests.java +++ b/src/test/java/org/opensearch/ad/feature/SearchFeatureDaoTests.java @@ -18,8 +18,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -29,7 +29,6 @@ import java.time.Clock; import java.time.ZoneId; import java.time.temporal.ChronoUnit; -import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -42,9 +41,6 @@ import java.util.Optional; import java.util.concurrent.ExecutorService; -import junitparams.JUnitParamsRunner; -import junitparams.Parameters; - import org.apache.lucene.search.TotalHits; import org.junit.Before; import org.junit.Test; @@ -74,7 +70,6 @@ import org.opensearch.ad.util.ParseUtils; import org.opensearch.ad.util.SecurityClientUtil; import org.opensearch.client.Client; -import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.settings.Settings; import org.opensearch.common.time.DateFormatter; import org.opensearch.common.xcontent.LoggingDeprecationHandler; @@ -107,17 +102,11 @@ import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; -import org.powermock.modules.junit4.PowerMockRunnerDelegate; - -import com.google.gson.Gson; @PowerMockIgnore("javax.management.*") @RunWith(PowerMockRunner.class) -@PowerMockRunnerDelegate(JUnitParamsRunner.class) -@PrepareForTest({ ParseUtils.class, Gson.class }) +@PrepareForTest({ ParseUtils.class }) public class SearchFeatureDaoTests { - // private final Logger LOG = LogManager.getLogger(SearchFeatureDaoTests.class); - private SearchFeatureDao searchFeatureDao; @Mock @@ -155,9 +144,6 @@ public class SearchFeatureDaoTests { @Mock private ThreadPool threadPool; - @Mock - private ClusterService clusterService; - @Mock private Clock clock; @@ -167,7 +153,6 @@ public class SearchFeatureDaoTests { private Map aggsMap; private IntervalTimeConfiguration detectionInterval; private String detectorId; - private Gson gson; private Interpolator interpolator; private Settings settings; @@ -243,79 +228,7 @@ public void setup() throws Exception { when(multiSearchResponse.getResponses()).thenReturn(new Item[] { multiSearchResponseItem }); when(multiSearchResponseItem.getResponse()).thenReturn(searchResponse); - gson = PowerMockito.mock(Gson.class); - } - - @Test - @SuppressWarnings("unchecked") - public void getLatestDataTime_returnExpectedToListener() { - SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder() - .aggregation(AggregationBuilders.max(CommonName.AGG_NAME_MAX_TIME).field(detector.getTimeField())) - .size(0); - searchRequest.source(searchSourceBuilder); - long epochTime = 100L; - aggsMap.put(CommonName.AGG_NAME_MAX_TIME, max); - when(max.getValue()).thenReturn((double) epochTime); - doAnswer(invocation -> { - ActionListener listener = invocation.getArgument(1); - listener.onResponse(searchResponse); - return null; - }).when(client).search(eq(searchRequest), any(ActionListener.class)); - - when(ParseUtils.getLatestDataTime(eq(searchResponse))).thenReturn(Optional.of(epochTime)); - ActionListener> listener = mock(ActionListener.class); - searchFeatureDao.getLatestDataTime(detector, listener); - - ArgumentCaptor> captor = ArgumentCaptor.forClass(Optional.class); - verify(listener).onResponse(captor.capture()); - Optional result = captor.getValue(); - assertEquals(epochTime, result.get().longValue()); - } - - @SuppressWarnings("unchecked") - private Object[] getFeaturesForPeriodData() { - String maxName = "max"; - double maxValue = 2; - Max max = mock(Max.class); - when(max.value()).thenReturn(maxValue); - when(max.getName()).thenReturn(maxName); - - String percentileName = "percentile"; - double percentileValue = 1; - InternalTDigestPercentiles percentiles = mock(InternalTDigestPercentiles.class); - Iterator percentilesIterator = mock(Iterator.class); - Percentile percentile = mock(Percentile.class); - when(percentiles.iterator()).thenReturn(percentilesIterator); - when(percentilesIterator.hasNext()).thenReturn(true); - when(percentilesIterator.next()).thenReturn(percentile); - when(percentile.getValue()).thenReturn(percentileValue); - when(percentiles.getName()).thenReturn(percentileName); - - String missingName = "missing"; - Max missing = mock(Max.class); - when(missing.value()).thenReturn(Double.NaN); - when(missing.getName()).thenReturn(missingName); - - String infinityName = "infinity"; - Max infinity = mock(Max.class); - when(infinity.value()).thenReturn(Double.POSITIVE_INFINITY); - when(infinity.getName()).thenReturn(infinityName); - - String emptyName = "empty"; - InternalTDigestPercentiles empty = mock(InternalTDigestPercentiles.class); - Iterator emptyIterator = mock(Iterator.class); - when(empty.iterator()).thenReturn(emptyIterator); - when(emptyIterator.hasNext()).thenReturn(false); - when(empty.getName()).thenReturn(emptyName); - - return new Object[] { - new Object[] { asList(max), asList(maxName), new double[] { maxValue }, }, - new Object[] { asList(percentiles), asList(percentileName), new double[] { percentileValue } }, - new Object[] { asList(missing), asList(missingName), null }, - new Object[] { asList(infinity), asList(infinityName), null }, - new Object[] { asList(max, percentiles), asList(maxName, percentileName), new double[] { maxValue, percentileValue } }, - new Object[] { asList(max, percentiles), asList(percentileName, maxName), new double[] { percentileValue, maxValue } }, - new Object[] { asList(max, percentiles, missing), asList(maxName, percentileName, missingName), null }, }; + // gson = PowerMockito.mock(Gson.class); } @SuppressWarnings("unchecked") @@ -337,46 +250,42 @@ private Object[] getFeaturesForPeriodThrowIllegalStateData() { } @Test - @Parameters(method = "getFeaturesForPeriodData") @SuppressWarnings("unchecked") - public void getFeaturesForPeriod_returnExpectedToListener(List aggs, List featureIds, double[] expected) - throws Exception { - - long start = 100L; - long end = 200L; - when(ParseUtils.generateInternalFeatureQuery(eq(detector), eq(start), eq(end), eq(xContent))).thenReturn(searchSourceBuilder); - when(searchResponse.getAggregations()).thenReturn(new Aggregations(aggs)); - when(detector.getEnabledFeatureIds()).thenReturn(featureIds); + public void getLatestDataTime_returnExpectedToListener() { + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder() + .aggregation(AggregationBuilders.max(CommonName.AGG_NAME_MAX_TIME).field(detector.getTimeField())) + .size(0); + searchRequest.source(searchSourceBuilder); + long epochTime = 100L; + aggsMap.put(CommonName.AGG_NAME_MAX_TIME, max); + when(max.getValue()).thenReturn((double) epochTime); doAnswer(invocation -> { ActionListener listener = invocation.getArgument(1); listener.onResponse(searchResponse); return null; }).when(client).search(eq(searchRequest), any(ActionListener.class)); - ActionListener> listener = mock(ActionListener.class); - searchFeatureDao.getFeaturesForPeriod(detector, start, end, listener); + when(ParseUtils.getLatestDataTime(eq(searchResponse))).thenReturn(Optional.of(epochTime)); + ActionListener> listener = mock(ActionListener.class); + searchFeatureDao.getLatestDataTime(detector, listener); - ArgumentCaptor> captor = ArgumentCaptor.forClass(Optional.class); + ArgumentCaptor> captor = ArgumentCaptor.forClass(Optional.class); verify(listener).onResponse(captor.capture()); - Optional result = captor.getValue(); - assertTrue(Arrays.equals(expected, result.orElse(null))); + Optional result = captor.getValue(); + assertEquals(epochTime, result.get().longValue()); } @Test @SuppressWarnings("unchecked") - public void getFeaturesForPeriod_throwToListener_whenSearchFails() throws Exception { - - long start = 100L; - long end = 200L; - when(ParseUtils.generateInternalFeatureQuery(eq(detector), eq(start), eq(end), eq(xContent))).thenReturn(searchSourceBuilder); + public void getFeaturesForSampledPeriods_throwToListener_whenSamplingFail() { doAnswer(invocation -> { - ActionListener listener = invocation.getArgument(1); + ActionListener> listener = invocation.getArgument(3); listener.onFailure(new RuntimeException()); return null; - }).when(client).search(eq(searchRequest), any(ActionListener.class)); + }).when(searchFeatureDao).getFeaturesForPeriod(any(), anyLong(), anyLong(), any(ActionListener.class)); - ActionListener> listener = mock(ActionListener.class); - searchFeatureDao.getFeaturesForPeriod(detector, start, end, listener); + ActionListener>> listener = mock(ActionListener.class); + searchFeatureDao.getFeaturesForSampledPeriods(detector, 1, 1, 0, listener); verify(listener).onFailure(any(Exception.class)); } @@ -401,166 +310,25 @@ public void getFeaturesForPeriod_throwToListener_whenResponseParsingFails() thro verify(listener).onFailure(any(Exception.class)); } - private Object[] getFeaturesForSampledPeriodsData() { - long endTime = 300_000; - int maxStride = 4; - return new Object[] { - - // No data - - new Object[] { new Long[0][0], new double[0][0], endTime, 1, 1, Optional.empty() }, - - // 1 data point - - new Object[] { - new Long[][] { { 240_000L, 300_000L } }, - new double[][] { { 1, 2 } }, - endTime, - 1, - 1, - Optional.of(new SimpleEntry<>(new double[][] { { 1, 2 } }, 1)) }, - - new Object[] { - new Long[][] { { 240_000L, 300_000L } }, - new double[][] { { 1, 2 } }, - endTime, - 1, - 3, - Optional.of(new SimpleEntry<>(new double[][] { { 1, 2 } }, 1)) }, - - // 2 data points - - new Object[] { - new Long[][] { { 180_000L, 240_000L }, { 240_000L, 300_000L } }, - new double[][] { { 1, 2 }, { 2, 4 } }, - endTime, - 1, - 2, - Optional.of(new SimpleEntry<>(new double[][] { { 1, 2 }, { 2, 4 } }, 1)) }, - - new Object[] { - new Long[][] { { 180_000L, 240_000L }, { 240_000L, 300_000L } }, - new double[][] { { 1, 2 }, { 2, 4 } }, - endTime, - 1, - 1, - Optional.of(new SimpleEntry<>(new double[][] { { 2, 4 } }, 1)) }, - - new Object[] { - new Long[][] { { 180_000L, 240_000L }, { 240_000L, 300_000L } }, - new double[][] { { 1, 2 }, { 2, 4 } }, - endTime, - 4, - 2, - Optional.of(new SimpleEntry<>(new double[][] { { 1, 2 }, { 2, 4 } }, 1)) }, - - new Object[] { - new Long[][] { { 0L, 60_000L }, { 240_000L, 300_000L } }, - new double[][] { { 1, 2 }, { 2, 4 } }, - endTime, - 4, - 2, - Optional.of(new SimpleEntry<>(new double[][] { { 1, 2 }, { 2, 4 } }, 4)) }, - - // 5 data points - - new Object[] { - new Long[][] { - { 0L, 60_000L }, - { 60_000L, 120_000L }, - { 120_000L, 180_000L }, - { 180_000L, 240_000L }, - { 240_000L, 300_000L } }, - new double[][] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 }, { 9, 10 } }, - endTime, - 4, - 10, - Optional.of(new SimpleEntry<>(new double[][] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 }, { 9, 10 } }, 1)) }, - - new Object[] { - new Long[][] { { 0L, 60_000L }, { 60_000L, 120_000L }, { 180_000L, 240_000L }, { 240_000L, 300_000L } }, - new double[][] { { 1, 2 }, { 3, 4 }, { 7, 8 }, { 9, 10 } }, - endTime, - 4, - 10, - Optional.of(new SimpleEntry<>(new double[][] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 }, { 9, 10 } }, 1)) }, - - new Object[] { - new Long[][] { { 0L, 60_000L }, { 120_000L, 180_000L }, { 240_000L, 300_000L } }, - new double[][] { { 1, 2 }, { 5, 6 }, { 9, 10 } }, - endTime, - 4, - 10, - Optional.of(new SimpleEntry<>(new double[][] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 }, { 9, 10 } }, 1)) }, - - new Object[] { - new Long[][] { { 0L, 60_000L }, { 240_000L, 300_000L } }, - new double[][] { { 1, 2 }, { 9, 10 } }, - endTime, - 4, - 10, - Optional.of(new SimpleEntry<>(new double[][] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 }, { 9, 10 } }, 1)) }, }; - } - @Test - @Parameters(method = "getFeaturesForSampledPeriodsData") @SuppressWarnings("unchecked") - public void getFeaturesForSampledPeriods_returnExpectedToListener( - Long[][] queryRanges, - double[][] queryResults, - long endTime, - int maxStride, - int maxSamples, - Optional> expected - ) { - doAnswer(invocation -> { - ActionListener> listener = invocation.getArgument(3); - listener.onResponse(Optional.empty()); - return null; - }).when(searchFeatureDao).getFeaturesForPeriod(any(), anyLong(), anyLong(), any(ActionListener.class)); - for (int i = 0; i < queryRanges.length; i++) { - double[] queryResult = queryResults[i]; - doAnswer(invocation -> { - ActionListener> listener = invocation.getArgument(3); - listener.onResponse(Optional.of(queryResult)); - return null; - }) - .when(searchFeatureDao) - .getFeaturesForPeriod(eq(detector), eq(queryRanges[i][0]), eq(queryRanges[i][1]), any(ActionListener.class)); - } - - ActionListener>> listener = mock(ActionListener.class); - searchFeatureDao.getFeaturesForSampledPeriods(detector, maxSamples, maxStride, endTime, listener); - - ArgumentCaptor>> captor = ArgumentCaptor.forClass(Optional.class); - verify(listener).onResponse(captor.capture()); - Optional> result = captor.getValue(); - assertEquals(expected.isPresent(), result.isPresent()); - if (expected.isPresent()) { - assertTrue(Arrays.deepEquals(expected.get().getKey(), result.get().getKey())); - assertEquals(expected.get().getValue(), result.get().getValue()); - } - } + public void getFeaturesForPeriod_throwToListener_whenSearchFails() throws Exception { - @Test - @SuppressWarnings("unchecked") - public void getFeaturesForSampledPeriods_throwToListener_whenSamplingFail() { + long start = 100L; + long end = 200L; + when(ParseUtils.generateInternalFeatureQuery(eq(detector), eq(start), eq(end), eq(xContent))).thenReturn(searchSourceBuilder); doAnswer(invocation -> { - ActionListener> listener = invocation.getArgument(3); + ActionListener listener = invocation.getArgument(1); listener.onFailure(new RuntimeException()); return null; - }).when(searchFeatureDao).getFeaturesForPeriod(any(), anyLong(), anyLong(), any(ActionListener.class)); + }).when(client).search(eq(searchRequest), any(ActionListener.class)); - ActionListener>> listener = mock(ActionListener.class); - searchFeatureDao.getFeaturesForSampledPeriods(detector, 1, 1, 0, listener); + ActionListener> listener = mock(ActionListener.class); + searchFeatureDao.getFeaturesForPeriod(detector, start, end, listener); verify(listener).onFailure(any(Exception.class)); } - private Entry pair(K key, V value) { - return new SimpleEntry<>(key, value); - } - @SuppressWarnings("unchecked") @Test public void testGetEntityMinDataTime() { diff --git a/src/test/java/org/opensearch/ad/indices/UpdateMappingTests.java b/src/test/java/org/opensearch/ad/indices/UpdateMappingTests.java index 224d4b980..a55ea9a9b 100644 --- a/src/test/java/org/opensearch/ad/indices/UpdateMappingTests.java +++ b/src/test/java/org/opensearch/ad/indices/UpdateMappingTests.java @@ -28,6 +28,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import org.junit.BeforeClass; import org.mockito.ArgumentCaptor; @@ -53,7 +54,6 @@ import org.opensearch.cluster.metadata.Metadata; import org.opensearch.cluster.routing.RoutingTable; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.collect.ImmutableOpenMap; import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.Settings; import org.opensearch.index.IndexNotFoundException; @@ -109,8 +109,8 @@ public void setUp() throws Exception { when(clusterService.getClusterSettings()).thenReturn(clusterSettings); when(clusterService.state()).thenReturn(clusterState); - ImmutableOpenMap.Builder openMapBuilder = ImmutableOpenMap.builder(); - Metadata metadata = Metadata.builder().indices(openMapBuilder.build()).build(); + Map openMap = new HashMap<>(); + Metadata metadata = Metadata.builder().indices(openMap).build(); when(clusterState.getMetadata()).thenReturn(metadata); when(clusterState.metadata()).thenReturn(metadata); @@ -148,12 +148,12 @@ public void testUpdateMapping() throws IOException { doAnswer(invocation -> { ActionListener listener = (ActionListener) invocation.getArgument(1); - ImmutableOpenMap.Builder> builder = ImmutableOpenMap.builder(); + Map> builder = new HashMap<>(); List aliasMetadata = new ArrayList<>(); aliasMetadata.add(AliasMetadata.builder(ADIndex.RESULT.name()).build()); builder.put(resultIndexName, aliasMetadata); - listener.onResponse(new GetAliasesResponse(builder.build())); + listener.onResponse(new GetAliasesResponse(builder)); return null; }).when(indicesAdminClient).getAliases(any(GetAliasesRequest.class), any()); @@ -174,9 +174,9 @@ public void testUpdateMapping() throws IOException { } })) .build(); - ImmutableOpenMap.Builder openMapBuilder = ImmutableOpenMap.builder(); + Map openMapBuilder = new HashMap<>(); openMapBuilder.put(resultIndexName, indexMetadata); - Metadata metadata = Metadata.builder().indices(openMapBuilder.build()).build(); + Metadata metadata = Metadata.builder().indices(openMapBuilder).build(); when(clusterState.getMetadata()).thenReturn(metadata); when(clusterState.metadata()).thenReturn(metadata); adIndices.update(); @@ -186,7 +186,7 @@ public void testUpdateMapping() throws IOException { // since SETTING_AUTO_EXPAND_REPLICAS is set, we won't update @SuppressWarnings("unchecked") public void testJobSettingNoUpdate() { - ImmutableOpenMap.Builder indexToSettings = ImmutableOpenMap.builder(); + Map indexToSettings = new HashMap<>(); Settings jobSettings = Settings .builder() .put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT) @@ -194,7 +194,7 @@ public void testJobSettingNoUpdate() { .put(IndexMetadata.SETTING_AUTO_EXPAND_REPLICAS, "1-all") .build(); indexToSettings.put(ADIndex.JOB.getIndexName(), jobSettings); - GetSettingsResponse getSettingsResponse = new GetSettingsResponse(indexToSettings.build(), ImmutableOpenMap.of()); + GetSettingsResponse getSettingsResponse = new GetSettingsResponse(indexToSettings, new HashMap<>()); doAnswer(invocation -> { ActionListener listener = (ActionListener) invocation.getArgument(2); @@ -207,7 +207,7 @@ public void testJobSettingNoUpdate() { @SuppressWarnings("unchecked") private void setUpSuccessfulGetJobSetting() { - ImmutableOpenMap.Builder indexToSettings = ImmutableOpenMap.builder(); + Map indexToSettings = new HashMap<>(); Settings jobSettings = Settings .builder() .put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT) @@ -215,7 +215,7 @@ private void setUpSuccessfulGetJobSetting() { .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0) .build(); indexToSettings.put(ADIndex.JOB.getIndexName(), jobSettings); - GetSettingsResponse getSettingsResponse = new GetSettingsResponse(indexToSettings.build(), ImmutableOpenMap.of()); + GetSettingsResponse getSettingsResponse = new GetSettingsResponse(indexToSettings, new HashMap<>()); doAnswer(invocation -> { ActionListener listener = (ActionListener) invocation.getArgument(2); @@ -249,10 +249,10 @@ public void testJobSettingUpdate() { // since SETTING_NUMBER_OF_SHARDS is not there, we skip updating @SuppressWarnings("unchecked") public void testMissingPrimaryJobShards() { - ImmutableOpenMap.Builder indexToSettings = ImmutableOpenMap.builder(); + Map indexToSettings = new HashMap<>(); Settings jobSettings = Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT).build(); indexToSettings.put(ADIndex.JOB.getIndexName(), jobSettings); - GetSettingsResponse getSettingsResponse = new GetSettingsResponse(indexToSettings.build(), ImmutableOpenMap.of()); + GetSettingsResponse getSettingsResponse = new GetSettingsResponse(indexToSettings, new HashMap<>()); doAnswer(invocation -> { ActionListener listener = (ActionListener) invocation.getArgument(2); diff --git a/src/test/java/org/opensearch/ad/ml/ModelManagerTests.java b/src/test/java/org/opensearch/ad/ml/ModelManagerTests.java index 9aca51136..1b4797eb6 100644 --- a/src/test/java/org/opensearch/ad/ml/ModelManagerTests.java +++ b/src/test/java/org/opensearch/ad/ml/ModelManagerTests.java @@ -34,7 +34,9 @@ import java.util.ArrayDeque; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.Random; @@ -73,7 +75,6 @@ import org.opensearch.ad.util.DiscoveryNodeFilterer; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.collect.ImmutableOpenMap; import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.Settings; import org.opensearch.common.unit.TimeValue; @@ -287,12 +288,12 @@ public void getDetectorIdForModelId_throwIllegalArgument_forInvalidId(String mod SingleStreamModelIdMapper.getDetectorIdForModelId(modelId); } - private ImmutableOpenMap createDataNodes(int numDataNodes) { - ImmutableOpenMap.Builder dataNodes = ImmutableOpenMap.builder(); + private Map createDataNodes(int numDataNodes) { + Map dataNodes = new HashMap<>(); for (int i = 0; i < numDataNodes; i++) { dataNodes.put("foo" + i, mock(DiscoveryNode.class)); } - return dataNodes.build(); + return dataNodes; } private Object[] getPartitionedForestSizesData() { diff --git a/src/test/java/org/opensearch/ad/transport/DeleteAnomalyDetectorTests.java b/src/test/java/org/opensearch/ad/transport/DeleteAnomalyDetectorTests.java index e7085ee31..d4d66ab66 100644 --- a/src/test/java/org/opensearch/ad/transport/DeleteAnomalyDetectorTests.java +++ b/src/test/java/org/opensearch/ad/transport/DeleteAnomalyDetectorTests.java @@ -20,7 +20,9 @@ import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Optional; import java.util.function.Consumer; @@ -50,7 +52,6 @@ import org.opensearch.cluster.metadata.Metadata; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.bytes.BytesReference; -import org.opensearch.common.collect.ImmutableOpenMap; import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.Settings; import org.opensearch.core.xcontent.ToXContent; @@ -196,9 +197,9 @@ public void testDeleteADTransportAction_GetResponseException() { } private ClusterState createClusterState() { - ImmutableOpenMap immutableOpenMap = ImmutableOpenMap - .builder() - .fPut( + Map immutableOpenMap = new HashMap<>(); + immutableOpenMap + .put( ANOMALY_DETECTOR_JOB_INDEX, IndexMetadata .builder("test") @@ -210,10 +211,20 @@ private ClusterState createClusterState() { .put("index.version.created", Version.CURRENT.id) ) .build() - ) - .build(); + ); Metadata metaData = Metadata.builder().indices(immutableOpenMap).build(); - ClusterState clusterState = new ClusterState(new ClusterName("test_name"), 1l, "uuid", metaData, null, null, null, null, 1, true); + ClusterState clusterState = new ClusterState( + new ClusterName("test_name"), + 1l, + "uuid", + metaData, + null, + null, + null, + new HashMap<>(), + 1, + true + ); return clusterState; } diff --git a/src/test/java/org/opensearch/ad/transport/IndexAnomalyDetectorTransportActionTests.java b/src/test/java/org/opensearch/ad/transport/IndexAnomalyDetectorTransportActionTests.java index b76ba44bb..609546ac7 100644 --- a/src/test/java/org/opensearch/ad/transport/IndexAnomalyDetectorTransportActionTests.java +++ b/src/test/java/org/opensearch/ad/transport/IndexAnomalyDetectorTransportActionTests.java @@ -19,8 +19,10 @@ import java.time.Instant; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Locale; +import java.util.Map; import org.junit.Assert; import org.junit.Before; @@ -49,7 +51,6 @@ import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.metadata.Metadata; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.collect.ImmutableOpenMap; import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.Settings; import org.opensearch.common.unit.TimeValue; @@ -98,10 +99,8 @@ public void setUp() throws Exception { .build(); final Settings.Builder existingSettings = Settings.builder().put(indexSettings).put(IndexMetadata.SETTING_INDEX_UUID, "test2UUID"); IndexMetadata indexMetaData = IndexMetadata.builder(AnomalyDetector.ANOMALY_DETECTORS_INDEX).settings(existingSettings).build(); - final ImmutableOpenMap indices = ImmutableOpenMap - .builder() - .fPut(AnomalyDetector.ANOMALY_DETECTORS_INDEX, indexMetaData) - .build(); + final Map indices = new HashMap<>(); + indices.put(AnomalyDetector.ANOMALY_DETECTORS_INDEX, indexMetaData); ClusterState clusterState = ClusterState.builder(clusterName).metadata(Metadata.builder().indices(indices).build()).build(); when(clusterService.state()).thenReturn(clusterState); diff --git a/src/test/java/org/opensearch/ad/transport/PreviewAnomalyDetectorTransportActionTests.java b/src/test/java/org/opensearch/ad/transport/PreviewAnomalyDetectorTransportActionTests.java index b10c66e7b..dcc9f565a 100644 --- a/src/test/java/org/opensearch/ad/transport/PreviewAnomalyDetectorTransportActionTests.java +++ b/src/test/java/org/opensearch/ad/transport/PreviewAnomalyDetectorTransportActionTests.java @@ -24,6 +24,7 @@ import java.time.Instant; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -63,7 +64,6 @@ import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.metadata.Metadata; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.collect.ImmutableOpenMap; import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; @@ -121,10 +121,8 @@ public void setUp() throws Exception { .build(); final Settings.Builder existingSettings = Settings.builder().put(indexSettings).put(IndexMetadata.SETTING_INDEX_UUID, "test2UUID"); IndexMetadata indexMetaData = IndexMetadata.builder(AnomalyDetector.ANOMALY_DETECTORS_INDEX).settings(existingSettings).build(); - final ImmutableOpenMap indices = ImmutableOpenMap - .builder() - .fPut(AnomalyDetector.ANOMALY_DETECTORS_INDEX, indexMetaData) - .build(); + final Map indices = new HashMap<>(); + indices.put(AnomalyDetector.ANOMALY_DETECTORS_INDEX, indexMetaData); ClusterState clusterState = ClusterState.builder(clusterName).metadata(Metadata.builder().indices(indices).build()).build(); when(clusterService.state()).thenReturn(clusterState); diff --git a/src/test/java/test/org/opensearch/ad/util/ClusterCreation.java b/src/test/java/test/org/opensearch/ad/util/ClusterCreation.java index d9928b623..3eb4fa80a 100644 --- a/src/test/java/test/org/opensearch/ad/util/ClusterCreation.java +++ b/src/test/java/test/org/opensearch/ad/util/ClusterCreation.java @@ -18,7 +18,9 @@ import java.net.InetAddress; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.opensearch.Version; import org.opensearch.cluster.ClusterName; @@ -26,7 +28,6 @@ import org.opensearch.cluster.metadata.Metadata; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.node.DiscoveryNodes; -import org.opensearch.common.collect.ImmutableOpenMap; import org.opensearch.common.transport.TransportAddress; public class ClusterCreation { @@ -66,12 +67,12 @@ public static ClusterState state( * * TODO: ModelManagerTests has the same method. Refactor. */ - public static ImmutableOpenMap createDataNodes(int numDataNodes) { - ImmutableOpenMap.Builder dataNodes = ImmutableOpenMap.builder(); + public static Map createDataNodes(int numDataNodes) { + Map dataNodes = new HashMap<>(); for (int i = 0; i < numDataNodes; i++) { dataNodes.put("foo" + i, mock(DiscoveryNode.class)); } - return dataNodes.build(); + return Collections.unmodifiableMap(dataNodes); } /**