Skip to content

Commit ceed8c4

Browse files
committed
fix(tests): retry stopDetector to absorb AD plugin start/stop race
The live Anomaly Detection round-trip test (create -> get -> preview -> start -> stop) intermittently failed on OpenSearch 2.19.5 (run 28338345697) with "Fail to stop detector". The AD plugin registers the real-time job asynchronously in .opendistro-anomaly-detector-jobs after startDetector acknowledges, so an immediate stop can transiently find no running job. - Wrap stopDetector in a bounded retry helper (10 x 1s) that tolerates only the specific "Fail to stop detector" error, mirroring the existing `waitFor` idiom (CCRSpec/ReindexSpec/TasksSpec). Genuine errors still surface. - Apply the helper to the OS1/OS2/OS3 live round-trip tests.
1 parent 661dfaf commit ceed8c4

1 file changed

Lines changed: 40 additions & 18 deletions

File tree

tests/Test/AnomalyDetectionSpec.hs

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,29 @@
33

44
module Test.AnomalyDetectionSpec (spec) where
55

6+
import Control.Concurrent (threadDelay)
67
import Control.Monad (void)
78
import Control.Monad.Catch (bracket_)
89
import Control.Monad.IO.Class (liftIO)
910
import Data.Aeson (Value (..), decode, encode, object, (.=))
10-
import qualified Data.ByteString.Lazy.Char8 as LBS
11+
import Data.ByteString.Lazy.Char8 qualified as LBS
1112
import Data.Fixed (Pico)
12-
import qualified Data.List as L
13+
import Data.List qualified as L
1314
import Data.Maybe (isJust)
14-
import qualified Data.Text as T
15+
import Data.Text qualified as T
1516
import Data.Time.Calendar (toGregorian)
1617
import Data.Time.Clock (UTCTime (..))
1718
import Data.Time.Clock.POSIX (getPOSIXTime, posixSecondsToUTCTime)
1819
import Data.Time.LocalTime (TimeOfDay (..), timeToTimeOfDay)
19-
import qualified Database.Bloodhound.OpenSearch1.Client as OS1Client
20-
import qualified Database.Bloodhound.OpenSearch1.Requests as OS1Requests
21-
import qualified Database.Bloodhound.OpenSearch1.Types as OS1Types
22-
import qualified Database.Bloodhound.OpenSearch2.Client as OS2Client
23-
import qualified Database.Bloodhound.OpenSearch2.Requests as OS2Requests
24-
import qualified Database.Bloodhound.OpenSearch2.Types as OS2Types
25-
import qualified Database.Bloodhound.OpenSearch3.Client as OS3Client
26-
import qualified Database.Bloodhound.OpenSearch3.Requests as OS3Requests
27-
import qualified Database.Bloodhound.OpenSearch3.Types as OS3Types
20+
import Database.Bloodhound.OpenSearch1.Client qualified as OS1Client
21+
import Database.Bloodhound.OpenSearch1.Requests qualified as OS1Requests
22+
import Database.Bloodhound.OpenSearch1.Types qualified as OS1Types
23+
import Database.Bloodhound.OpenSearch2.Client qualified as OS2Client
24+
import Database.Bloodhound.OpenSearch2.Requests qualified as OS2Requests
25+
import Database.Bloodhound.OpenSearch2.Types qualified as OS2Types
26+
import Database.Bloodhound.OpenSearch3.Client qualified as OS3Client
27+
import Database.Bloodhound.OpenSearch3.Requests qualified as OS3Requests
28+
import Database.Bloodhound.OpenSearch3.Types qualified as OS3Types
2829
import TestsUtils.Common
2930
( os1OnlyIT,
3031
os2OnlyIT,
@@ -1074,7 +1075,7 @@ spec = do
10741075
expectationFailure
10751076
("startDetector failed: " <> T.unpack (errorMessage e))
10761077
Right _ -> pure ()
1077-
stopResp <- OS1Client.stopDetector detectorId False
1078+
stopResp <- stopDetectorWithRetry (OS1Client.stopDetector detectorId False)
10781079
liftIO $
10791080
case stopResp of
10801081
Left e ->
@@ -1086,7 +1087,7 @@ spec = do
10861087
liftIO $ expectationFailureOnLeft updateResp "updateDetector failed:"
10871088
startResp <- OS1Client.startDetector detectorId Nothing
10881089
liftIO $ expectationFailureOnLeft startResp "startDetector failed:"
1089-
stopResp <- OS1Client.stopDetector detectorId False
1090+
stopResp <- stopDetectorWithRetry (OS1Client.stopDetector detectorId False)
10901091
liftIO $ expectationFailureOnLeft stopResp "stopDetector failed:"
10911092
delResp <- OS1Client.deleteDetector detectorId
10921093
liftIO $ expectationFailureOnLeft delResp "deleteDetector failed:"
@@ -1161,7 +1162,7 @@ spec = do
11611162
expectationFailure
11621163
("startDetector failed: " <> T.unpack (errorMessage e))
11631164
Right _ -> pure ()
1164-
stopResp <- OS2Client.stopDetector detectorId False
1165+
stopResp <- stopDetectorWithRetry (OS2Client.stopDetector detectorId False)
11651166
liftIO $
11661167
case stopResp of
11671168
Left e ->
@@ -1173,7 +1174,7 @@ spec = do
11731174
liftIO $ expectationFailureOnLeft updateResp "updateDetector failed:"
11741175
startResp <- OS2Client.startDetector detectorId Nothing
11751176
liftIO $ expectationFailureOnLeft startResp "startDetector failed:"
1176-
stopResp <- OS2Client.stopDetector detectorId False
1177+
stopResp <- stopDetectorWithRetry (OS2Client.stopDetector detectorId False)
11771178
liftIO $ expectationFailureOnLeft stopResp "stopDetector failed:"
11781179
delResp <- OS2Client.deleteDetector detectorId
11791180
liftIO $ expectationFailureOnLeft delResp "deleteDetector failed:"
@@ -1248,7 +1249,7 @@ spec = do
12481249
expectationFailure
12491250
("startDetector failed: " <> T.unpack (errorMessage e))
12501251
Right _ -> pure ()
1251-
stopResp <- OS3Client.stopDetector detectorId False
1252+
stopResp <- stopDetectorWithRetry (OS3Client.stopDetector detectorId False)
12521253
liftIO $
12531254
case stopResp of
12541255
Left e ->
@@ -1260,7 +1261,7 @@ spec = do
12601261
liftIO $ expectationFailureOnLeft updateResp "updateDetector failed:"
12611262
startResp <- OS3Client.startDetector detectorId Nothing
12621263
liftIO $ expectationFailureOnLeft startResp "startDetector failed:"
1263-
stopResp <- OS3Client.stopDetector detectorId False
1264+
stopResp <- stopDetectorWithRetry (OS3Client.stopDetector detectorId False)
12641265
liftIO $ expectationFailureOnLeft stopResp "stopDetector failed:"
12651266
delResp <- OS3Client.deleteDetector detectorId
12661267
liftIO $ expectationFailureOnLeft delResp "deleteDetector failed:"
@@ -1326,3 +1327,24 @@ expectationFailureOnLeft r prefix =
13261327
case r of
13271328
Left e -> expectationFailure (prefix <> " " <> T.unpack (errorMessage e))
13281329
Right _ -> pure ()
1330+
1331+
-- | Tolerate the Anomaly Detection plugin's start\/stop race:
1332+
-- 'startDetector' returns as soon as it acknowledges the start, but the
1333+
-- real-time job is registered asynchronously in the
1334+
-- @.opendistro-anomaly-detector-jobs@ index, so an immediate
1335+
-- 'stopDetector' can transiently receive "Fail to stop detector" from the
1336+
-- server. Retry only that specific error, bounded so a genuine failure
1337+
-- still surfaces. Mirrors the 'waitFor' idiom in "Test.CCRSpec".
1338+
stopDetectorWithRetry ::
1339+
(MonadIO m) =>
1340+
m (Either EsError a) ->
1341+
m (Either EsError a)
1342+
stopDetectorWithRetry stop = go (0 :: Int)
1343+
where
1344+
go n = do
1345+
r <- stop
1346+
case r of
1347+
Left e
1348+
| "Fail to stop detector" `T.isInfixOf` errorMessage e && n < 10 ->
1349+
liftIO (threadDelay 1000000) >> go (n + 1)
1350+
_ -> pure r

0 commit comments

Comments
 (0)