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 e708b1c02..943f0cbfa 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ 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..10ddfd0ca 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 mappings = new HashMap<>(); + + mappings + .put( ANOMALY_DETECTOR_JOB_INDEX, IndexMetadata .builder("test") @@ -1523,10 +1523,24 @@ 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); + ); + + // The usage of Collections.unmodifiableMap is due to replacing ImmutableOpenMap + // with java.util.Map in the core (refer to https://tinyurl.com/5fjdccs3 and https://tinyurl.com/5fjdccs3) + // The meaning and logic of the code stay the same. + Metadata metaData = Metadata.builder().indices(Collections.unmodifiableMap(mappings)).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); } /**